├── .editorconfig ├── .gitattributes ├── .gitignore ├── .template.config └── template.json ├── BlogTemplate.Tests ├── BlogTemplate.Tests.csproj ├── Fakes │ ├── FakeEmailSender.cs │ ├── FakeFileSystem.cs │ ├── FakeFormFile.cs │ ├── FakeLogger.cs │ ├── FakeSignInManager.cs │ └── FakeUserManager.cs ├── Models │ └── BlogDataStoreTests.cs ├── Pages │ ├── Account │ │ └── RegisterTests.cs │ ├── DraftsTests.cs │ ├── EditTests.cs │ ├── IndexTests.cs │ ├── NewTests.cs │ └── TrashTests.cs └── Services │ ├── ExcerptGeneratorTests.cs │ └── SlugGeneratorTests.cs ├── BlogTemplate.sln ├── BlogTemplate ├── .bowerrc ├── BlogTemplate.csproj ├── BlogTemplate.slnOLD ├── Controllers │ └── AccountController.cs ├── Data │ ├── ApplicationDbContext.cs │ ├── ApplicationUser.cs │ └── Migrations │ │ ├── 00000000000000_CreateIdentitySchema.Designer.cs │ │ ├── 00000000000000_CreateIdentitySchema.cs │ │ └── ApplicationDbContextModelSnapshot.cs ├── Extensions │ ├── EmailSenderExtensions.cs │ └── UrlHelperExtensions.cs ├── Models │ ├── BlogDataStore.cs │ ├── Comment.cs │ ├── IFileSystem.cs │ ├── PhysicalFileSystem.cs │ └── Post.cs ├── Pages │ ├── About.cshtml │ ├── About.cshtml.cs │ ├── Account │ │ ├── AccessDenied.cshtml │ │ ├── AccessDenied.cshtml.cs │ │ ├── ConfirmEmail.cshtml │ │ ├── ConfirmEmail.cshtml.cs │ │ ├── ExternalLogin.cshtml │ │ ├── ExternalLogin.cshtml.cs │ │ ├── ForgotPassword.cshtml │ │ ├── ForgotPassword.cshtml.cs │ │ ├── ForgotPasswordConfirmation.cshtml │ │ ├── ForgotPasswordConfirmation.cshtml.cs │ │ ├── Lockout.cshtml │ │ ├── Lockout.cshtml.cs │ │ ├── Login.cshtml │ │ ├── Login.cshtml.cs │ │ ├── LoginWith2fa.cshtml │ │ ├── LoginWith2fa.cshtml.cs │ │ ├── LoginWithRecoveryCode.cshtml │ │ ├── LoginWithRecoveryCode.cshtml.cs │ │ ├── Manage │ │ │ ├── ChangePassword.cshtml │ │ │ ├── ChangePassword.cshtml.cs │ │ │ ├── Disable2fa.cshtml │ │ │ ├── Disable2fa.cshtml.cs │ │ │ ├── EnableAuthenticator.cshtml │ │ │ ├── EnableAuthenticator.cshtml.cs │ │ │ ├── ExternalLogins.cshtml │ │ │ ├── ExternalLogins.cshtml.cs │ │ │ ├── GenerateRecoveryCodes.cshtml │ │ │ ├── GenerateRecoveryCodes.cshtml.cs │ │ │ ├── Index.cshtml │ │ │ ├── Index.cshtml.cs │ │ │ ├── ManageNavPages.cs │ │ │ ├── ResetAuthenticator.cshtml │ │ │ ├── ResetAuthenticator.cshtml.cs │ │ │ ├── SetPassword.cshtml │ │ │ ├── SetPassword.cshtml.cs │ │ │ ├── TwoFactorAuthentication.cshtml │ │ │ ├── TwoFactorAuthentication.cshtml.cs │ │ │ ├── _Layout.cshtml │ │ │ ├── _ManageNav.cshtml │ │ │ ├── _StatusMessage.cshtml │ │ │ └── _ViewImports.cshtml │ │ ├── Register.cshtml │ │ ├── Register.cshtml.cs │ │ ├── ResetPassword.cshtml │ │ ├── ResetPassword.cshtml.cs │ │ ├── ResetPasswordConfirmation.cshtml │ │ ├── ResetPasswordConfirmation.cshtml.cs │ │ ├── SignedOut.cshtml │ │ ├── SignedOut.cshtml.cs │ │ └── _ViewImports.cshtml │ ├── AlreadyRegistered.cshtml │ ├── Drafts.cshtml │ ├── Drafts.cshtml.cs │ ├── Edit.cshtml │ ├── Edit.cshtml.cs │ ├── Error.cshtml │ ├── Error.cshtml.cs │ ├── Index.cshtml │ ├── Index.cshtml.cs │ ├── ManageComment.cshtml │ ├── ManageComment.cshtml.cs │ ├── New.cshtml │ ├── New.cshtml.cs │ ├── Post.cshtml │ ├── Post.cshtml.cs │ ├── Trash.cshtml │ ├── Trash.cshtml.cs │ ├── _Layout.cshtml │ ├── _LoginPartial.cshtml │ ├── _ValidationScriptsPartial.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml ├── Program.cs ├── Properties │ └── launchSettings.json ├── Services │ ├── EmailSender.cs │ ├── ExcerptGenerator.cs │ ├── IEmailSender.cs │ ├── MarkdownRenderer.cs │ └── SlugGenerator.cs ├── Startup.cs ├── appsettings.Development.json ├── appsettings.json ├── bower.json ├── bundleconfig.json └── wwwroot │ ├── css │ ├── bootstrap-theme.css │ ├── bootstrap-theme.css.map │ ├── bootstrap-theme.min.css │ ├── bootstrap-theme.min.css.map │ ├── bootstrap.css │ ├── bootstrap.css.map │ ├── bootstrap.min.css │ ├── bootstrap.min.css.map │ ├── journal-bootstrap.min.css │ ├── site.css │ └── site.min.css │ ├── favicon.ico │ ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 │ ├── js │ ├── bootstrap.js │ ├── bootstrap.min.js │ ├── npm.js │ ├── site.js │ ├── site.min.js │ ├── slugUpdateWarning.js │ └── updateBody.js │ └── lib │ ├── bootstrap │ ├── .bower.json │ ├── CHANGELOG.md │ ├── Gemfile │ ├── Gemfile.lock │ ├── Gruntfile.js │ ├── ISSUE_TEMPLATE.md │ ├── LICENSE │ ├── README.md │ ├── bower.json │ ├── dist │ │ ├── css │ │ │ ├── bootstrap-theme.css │ │ │ ├── bootstrap-theme.css.map │ │ │ ├── bootstrap-theme.min.css │ │ │ ├── bootstrap-theme.min.css.map │ │ │ ├── bootstrap.css │ │ │ ├── bootstrap.css.map │ │ │ ├── bootstrap.min.css │ │ │ └── bootstrap.min.css.map │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ └── glyphicons-halflings-regular.woff2 │ │ └── js │ │ │ ├── bootstrap.js │ │ │ ├── bootstrap.min.js │ │ │ └── npm.js │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ ├── grunt │ │ ├── .jshintrc │ │ ├── bs-commonjs-generator.js │ │ ├── bs-glyphicons-data-generator.js │ │ ├── bs-lessdoc-parser.js │ │ ├── bs-raw-files-generator.js │ │ ├── change-version.js │ │ ├── configBridge.json │ │ ├── npm-shrinkwrap.json │ │ └── sauce_browsers.yml │ ├── js │ │ ├── .jscsrc │ │ ├── .jshintrc │ │ ├── affix.js │ │ ├── alert.js │ │ ├── button.js │ │ ├── carousel.js │ │ ├── collapse.js │ │ ├── dropdown.js │ │ ├── modal.js │ │ ├── popover.js │ │ ├── scrollspy.js │ │ ├── tab.js │ │ ├── tooltip.js │ │ └── transition.js │ ├── less │ │ ├── .csscomb.json │ │ ├── .csslintrc │ │ ├── alerts.less │ │ ├── badges.less │ │ ├── bootstrap.less │ │ ├── breadcrumbs.less │ │ ├── button-groups.less │ │ ├── buttons.less │ │ ├── carousel.less │ │ ├── close.less │ │ ├── code.less │ │ ├── component-animations.less │ │ ├── dropdowns.less │ │ ├── forms.less │ │ ├── glyphicons.less │ │ ├── grid.less │ │ ├── input-groups.less │ │ ├── jumbotron.less │ │ ├── labels.less │ │ ├── list-group.less │ │ ├── media.less │ │ ├── mixins.less │ │ ├── mixins │ │ │ ├── alerts.less │ │ │ ├── background-variant.less │ │ │ ├── border-radius.less │ │ │ ├── buttons.less │ │ │ ├── center-block.less │ │ │ ├── clearfix.less │ │ │ ├── forms.less │ │ │ ├── gradients.less │ │ │ ├── grid-framework.less │ │ │ ├── grid.less │ │ │ ├── hide-text.less │ │ │ ├── image.less │ │ │ ├── labels.less │ │ │ ├── list-group.less │ │ │ ├── nav-divider.less │ │ │ ├── nav-vertical-align.less │ │ │ ├── opacity.less │ │ │ ├── pagination.less │ │ │ ├── panels.less │ │ │ ├── progress-bar.less │ │ │ ├── reset-filter.less │ │ │ ├── reset-text.less │ │ │ ├── resize.less │ │ │ ├── responsive-visibility.less │ │ │ ├── size.less │ │ │ ├── tab-focus.less │ │ │ ├── table-row.less │ │ │ ├── text-emphasis.less │ │ │ ├── text-overflow.less │ │ │ └── vendor-prefixes.less │ │ ├── modals.less │ │ ├── navbar.less │ │ ├── navs.less │ │ ├── normalize.less │ │ ├── pager.less │ │ ├── pagination.less │ │ ├── panels.less │ │ ├── popovers.less │ │ ├── print.less │ │ ├── progress-bars.less │ │ ├── responsive-embed.less │ │ ├── responsive-utilities.less │ │ ├── scaffolding.less │ │ ├── tables.less │ │ ├── theme.less │ │ ├── thumbnails.less │ │ ├── tooltip.less │ │ ├── type.less │ │ ├── utilities.less │ │ ├── variables.less │ │ └── wells.less │ ├── nuget │ │ ├── MyGet.ps1 │ │ ├── bootstrap.less.nuspec │ │ └── bootstrap.nuspec │ ├── package.js │ └── package.json │ ├── jquery-validation-unobtrusive │ ├── .bower.json │ ├── jquery.validate.unobtrusive.js │ └── jquery.validate.unobtrusive.min.js │ ├── jquery-validation │ ├── .bower.json │ ├── LICENSE.md │ └── dist │ │ ├── additional-methods.js │ │ └── jquery.validate.js │ └── jquery │ ├── .bower.json │ ├── LICENSE.txt │ └── dist │ ├── jquery.js │ └── jquery.min.map ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── TechnicalBlogPost.md ├── ThirdPartyNotices ├── css ├── bootstrap-theme.css ├── bootstrap-theme.css.map ├── bootstrap-theme.min.css ├── bootstrap-theme.min.css.map ├── bootstrap.css ├── bootstrap.css.map ├── bootstrap.min.css ├── bootstrap.min.css.map └── journal-bootstrap.min.css ├── fonts ├── glyphicons-halflings-regular.eot ├── glyphicons-halflings-regular.svg ├── glyphicons-halflings-regular.ttf ├── glyphicons-halflings-regular.woff └── glyphicons-halflings-regular.woff2 └── js ├── bootstrap.js ├── bootstrap.min.js └── npm.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Newline ending every file 7 | [*] 8 | end_of_line = crlf 9 | indent_style = space 10 | indent_size = 4 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | charset = utf-8 14 | 15 | [**/*.json] 16 | indent_size = 2 17 | 18 | 19 | [**/*.js] 20 | indent_size = 4 21 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * -text 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Me", 3 | "classifications": [], 4 | "name": "Blog Template", 5 | "groupIdentity": "Microsoft.DotNet.Web.BlogTemplate", 6 | "identity": "Microsoft.DotNet.Web.BlogTemplate.CSharp", 7 | "shortName": "blog", 8 | "tags": { 9 | "language": "C#", 10 | "type": "project" 11 | }, 12 | "sourceName": "BlogTemplate.1", 13 | "guids": [], 14 | "preferNameDirectory": true, 15 | "primaryOutputs": [ 16 | { "path": "BlogTemplate.1.csproj" } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /BlogTemplate.Tests/BlogTemplate.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /BlogTemplate.Tests/Fakes/FakeEmailSender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using BlogTemplate._1.Services; 6 | 7 | namespace BlogTemplate._1.Tests.Fakes 8 | { 9 | class FakeEmailSender : IEmailSender 10 | { 11 | public Task SendEmailAsync(string email, string subject, string message) 12 | { 13 | throw new NotImplementedException(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /BlogTemplate.Tests/Fakes/FakeFormFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.AspNetCore.Http; 8 | 9 | namespace BlogTemplate._1.Tests.Fakes 10 | { 11 | class FakeFormFile : IFormFile 12 | { 13 | public string ContentType { get; set; } 14 | 15 | public string ContentDisposition => throw new NotImplementedException(); 16 | 17 | public IHeaderDictionary Headers => throw new NotImplementedException(); 18 | 19 | public long Length { get; set; } 20 | 21 | public string Name { get; set; } 22 | 23 | public string FileName { get; set; } 24 | 25 | public void CopyTo(Stream target) 26 | { 27 | throw new NotImplementedException(); 28 | } 29 | 30 | public Task CopyToAsync(Stream target, CancellationToken cancellationToken = default(CancellationToken)) 31 | { 32 | throw new NotImplementedException(); 33 | } 34 | 35 | public Stream OpenReadStream() 36 | { 37 | return new MemoryStream(new byte[this.Length]); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /BlogTemplate.Tests/Fakes/FakeLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace BlogTemplate._1.Tests.Fakes 5 | { 6 | class FakeLogger : ILogger 7 | { 8 | public IDisposable BeginScope(TState state) 9 | { 10 | throw new NotImplementedException(); 11 | } 12 | 13 | public bool IsEnabled(LogLevel logLevel) 14 | { 15 | throw new NotImplementedException(); 16 | } 17 | 18 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) 19 | { 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /BlogTemplate.Tests/Fakes/FakeSignInManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using BlogTemplate._1.Data; 6 | using Microsoft.AspNetCore.Authentication; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.AspNetCore.Identity; 9 | using Microsoft.Extensions.Logging; 10 | using Microsoft.Extensions.Options; 11 | 12 | namespace BlogTemplate._1.Tests.Fakes 13 | { 14 | class FakeSignInManager : SignInManager 15 | { 16 | public FakeSignInManager() 17 | : this(new FakeUserManager(), null, null, null, null, null) 18 | { 19 | } 20 | 21 | public FakeSignInManager(UserManager userManager, IHttpContextAccessor contextAccessor, IUserClaimsPrincipalFactory claimsFactory, IOptions optionsAccessor, ILogger> logger, IAuthenticationSchemeProvider schemes) 22 | : base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes) 23 | { 24 | } 25 | 26 | public override Task SignInAsync(ApplicationUser user, bool isPersistent, string authenticationMethod = null) 27 | { 28 | return Task.FromResult(IdentityResult.Success); 29 | } 30 | 31 | } 32 | 33 | } 34 | 35 | -------------------------------------------------------------------------------- /BlogTemplate.Tests/Pages/Account/RegisterTests.cs: -------------------------------------------------------------------------------- 1 | using BlogTemplate._1.Data; 2 | using BlogTemplate._1.Pages.Account; 3 | using BlogTemplate._1.Tests.Fakes; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Threading; 9 | using Xunit; 10 | using Microsoft.AspNetCore.Identity; 11 | 12 | namespace BlogTemplate._1.Tests.Pages.Account 13 | { 14 | public class RegisterTests 15 | { 16 | 17 | [Fact] 18 | public void RegisterUser_CreateSecondUser_RedirectToUserAlreadyExistsPage() 19 | { 20 | // Arrange 21 | FakeUserManager fakeUserManager = new FakeUserManager(); 22 | fakeUserManager.SetUsers(new List(new ApplicationUser[] { 23 | new ApplicationUser 24 | { 25 | UserName = "Test User 1", 26 | } 27 | }).AsQueryable()); 28 | 29 | RegisterModel registerModel = new RegisterModel(fakeUserManager, null, null, null); 30 | registerModel.PageContext = new PageContext(); 31 | 32 | // Act 33 | IActionResult result = registerModel.OnPostAsync().Result; 34 | 35 | // Assert 36 | Assert.IsType(typeof(RedirectToPageResult), result); 37 | } 38 | 39 | [Fact] 40 | public void RegisterUser_FirstUser_LocalRedirect() 41 | { 42 | // Arrange 43 | FakeUserManager fakeUserManager = new FakeUserManager(); 44 | fakeUserManager.SetUsers(new List(new ApplicationUser[] {}).AsQueryable()); 45 | 46 | FakeSignInManager fakeSignInManager = new FakeSignInManager(); 47 | 48 | RegisterModel registerModel = new RegisterModel(fakeUserManager, fakeSignInManager, new FakeLogger(), null); 49 | registerModel.PageContext = new PageContext(); 50 | 51 | registerModel.Input = new RegisterModel.InputModel 52 | { 53 | Email = "test@test.com", 54 | Password = "TestPassword.1", 55 | }; 56 | 57 | // Act 58 | IActionResult result = registerModel.OnPostAsync().Result; 59 | 60 | // Assert 61 | Assert.IsType(typeof(LocalRedirectResult), result); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /BlogTemplate.Tests/Pages/DraftsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using BlogTemplate._1.Models; 5 | using BlogTemplate._1.Pages; 6 | using BlogTemplate._1.Tests.Fakes; 7 | using Xunit; 8 | 9 | namespace BlogTemplate._1.Tests.Pages 10 | { 11 | public class DraftsTests 12 | { 13 | [Fact] 14 | public void GetDrafts_ShowDraftSummaries() 15 | { 16 | IFileSystem fakeFileSystem = new FakeFileSystem(); 17 | BlogDataStore testDataStore = new BlogDataStore(fakeFileSystem); 18 | 19 | Post draftPost = new Post 20 | { 21 | Title = "Draft Post", 22 | IsPublic = false, 23 | PubDate = DateTime.UtcNow, 24 | }; 25 | Post publishedPost = new Post 26 | { 27 | Title = "Published Post", 28 | IsPublic = true, 29 | PubDate = DateTime.UtcNow, 30 | }; 31 | 32 | testDataStore.SavePost(draftPost); 33 | testDataStore.SavePost(publishedPost); 34 | 35 | DraftsModel testDraftsModel = new DraftsModel(testDataStore); 36 | testDraftsModel.OnGet(); 37 | DraftSummaryModel testDraftSummaryModel = testDraftsModel.DraftSummaries.First(); 38 | 39 | Assert.Equal(1, testDraftsModel.DraftSummaries.Count()); 40 | Assert.Equal(draftPost.Id, testDraftSummaryModel.Id); 41 | Assert.Equal("Draft Post", testDraftSummaryModel.Title); 42 | Assert.Equal(draftPost.PubDate, testDraftSummaryModel.PublishTime); 43 | Assert.Equal(0, testDraftSummaryModel.CommentCount); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /BlogTemplate.Tests/Pages/IndexTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using BlogTemplate._1.Models; 5 | using BlogTemplate._1.Pages; 6 | using BlogTemplate._1.Tests.Fakes; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.AspNetCore.Mvc.RazorPages; 9 | using Xunit; 10 | 11 | namespace BlogTemplate._1.Tests.Pages 12 | { 13 | public class IndexTests 14 | { 15 | [Fact] 16 | public void Index_PostSummary_DoesNotIncludeDeletedComments() 17 | { 18 | BlogDataStore testDataStore = new BlogDataStore(new FakeFileSystem()); 19 | testDataStore.SavePost(new Post { 20 | Comments = new List { 21 | new Comment { 22 | Body = "Test comment 1", 23 | IsPublic = true, 24 | }, 25 | new Comment { 26 | Body = "Deleted comment 1", 27 | IsPublic = false, 28 | }, 29 | }, 30 | IsPublic = true, 31 | PubDate = DateTimeOffset.Now, 32 | }); 33 | 34 | IndexModel model = new IndexModel(testDataStore); 35 | model.OnGet(); 36 | 37 | Assert.Equal(1, model.PostSummaries.Count()); 38 | Assert.Equal(1, model.PostSummaries.First().CommentCount); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /BlogTemplate.Tests/Pages/NewTests.cs: -------------------------------------------------------------------------------- 1 | using BlogTemplate._1.Models; 2 | using BlogTemplate._1.Pages; 3 | using BlogTemplate._1.Services; 4 | using BlogTemplate._1.Tests.Fakes; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | using Xunit; 7 | 8 | namespace BlogTemplate._1.Tests.Pages 9 | { 10 | public class NewTests 11 | { 12 | [Fact] 13 | public void OnPostPublish_NoExcerptIsEntered_AutoGenerateExcerpt() 14 | { 15 | IFileSystem fakeFileSystem = new FakeFileSystem(); 16 | BlogDataStore testDataStore = new BlogDataStore(fakeFileSystem); 17 | ExcerptGenerator testExcerptGenerator = new ExcerptGenerator(5); 18 | SlugGenerator testSlugGenerator = new SlugGenerator(testDataStore); 19 | 20 | NewModel model = new NewModel(testDataStore, testSlugGenerator, testExcerptGenerator); 21 | model.PageContext = new PageContext(); 22 | model.NewPost = new NewModel.NewPostViewModel { 23 | Title = "Title", 24 | Body = "This is the body", 25 | }; 26 | 27 | model.OnPostPublish(); 28 | 29 | Assert.Equal("This is the body", model.NewPost.Body); 30 | Assert.Equal("This ...", model.NewPost.Excerpt); 31 | } 32 | 33 | [Fact] 34 | public void OnPostSaveDraft_NoExcerptIsEntered_AutoGenerateExcerpt() 35 | { 36 | IFileSystem fakeFileSystem = new FakeFileSystem(); 37 | BlogDataStore testDataStore = new BlogDataStore(fakeFileSystem); 38 | ExcerptGenerator testExcerptGenerator = new ExcerptGenerator(5); 39 | SlugGenerator testSlugGenerator = new SlugGenerator(testDataStore); 40 | 41 | NewModel model = new NewModel(testDataStore, testSlugGenerator, testExcerptGenerator); 42 | model.PageContext = new PageContext(); 43 | model.NewPost = new NewModel.NewPostViewModel { 44 | Title = "Title", 45 | Body = "This is the body", 46 | }; 47 | 48 | model.OnPostSaveDraft(); 49 | 50 | Assert.Equal("This is the body", model.NewPost.Body); 51 | Assert.Equal("This ...", model.NewPost.Excerpt); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /BlogTemplate.Tests/Pages/TrashTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using BlogTemplate._1.Models; 6 | using BlogTemplate._1.Pages; 7 | using BlogTemplate._1.Services; 8 | using BlogTemplate._1.Tests.Fakes; 9 | using Microsoft.AspNetCore.Mvc; 10 | using Microsoft.AspNetCore.Mvc.RazorPages; 11 | using Xunit; 12 | 13 | namespace BlogTemplate._1.Tests.Pages 14 | { 15 | public class TrashTests 16 | { 17 | [Fact] 18 | public void DeletePost_MoveToTrash() 19 | { 20 | IFileSystem fakeFileSystem = new FakeFileSystem(); 21 | BlogDataStore testDataStore = new BlogDataStore(fakeFileSystem); 22 | MarkdownRenderer markdownRenderer = new MarkdownRenderer(); 23 | PostModel testPostModel = new PostModel(testDataStore, markdownRenderer); 24 | testPostModel.PageContext = new PageContext(); 25 | 26 | Post post = new Post 27 | { 28 | Title = "Title", 29 | Body = "This is the body of my post", 30 | IsDeleted = false, 31 | }; 32 | testDataStore.SavePost(post); 33 | 34 | testPostModel.OnPostDeletePost(post.Id.ToString("N")); 35 | Post result = testDataStore.GetPost(post.Id.ToString("N")); 36 | 37 | Assert.True(result.IsDeleted); 38 | } 39 | 40 | [Fact] 41 | public void UnDeletePost_MoveToIndex() 42 | { 43 | IFileSystem fakeFileSystem = new FakeFileSystem(); 44 | BlogDataStore testDataStore = new BlogDataStore(fakeFileSystem); 45 | MarkdownRenderer markdownRenderer = new MarkdownRenderer(); 46 | PostModel testPostModel = new PostModel(testDataStore, markdownRenderer); 47 | testPostModel.PageContext = new PageContext(); 48 | 49 | Post post = new Post 50 | { 51 | Title = "Title", 52 | Body = "This is the body of my post", 53 | IsDeleted = true, 54 | }; 55 | testDataStore.SavePost(post); 56 | 57 | testPostModel.OnPostUnDeletePost(post.Id.ToString("N")); 58 | Post result = testDataStore.GetPost(post.Id.ToString("N")); 59 | 60 | Assert.False(result.IsDeleted); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /BlogTemplate.Tests/Services/ExcerptGeneratorTests.cs: -------------------------------------------------------------------------------- 1 | using BlogTemplate._1.Models; 2 | using BlogTemplate._1.Services; 3 | using BlogTemplate._1.Tests.Fakes; 4 | using Xunit; 5 | 6 | namespace BlogTemplate._1.Tests.Services 7 | { 8 | public class ExcerptGeneratorTests 9 | { 10 | [Fact] 11 | public void CreateExcerpt_BodyLengthExceedsMaxLength_ExcerptIsTruncated() 12 | { 13 | BlogDataStore testDataStore = new BlogDataStore(new FakeFileSystem()); 14 | ExcerptGenerator testExcerptGenerator = new ExcerptGenerator(5); 15 | string testExcerpt = testExcerptGenerator.CreateExcerpt("This is the body"); 16 | Assert.Equal("This ...", testExcerpt); 17 | } 18 | [Fact] 19 | public void CreateExcerpt_BodyLengthDoesNotExceedMaxLength_ExcerptEqualsBody() 20 | { 21 | BlogDataStore testDataStore = new BlogDataStore(new FakeFileSystem()); 22 | ExcerptGenerator testExcerptGenerator = new ExcerptGenerator(50); 23 | string testExcerpt = testExcerptGenerator.CreateExcerpt("This is the body"); 24 | Assert.Equal("This is the body", testExcerpt); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /BlogTemplate.Tests/Services/SlugGeneratorTests.cs: -------------------------------------------------------------------------------- 1 | using BlogTemplate._1.Models; 2 | using BlogTemplate._1.Services; 3 | using BlogTemplate._1.Tests.Fakes; 4 | using Xunit; 5 | 6 | namespace BlogTemplate._1.Tests.Services 7 | { 8 | public class SlugGeneratorTests 9 | { 10 | [Fact] 11 | public void CreateSlug_ReplacesSpaces() 12 | { 13 | IFileSystem testFileSystem = new FakeFileSystem(); 14 | BlogDataStore testDataStore = new BlogDataStore(testFileSystem); 15 | SlugGenerator testSlugGenerator = new SlugGenerator(testDataStore); 16 | Post test = new Post 17 | { 18 | Title = "Test title" 19 | }; 20 | test.Slug = testSlugGenerator.CreateSlug(test.Title); 21 | 22 | Assert.Equal(test.Slug, "Test-title"); 23 | } 24 | 25 | [Theory] 26 | [InlineData("test?", "test")] 27 | [InlineData("test<", "test")] 28 | [InlineData("test>", "test")] 29 | [InlineData("test/", "test")] 30 | [InlineData("test&", "test")] 31 | [InlineData("test!", "test")] 32 | [InlineData("test#", "test")] 33 | [InlineData("test''", "test")] 34 | [InlineData("test|", "test")] 35 | [InlineData("test©", "test")] 36 | [InlineData("test%", "test")] 37 | public void CreateSlug_TitleContainsInvalidChars_RemoveInvalidCharsInSlug(string input, string expected) 38 | { 39 | BlogDataStore testDataStore = new BlogDataStore(new FakeFileSystem()); 40 | SlugGenerator testSlugGenerator = new SlugGenerator(testDataStore); 41 | 42 | Assert.Equal(expected, testSlugGenerator.CreateSlug(input)); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /BlogTemplate.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26814.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlogTemplate", "BlogTemplate\BlogTemplate.csproj", "{F35A3EB7-533E-44B0-B153-4B20A5C0B6FF}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{58901495-13DD-47A5-BA4A-AB01E3E04C47}" 9 | ProjectSection(SolutionItems) = preProject 10 | .gitignore = .gitignore 11 | README.md = README.md 12 | EndProjectSection 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlogTemplate.Tests", "BlogTemplate.Tests\BlogTemplate.Tests.csproj", "{5824F803-D8E8-4EA4-A074-E68464F0CFF0}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {F35A3EB7-533E-44B0-B153-4B20A5C0B6FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {F35A3EB7-533E-44B0-B153-4B20A5C0B6FF}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {F35A3EB7-533E-44B0-B153-4B20A5C0B6FF}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {F35A3EB7-533E-44B0-B153-4B20A5C0B6FF}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {5824F803-D8E8-4EA4-A074-E68464F0CFF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {5824F803-D8E8-4EA4-A074-E68464F0CFF0}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {5824F803-D8E8-4EA4-A074-E68464F0CFF0}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {5824F803-D8E8-4EA4-A074-E68464F0CFF0}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {B7843AA1-190E-4A15-9F90-ED4C35F900FB} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /BlogTemplate/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "wwwroot/lib" 3 | } 4 | -------------------------------------------------------------------------------- /BlogTemplate/BlogTemplate.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.0 4 | aspnet-BlogTemplate-42899553-8CF4-4C54-9A35-69ADC51F0DA0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /BlogTemplate/BlogTemplate.slnOLD: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlogTemplate", "BlogTemplate.csproj", "{458B4C76-B35C-43D1-B4F3-96E59B5FE4C7}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{241E87F9-EF37-4AEB-B356-02F5E4BC60BE}" 9 | ProjectSection(SolutionItems) = preProject 10 | ..\.editorconfig = ..\.editorconfig 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {458B4C76-B35C-43D1-B4F3-96E59B5FE4C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {458B4C76-B35C-43D1-B4F3-96E59B5FE4C7}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {458B4C76-B35C-43D1-B4F3-96E59B5FE4C7}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {458B4C76-B35C-43D1-B4F3-96E59B5FE4C7}.Release|Any CPU.Build.0 = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | GlobalSection(ExtensibilityGlobals) = postSolution 28 | SolutionGuid = {02877152-60CF-4052-A1B1-0E03D3937046} 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /BlogTemplate/Controllers/AccountController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.Extensions.Logging; 8 | using BlogTemplate._1.Data; 9 | using Microsoft.AspNetCore.Authorization; 10 | 11 | namespace BlogTemplate._1.Controllers 12 | { 13 | [Route("[controller]/[action]")] 14 | public class AccountController : Controller 15 | { 16 | private readonly SignInManager _signInManager; 17 | private readonly ILogger _logger; 18 | 19 | public AccountController(SignInManager signInManager, ILogger logger) 20 | { 21 | _signInManager = signInManager; 22 | _logger = logger; 23 | } 24 | 25 | [HttpPost] 26 | [ValidateAntiForgeryToken] 27 | [Authorize] 28 | public async Task Logout() 29 | { 30 | await _signInManager.SignOutAsync(); 31 | _logger.LogInformation("User logged out."); 32 | return RedirectToPage("/Index"); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /BlogTemplate/Data/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 6 | using Microsoft.EntityFrameworkCore; 7 | 8 | namespace BlogTemplate._1.Data 9 | { 10 | public class ApplicationDbContext : IdentityDbContext 11 | { 12 | public ApplicationDbContext(DbContextOptions options) 13 | : base(options) 14 | { 15 | } 16 | 17 | protected override void OnModelCreating(ModelBuilder builder) 18 | { 19 | base.OnModelCreating(builder); 20 | // Customize the ASP.NET Identity model and override the defaults if needed. 21 | // For example, you can rename the ASP.NET Identity table names and more. 22 | // Add your customizations after calling base.OnModelCreating(builder); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /BlogTemplate/Data/ApplicationUser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Identity; 6 | 7 | namespace BlogTemplate._1.Data 8 | { 9 | // Add profile data for application users by adding properties to the ApplicationUser class 10 | public class ApplicationUser : IdentityUser 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /BlogTemplate/Extensions/EmailSenderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.Encodings.Web; 5 | using System.Threading.Tasks; 6 | using BlogTemplate._1.Services; 7 | 8 | namespace BlogTemplate._1.Services 9 | { 10 | public static class EmailSenderExtensions 11 | { 12 | public static Task SendEmailConfirmationAsync(this IEmailSender emailSender, string email, string link) 13 | { 14 | return emailSender.SendEmailAsync(email, "Confirm your email", 15 | $"Please confirm your account by clicking here."); 16 | } 17 | 18 | public static Task SendResetPasswordAsync(this IEmailSender emailSender, string email, string callbackUrl) 19 | { 20 | return emailSender.SendEmailAsync(email, "Reset Password", 21 | $"Please reset your password by clicking here."); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /BlogTemplate/Extensions/UrlHelperExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Microsoft.AspNetCore.Mvc 7 | { 8 | public static class UrlHelperExtensions 9 | { 10 | public static string GetLocalUrl(this IUrlHelper urlHelper, string localUrl) 11 | { 12 | if (!urlHelper.IsLocalUrl(localUrl)) 13 | { 14 | return urlHelper.Page("/Index"); 15 | } 16 | 17 | return localUrl; 18 | } 19 | 20 | public static string EmailConfirmationLink(this IUrlHelper urlHelper, string userId, string code, string scheme) 21 | { 22 | return urlHelper.Page( 23 | "/Account/ConfirmEmail", 24 | pageHandler: null, 25 | values: new { userId, code }, 26 | protocol: scheme); 27 | } 28 | 29 | public static string ResetPasswordCallbackLink(this IUrlHelper urlHelper, string userId, string code, string scheme) 30 | { 31 | return urlHelper.Page( 32 | "/Account/ResetPassword", 33 | pageHandler: null, 34 | values: new { userId, code }, 35 | protocol: scheme); 36 | } 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /BlogTemplate/Models/Comment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace BlogTemplate._1.Models 5 | { 6 | public class Comment 7 | { 8 | [Required(ErrorMessage = "Name required")] 9 | public string AuthorName { get; set; } 10 | [Required(ErrorMessage = "Comment text required")] 11 | public string Body { get; set; } 12 | public DateTimeOffset PubDate { get; set; } = DateTimeOffset.Now; 13 | public bool IsPublic { get; set; } 14 | public Guid UniqueId { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /BlogTemplate/Models/IFileSystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace BlogTemplate._1.Models 5 | { 6 | /* 7 | * It would have been nice to use a supported mockable interface like 8 | * Microsoft.Extensions.FileProviders.IFileProvider, but that specifically 9 | * does not support writing files (see https://github.com/aspnet/FileSystem/issues/200) 10 | */ 11 | public interface IFileSystem 12 | { 13 | bool FileExists(string path); 14 | string ReadFileText(string path); 15 | void WriteFileText(string path, string text); 16 | void WriteFile(string path, byte[] data); 17 | void DeleteFile(string path); 18 | DateTime GetFileLastWriteTime(string path); 19 | 20 | bool DirectoryExists(string path); 21 | void CreateDirectory(string path); 22 | IEnumerable EnumerateFiles(string directoryPath); 23 | void AppendFile(string path, byte[] data); 24 | void AppendFile(string path, byte[] data, int offset, int count); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /BlogTemplate/Models/PhysicalFileSystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace BlogTemplate._1.Models 6 | { 7 | public class PhysicalFileSystem : IFileSystem 8 | { 9 | public void CreateDirectory(string path) 10 | { 11 | Directory.CreateDirectory(path); 12 | } 13 | 14 | public bool DirectoryExists(string path) 15 | { 16 | return Directory.Exists(path); 17 | } 18 | 19 | public void DeleteFile(string path) 20 | { 21 | File.Delete(path); 22 | } 23 | 24 | public bool FileExists(string path) 25 | { 26 | return File.Exists(path); 27 | } 28 | 29 | public string ReadFileText(string path) 30 | { 31 | return File.ReadAllText(path); 32 | } 33 | 34 | public void WriteFileText(string path, string text) 35 | { 36 | File.WriteAllText(path, text); 37 | } 38 | 39 | public IEnumerable EnumerateFiles(string directoryPath) 40 | { 41 | return Directory.EnumerateFiles(directoryPath); 42 | } 43 | 44 | public DateTime GetFileLastWriteTime(string path) 45 | { 46 | return File.GetLastWriteTime(path); 47 | } 48 | 49 | public void WriteFile(string path, byte[] data) 50 | { 51 | File.WriteAllBytes(path, data); 52 | } 53 | 54 | public void AppendFile(string path, byte[] data) 55 | { 56 | Stream outStream = File.OpenWrite(path); 57 | outStream.Seek(0, SeekOrigin.End); 58 | outStream.Write(data, 0, data.Length); 59 | } 60 | 61 | public void AppendFile(string path, byte[] data, int offset, int count) 62 | { 63 | using (Stream outStream = File.OpenWrite(path)) 64 | { 65 | outStream.Seek(0, SeekOrigin.End); 66 | outStream.Write(data, offset, count); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /BlogTemplate/Models/Post.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | 5 | namespace BlogTemplate._1.Models 6 | { 7 | public class Post 8 | { 9 | public Guid Id { get; set; } 10 | public List Comments { get; set; } = new List(); 11 | [Required(ErrorMessage = "Title required")] 12 | public string Title { get; set; } 13 | [Required(ErrorMessage = "Post text required")] 14 | public string Body { get; set; } 15 | public DateTimeOffset PubDate { get; set; } 16 | public DateTimeOffset LastModified { get; set; } = DateTimeOffset.Now; 17 | public string Slug{ get; set; } 18 | public bool IsPublic { get; set; } 19 | public bool IsDeleted { get; set; } 20 | public string Excerpt { get; set; } 21 | public int ExcerptMaxLength { get; } = 140; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/About.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model AboutModel 3 | @{ 4 | ViewData["Title"] = "About"; 5 | } 6 |

@ViewData["Title"]

7 |

@Model.Message

8 | 9 |

Use this area to provide additional information.

10 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/About.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | 7 | namespace BlogTemplate._1.Pages 8 | { 9 | public class AboutModel : PageModel 10 | { 11 | public string Message { get; set; } 12 | 13 | public void OnGet() 14 | { 15 | Message = "Your blog description page."; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/AccessDenied.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model AccessDeniedModel 3 | @{ 4 | ViewData["Title"] = "Access denied"; 5 | } 6 | 7 |
8 |

ViewData["Title"]

9 |

You do not have access to this resource.

10 |
11 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/AccessDenied.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | 7 | namespace BlogTemplate._1.Pages.Account 8 | { 9 | public class AccessDeniedModel : PageModel 10 | { 11 | public void OnGet() 12 | { 13 | 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/ConfirmEmail.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ConfirmEmailModel 3 | @{ 4 | ViewData["Title"] = "Confirm email"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |
9 |

10 | Thank you for confirming your email. 11 |

12 |
13 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/ConfirmEmail.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | using BlogTemplate._1.Data; 9 | 10 | namespace BlogTemplate._1.Pages.Account 11 | { 12 | public class ConfirmEmailModel : PageModel 13 | { 14 | private readonly UserManager _userManager; 15 | 16 | public ConfirmEmailModel(UserManager userManager) 17 | { 18 | _userManager = userManager; 19 | } 20 | 21 | public async Task OnGetAsync(string userId, string code) 22 | { 23 | if (userId == null || code == null) 24 | { 25 | return RedirectToPage("/Index"); 26 | } 27 | 28 | var user = await _userManager.FindByIdAsync(userId); 29 | if (user == null) 30 | { 31 | return NotFound(); 32 | } 33 | 34 | var result = await _userManager.ConfirmEmailAsync(user, code); 35 | if (!result.Succeeded) 36 | { 37 | return NotFound(); 38 | } 39 | 40 | return Page(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/ExternalLogin.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ExternalLoginModel 3 | @{ 4 | ViewData["Title"] = "Register"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |

Associate your @Model.LoginProvider account.

9 |
10 | 11 |

12 | You've successfully authenticated with @Model.LoginProvider. 13 | Please enter an email address for this site below and click the Register button to finish 14 | logging in. 15 |

16 | 17 |
18 |
19 |
20 |
21 |
22 | 23 | 24 | 25 |
26 | 27 |
28 |
29 |
30 | 31 | @section Scripts { 32 | @await Html.PartialAsync("_ValidationScriptsPartial") 33 | } 34 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/ForgotPassword.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ForgotPasswordModel 3 | @{ 4 | ViewData["Title"] = "Forgot your password?"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |

Enter your email.

9 |
10 |
11 |
12 |
13 |
14 |
15 | 16 | 17 | 18 |
19 | 20 |
21 |
22 |
23 | 24 | @section Scripts { 25 | @await Html.PartialAsync("_ValidationScriptsPartial") 26 | } 27 | 28 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/ForgotPassword.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | using BlogTemplate._1.Data; 9 | using BlogTemplate._1.Services; 10 | 11 | namespace BlogTemplate._1.Pages.Account 12 | { 13 | public class ForgotPasswordModel : PageModel 14 | { 15 | private readonly UserManager _userManager; 16 | private readonly IEmailSender _emailSender; 17 | 18 | public ForgotPasswordModel(UserManager userManager, IEmailSender emailSender) 19 | { 20 | _userManager = userManager; 21 | _emailSender = emailSender; 22 | } 23 | 24 | [BindProperty] 25 | public InputModel Input { get; set; } 26 | 27 | public class InputModel 28 | { 29 | [Required] 30 | [EmailAddress] 31 | public string Email { get; set; } 32 | } 33 | 34 | public async Task OnPostAsync() 35 | { 36 | if (ModelState.IsValid) 37 | { 38 | var user = await _userManager.FindByEmailAsync(Input.Email); 39 | if (user == null || !(await _userManager.IsEmailConfirmedAsync(user))) 40 | { 41 | // Don't reveal that the user does not exist or is not confirmed 42 | return RedirectToPage("./ForgotPasswordConfirmation"); 43 | } 44 | 45 | // For more information on how to enable account confirmation and password reset please 46 | // visit https://go.microsoft.com/fwlink/?LinkID=532713 47 | var code = await _userManager.GeneratePasswordResetTokenAsync(user); 48 | var callbackUrl = Url.ResetPasswordCallbackLink(user.Id, code, Request.Scheme); 49 | await _emailSender.SendResetPasswordAsync(Input.Email, callbackUrl); 50 | return RedirectToPage("./ForgotPasswordConfirmation"); 51 | } 52 | 53 | return Page(); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/ForgotPasswordConfirmation.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ForgotPasswordConfirmation 3 | @{ 4 | ViewData["Title"] = "Forgot password confirmation"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |

9 | Please check your email to reset your password. 10 |

11 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/ForgotPasswordConfirmation.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | 7 | namespace BlogTemplate._1.Pages.Account 8 | { 9 | public class ForgotPasswordConfirmation : PageModel 10 | { 11 | public void OnGet() 12 | { 13 | 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/Lockout.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model LockoutModel 3 | @{ 4 | ViewData["Title"] = "Locked out"; 5 | } 6 | 7 |
8 |

@ViewData["Title"]

9 |

This account has been locked out, please try again later.

10 |
11 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/Lockout.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | 7 | namespace BlogTemplate._1.Pages.Account 8 | { 9 | public class LockoutModel : PageModel 10 | { 11 | public void OnGet() 12 | { 13 | 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/LoginWith2fa.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model LoginWith2faModel 3 | @{ 4 | ViewData["Title"] = "Two-factor authentication"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |
9 |

Your login is protected with an authenticator app. Enter your authenticator code below.

10 |
11 |
12 |
13 | 14 |
15 |
16 | 17 | 18 | 19 |
20 |
21 |
22 | 26 |
27 |
28 |
29 | 30 |
31 |
32 |
33 |
34 |

35 | Don't have access to your authenticator device? You can 36 | log in with a recovery code. 37 |

38 | 39 | @section Scripts { 40 | @await Html.PartialAsync("_ValidationScriptsPartial") 41 | } 42 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/LoginWithRecoveryCode.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model LoginWithRecoveryCodeModel 3 | @{ 4 | ViewData["Title"] = "Recovery code verification"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |
9 |

10 | You have requested to log in with a recovery code. This login will not be remembered until you provide 11 | an authenticator app code at log in or disable 2FA and log in again. 12 |

13 |
14 |
15 |
16 |
17 |
18 | 19 | 20 | 21 |
22 | 23 |
24 |
25 |
26 | 27 | @section Scripts { 28 | @await Html.PartialAsync("_ValidationScriptsPartial") 29 | } 30 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/Manage/ChangePassword.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ChangePasswordModel 3 | @{ 4 | ViewData["Title"] = "Change password"; 5 | } 6 | 7 |

@ViewData["Title"]

8 | @Html.Partial("_StatusMessage", Model.StatusMessage) 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 | @section Scripts { 34 | @await Html.PartialAsync("_ValidationScriptsPartial") 35 | } 36 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/Manage/Disable2fa.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model Disable2faModel 3 | @{ 4 | ViewData["Title"] = "Disable two-factor authentication (2FA)"; 5 | ViewData["ActivePage"] = "TwoFactorAuthentication"; 6 | } 7 | 8 |

@ViewData["Title"]

9 | 10 | 20 | 21 |
22 |
23 | 24 |
25 |
26 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/Manage/Disable2fa.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | using Microsoft.Extensions.Logging; 9 | using BlogTemplate._1.Data; 10 | 11 | namespace BlogTemplate._1.Pages.Account.Manage 12 | { 13 | public class Disable2faModel : PageModel 14 | { 15 | private readonly UserManager _userManager; 16 | private readonly ILogger _logger; 17 | 18 | public Disable2faModel( 19 | UserManager userManager, 20 | ILogger logger) 21 | { 22 | _userManager = userManager; 23 | _logger = logger; 24 | } 25 | 26 | public async Task OnGet() 27 | { 28 | var user = await _userManager.GetUserAsync(User); 29 | if (user == null) 30 | { 31 | return NotFound(); 32 | } 33 | 34 | if (!await _userManager.GetTwoFactorEnabledAsync(user)) 35 | { 36 | return new BadRequestResult(); 37 | } 38 | 39 | return Page(); 40 | } 41 | 42 | public async Task OnPostAsync() 43 | { 44 | var user = await _userManager.GetUserAsync(User); 45 | if (user == null) 46 | { 47 | return NotFound(); 48 | } 49 | 50 | var disable2faResult = await _userManager.SetTwoFactorEnabledAsync(user, false); 51 | if (!disable2faResult.Succeeded) 52 | { 53 | return new BadRequestResult(); 54 | } 55 | 56 | _logger.LogInformation("User with ID '{UserId}' has disabled 2fa.", _userManager.GetUserId(User)); 57 | 58 | return RedirectToPage("./TwoFactorAuthentication"); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/Manage/EnableAuthenticator.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model EnableAuthenticatorModel 3 | @{ 4 | ViewData["Title"] = "Configure authenticator app"; 5 | ViewData["ActivePage"] = "TwoFactorAuthentication"; 6 | } 7 | 8 |

@ViewData["Title"]

9 |
10 |

To use an authenticator app go through the following steps:

11 |
    12 |
  1. 13 |

    14 | Download a two-factor authenticator app like Microsoft Authenticator for 15 | Windows Phone, 16 | Android and 17 | iOS or 18 | Google Authenticator for 19 | Android and 20 | iOS. 21 |

    22 |
  2. 23 |
  3. 24 |

    Scan the QR Code or enter this key @Model.SharedKey into your two factor authenticator app. Spaces and casing do not matter.

    25 |
    To enable QR code generation please read our documentation.
    26 |
    27 |
    28 |
  4. 29 |
  5. 30 |

    31 | Once you have scanned the QR code or input the key above, your two factor authentication app will provide you 32 | with a unique code. Enter the code in the confirmation box below. 33 |

    34 |
    35 |
    36 |
    37 |
    38 | 39 | 40 | 41 |
    42 | 43 |
    44 |
    45 |
    46 |
    47 |
  6. 48 |
49 |
50 | 51 | @section Scripts { 52 | @await Html.PartialAsync("_ValidationScriptsPartial") 53 | } 54 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/Manage/ExternalLogins.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ExternalLoginsModel 3 | @{ 4 | ViewData["Title"] = "Manage your external logins"; 5 | } 6 | 7 | @Html.Partial("_StatusMessage", Model.StatusMessage) 8 | @if (Model.CurrentLogins?.Count > 0) 9 | { 10 |

Registered Logins

11 | 12 | 13 | @foreach (var login in Model.CurrentLogins) 14 | { 15 | 16 | 17 | 33 | 34 | } 35 | 36 |
@login.LoginProvider 18 | @if (Model.ShowRemoveButton) 19 | { 20 |
21 |
22 | 23 | 24 | 25 |
26 |
27 | } 28 | else 29 | { 30 | @:   31 | } 32 |
37 | } 38 | @if (Model.OtherLogins?.Count > 0) 39 | { 40 |

Add another service to log in.

41 |
42 |
43 |
44 |

45 | @foreach (var provider in Model.OtherLogins) 46 | { 47 | 48 | } 49 |

50 |
51 |
52 | } 53 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/Manage/GenerateRecoveryCodes.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model GenerateRecoveryCodesModel 3 | @{ 4 | ViewData["Title"] = "Recovery codes"; 5 | ViewData["ActivePage"] = "TwoFactorAuthentication"; 6 | } 7 | 8 |

@ViewData["Title"]

9 | 18 |
19 |
20 | @for (var row = 0; row < Model.RecoveryCodes.Count(); row += 2) 21 | { 22 | @Model.RecoveryCodes[row] @Model.RecoveryCodes[row + 1]
23 | } 24 |
25 |
26 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/Manage/GenerateRecoveryCodes.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | using Microsoft.Extensions.Logging; 9 | using BlogTemplate._1.Data; 10 | 11 | namespace BlogTemplate._1.Pages.Account.Manage 12 | { 13 | public class GenerateRecoveryCodesModel : PageModel 14 | { 15 | private readonly UserManager _userManager; 16 | private readonly ILogger _logger; 17 | 18 | public GenerateRecoveryCodesModel( 19 | UserManager userManager, 20 | ILogger logger) 21 | { 22 | _userManager = userManager; 23 | _logger = logger; 24 | } 25 | 26 | public string[] RecoveryCodes { get; set; } 27 | 28 | public async Task OnGetAsync() 29 | { 30 | var user = await _userManager.GetUserAsync(User); 31 | if (user == null) 32 | { 33 | return NotFound(); 34 | } 35 | 36 | if (!user.TwoFactorEnabled) 37 | { 38 | return new BadRequestResult(); 39 | } 40 | 41 | var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); 42 | RecoveryCodes = recoveryCodes.ToArray(); 43 | 44 | _logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", user.Id); 45 | 46 | return Page(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/Manage/Index.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model IndexModel 3 | @{ 4 | ViewData["Title"] = "Profile"; 5 | } 6 | 7 |

@ViewData["Title"]

8 | @Html.Partial("_StatusMessage", Model.StatusMessage) 9 |
10 |
11 |
12 |
13 |
14 | 15 | 16 |
17 |
18 | 19 | @if (Model.IsEmailConfirmed) 20 | { 21 |
22 | 23 | 24 |
25 | } 26 | else 27 | { 28 | 29 | 30 | } 31 | 32 |
33 |
34 | 35 | 36 | 37 |
38 | 39 |
40 |
41 |
42 | 43 | @section Scripts { 44 | @await Html.PartialAsync("_ValidationScriptsPartial") 45 | } 46 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/Manage/ManageNavPages.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc.Rendering; 6 | 7 | namespace BlogTemplate._1.Pages.Account.Manage 8 | { 9 | public static class ManageNavPages 10 | { 11 | public static string Index => "Index"; 12 | 13 | public static string ChangePassword => "ChangePassword"; 14 | 15 | public static string ExternalLogins => "ExternalLogins"; 16 | 17 | public static string TwoFactorAuthentication => "TwoFactorAuthentication"; 18 | 19 | public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index); 20 | 21 | public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword); 22 | 23 | public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins); 24 | 25 | public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication); 26 | 27 | public static string PageNavClass(ViewContext viewContext, string page) 28 | { 29 | var activePage = viewContext.ViewData["ActivePage"] as string 30 | ?? System.IO.Path.GetFileNameWithoutExtension(viewContext.ActionDescriptor.DisplayName); 31 | return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/Manage/ResetAuthenticator.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ResetAuthenticatorModel 3 | @{ 4 | ViewData["Title"] = "Reset authenticator key"; 5 | ViewData["ActivePage"] = "TwoFactorAuthentication"; 6 | } 7 | 8 |

@ViewData["Title"]

9 | 19 |
20 |
21 | 22 |
23 |
24 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/Manage/ResetAuthenticator.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | using Microsoft.Extensions.Logging; 9 | using BlogTemplate._1.Data; 10 | 11 | namespace BlogTemplate._1.Pages.Account.Manage 12 | { 13 | public class ResetAuthenticatorModel : PageModel 14 | { 15 | UserManager _userManager; 16 | ILogger _logger; 17 | 18 | public ResetAuthenticatorModel( 19 | UserManager userManager, 20 | ILogger logger) 21 | { 22 | _userManager = userManager; 23 | _logger = logger; 24 | } 25 | public async Task OnGet() 26 | { 27 | var user = await _userManager.GetUserAsync(User); 28 | if (user == null) 29 | { 30 | return NotFound(); 31 | } 32 | 33 | return Page(); 34 | } 35 | 36 | public async Task OnPostAsync() 37 | { 38 | var user = await _userManager.GetUserAsync(User); 39 | if (user == null) 40 | { 41 | return NotFound(); 42 | } 43 | 44 | await _userManager.SetTwoFactorEnabledAsync(user, false); 45 | await _userManager.ResetAuthenticatorKeyAsync(user); 46 | _logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", user.Id); 47 | 48 | return RedirectToPage("./EnableAuthenticator"); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/Manage/SetPassword.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model SetPasswordModel 3 | @{ 4 | ViewData["Title"] = "Set password"; 5 | ViewData["ActivePage"] = "ChangePassword"; 6 | } 7 | 8 |

Set your password

9 | @Html.Partial("_StatusMessage", Model.StatusMessage) 10 |

11 | You do not have a local username/password for this site. Add a local 12 | account so you can log in without an external login. 13 |

14 |
15 |
16 |
17 |
18 |
19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 |
28 | 29 |
30 |
31 |
32 | 33 | @section Scripts { 34 | @await Html.PartialAsync("_ValidationScriptsPartial") 35 | } 36 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/Manage/TwoFactorAuthentication.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model TwoFactorAuthenticationModel 3 | @{ 4 | ViewData["Title"] = "Two-factor authentication (2FA)"; 5 | } 6 | 7 |

@ViewData["Title"]

8 | @if (Model.Is2faEnabled) 9 | { 10 | if (Model.RecoveryCodesLeft == 0) 11 | { 12 |
13 | You have no recovery codes left. 14 |

You must generate a new set of recovery codes before you can log in with a recovery code.

15 |
16 | } 17 | else if (Model.RecoveryCodesLeft == 1) 18 | { 19 |
20 | You have 1 recovery code left. 21 |

You can generate a new set of recovery codes.

22 |
23 | } 24 | else if (Model.RecoveryCodesLeft <= 3) 25 | { 26 |
27 | You have @Model.RecoveryCodesLeft recovery codes left. 28 |

You should generate a new set of recovery codes.

29 |
30 | } 31 | 32 | Disable 2FA 33 | Reset recovery codes 34 | } 35 | 36 |
Authenticator app
37 | @if (!Model.HasAuthenticator) 38 | { 39 | Add authenticator app 40 | } 41 | else 42 | { 43 | Configure authenticator app 44 | Reset authenticator app 45 | } 46 | 47 | @section Scripts { 48 | @await Html.PartialAsync("_ValidationScriptsPartial") 49 | } 50 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/Manage/TwoFactorAuthentication.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | using Microsoft.Extensions.Logging; 9 | using BlogTemplate._1.Data; 10 | 11 | namespace BlogTemplate._1.Pages.Account.Manage 12 | { 13 | public class TwoFactorAuthenticationModel : PageModel 14 | { 15 | private const string AuthenicatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}"; 16 | 17 | private readonly UserManager _userManager; 18 | private readonly SignInManager _signInManager; 19 | private readonly ILogger _logger; 20 | 21 | public TwoFactorAuthenticationModel( 22 | UserManager userManager, 23 | SignInManager signInManager, 24 | ILogger logger) 25 | { 26 | _userManager = userManager; 27 | _signInManager = signInManager; 28 | _logger = logger; 29 | } 30 | 31 | public bool HasAuthenticator { get; set; } 32 | 33 | public int RecoveryCodesLeft { get; set; } 34 | 35 | [BindProperty] 36 | public bool Is2faEnabled { get; set; } 37 | 38 | public async Task OnGet() 39 | { 40 | var user = await _userManager.GetUserAsync(User); 41 | if (user == null) 42 | { 43 | return NotFound(); 44 | } 45 | 46 | HasAuthenticator = await _userManager.GetAuthenticatorKeyAsync(user) != null; 47 | Is2faEnabled = await _userManager.GetTwoFactorEnabledAsync(user); 48 | RecoveryCodesLeft = await _userManager.CountRecoveryCodesAsync(user); 49 | 50 | return Page(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/Manage/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "/Pages/_Layout.cshtml"; 3 | } 4 | 5 |

Manage your account

6 | 7 |
8 |

Change your account settings

9 |
10 |
11 |
12 | @await Html.PartialAsync("_ManageNav") 13 |
14 |
15 | @RenderBody() 16 |
17 |
18 |
19 | 20 | @section Scripts { 21 | @RenderSection("Scripts", required: false) 22 | } 23 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/Manage/_ManageNav.cshtml: -------------------------------------------------------------------------------- 1 | @inject SignInManager SignInManager 2 | @{ 3 | var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any(); 4 | } 5 | 6 | 15 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/Manage/_StatusMessage.cshtml: -------------------------------------------------------------------------------- 1 | @model string 2 | 3 | @if (!String.IsNullOrEmpty(Model)) 4 | { 5 | var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success"; 6 | 10 | } 11 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/Manage/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using BlogTemplate._1.Pages.Account.Manage 2 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/Register.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model RegisterModel 3 | @{ 4 | ViewData["Title"] = "Register"; 5 | } 6 | 7 |

@ViewData["Title"]

8 | 9 |
10 |
11 |
12 |

Create a new account.

13 |
14 |
15 |
16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 |
25 |
26 | 27 | 28 | 29 |
30 | 31 |
32 |
33 |
34 | @section Scripts { 35 | @await Html.PartialAsync("_ValidationScriptsPartial") 36 | } 37 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/ResetPassword.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ResetPasswordModel 3 | @{ 4 | ViewData["Title"] = "Reset password"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |

Reset your password.

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 | @section Scripts { 36 | @await Html.PartialAsync("_ValidationScriptsPartial") 37 | } 38 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/ResetPassword.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Identity; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.AspNetCore.Mvc.RazorPages; 9 | using BlogTemplate._1.Data; 10 | 11 | namespace BlogTemplate._1.Pages.Account 12 | { 13 | public class ResetPasswordModel : PageModel 14 | { 15 | private readonly UserManager _userManager; 16 | 17 | public ResetPasswordModel(UserManager userManager) 18 | { 19 | _userManager = userManager; 20 | } 21 | 22 | [BindProperty] 23 | public InputModel Input { get; set; } 24 | 25 | public class InputModel 26 | { 27 | [Required] 28 | [EmailAddress] 29 | public string Email { get; set; } 30 | 31 | [Required] 32 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 33 | [DataType(DataType.Password)] 34 | public string Password { get; set; } 35 | 36 | [DataType(DataType.Password)] 37 | [Display(Name = "Confirm password")] 38 | [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] 39 | public string ConfirmPassword { get; set; } 40 | 41 | public string Code { get; set; } 42 | } 43 | 44 | public IActionResult OnGet(string code = null) 45 | { 46 | if (code == null) 47 | { 48 | return new BadRequestResult(); 49 | } 50 | else 51 | { 52 | Input = new InputModel 53 | { 54 | Code = code 55 | }; 56 | return Page(); 57 | } 58 | } 59 | 60 | public async Task OnPostAsync() 61 | { 62 | if (!ModelState.IsValid) 63 | { 64 | return Page(); 65 | } 66 | 67 | var user = await _userManager.FindByEmailAsync(Input.Email); 68 | if (user == null) 69 | { 70 | // Don't reveal that the user does not exist 71 | return RedirectToPage("./ResetPasswordConfirmation"); 72 | } 73 | 74 | var result = await _userManager.ResetPasswordAsync(user, Input.Code, Input.Password); 75 | if (result.Succeeded) 76 | { 77 | return RedirectToPage("./ResetPasswordConfirmation"); 78 | } 79 | 80 | foreach (var error in result.Errors) 81 | { 82 | ModelState.AddModelError(string.Empty, error.Description); 83 | } 84 | return Page(); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/ResetPasswordConfirmation.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ResetPasswordConfirmationModel 3 | @{ 4 | ViewData["Title"] = "Reset password confirmation"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |

9 | Your password has been reset. Please click here to log in. 10 |

11 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/ResetPasswordConfirmation.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | 7 | namespace BlogTemplate._1.Pages.Account 8 | { 9 | public class ResetPasswordConfirmationModel : PageModel 10 | { 11 | public void OnGet() 12 | { 13 | 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/SignedOut.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model SignedOutModel 3 | @{ 4 | ViewData["Title"] = "Signed out"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |

9 | You have successfully signed out. 10 |

11 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/SignedOut.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Mvc.RazorPages; 7 | 8 | public class SignedOutModel : PageModel 9 | { 10 | public IActionResult OnGet() 11 | { 12 | if (User.Identity.IsAuthenticated) 13 | { 14 | // Redirect to home page if the user is authenticated. 15 | return RedirectToPage("/Index"); 16 | } 17 | 18 | return Page(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Account/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using BlogTemplate._1.Pages.Account 2 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/AlreadyRegistered.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @{ 3 | ViewData["Title"] = "Already Registered"; 4 | } 5 | 6 |

You are already registered.

7 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Drafts.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model DraftsModel 3 | @{ 4 | ViewData["Title"] = "Drafts"; 5 | 6 |

@ViewData["Title"]

7 | 8 | if (!Model.DraftSummaries.Any()) 9 | { 10 |

View and edit your saved drafts here.

11 | } 12 | else 13 | { 14 | foreach (var post in Model.DraftSummaries) 15 | { 16 |
17 |
18 |

@post.Title

19 | @if (post.PublishTime != default(DateTimeOffset)) 20 | { 21 |
Last Published @post.PublishTime.ToString("MMM dd, yyyy")
22 | } 23 |

@post.Excerpt

24 |

@post.CommentCount

25 |
26 |
27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Drafts.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using BlogTemplate._1.Models; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | 7 | namespace BlogTemplate._1.Pages 8 | { 9 | public class DraftsModel : PageModel 10 | { 11 | const string StorageFolder = "BlogFiles"; 12 | 13 | private readonly BlogDataStore _dataStore; 14 | 15 | public IEnumerable DraftSummaries { get; private set; } 16 | 17 | public DraftsModel(BlogDataStore dataStore) 18 | { 19 | _dataStore = dataStore; 20 | } 21 | 22 | public void OnGet() 23 | { 24 | Func postFilter = p => !p.IsPublic; 25 | IEnumerable postModels = _dataStore.GetAllDrafts().Where(postFilter); 26 | 27 | DraftSummaries = postModels.Select(p => new DraftSummaryModel 28 | { 29 | Id = p.Id, 30 | Slug = p.Slug, 31 | Title = p.Title, 32 | Excerpt = p.Excerpt, 33 | PublishTime = p.PubDate, 34 | CommentCount = p.Comments.Count, 35 | }); 36 | } 37 | } 38 | 39 | public class DraftSummaryModel 40 | { 41 | public Guid Id { get; set; } 42 | public string Slug { get; set; } 43 | public string Title { get; set; } 44 | public DateTimeOffset PublishTime { get; set; } 45 | public string Excerpt { get; set; } 46 | public int CommentCount { get; set; } 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Edit.cshtml: -------------------------------------------------------------------------------- 1 | @page "{id}" 2 | @model EditModel 3 | @addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers" 4 | 5 | @using BlogTemplate._1 6 | @using BlogTemplate._1.Models 7 | @using Microsoft.AspNetCore.Mvc.RazorPages 8 | 9 | @{ 10 | ViewData["Title"] = "Edit Post"; 11 | } 12 | 13 |
14 |

Edit Post

15 |
16 |
17 | @if (TempData["notice"] != null) 18 | { 19 |

@TempData["notice"]

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 | 49 |
50 |
51 |
52 | 53 | @section Scripts { 54 | 55 | 56 | } 57 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ErrorModel 3 | @{ 4 | ViewData["Title"] = "Error"; 5 | } 6 | 7 |

Error.

8 |

An error occurred while processing your request.

9 | 10 | @if (Model.ShowRequestId) 11 | { 12 |

13 | Request ID: @Model.RequestId 14 |

15 | } 16 | 17 |

Development Mode

18 |

19 | Swapping to Development environment will display more detailed information about the error that occurred. 20 |

21 |

22 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. 23 |

24 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc.RazorPages; 7 | 8 | namespace BlogTemplate._1.Pages 9 | { 10 | public class ErrorModel : PageModel 11 | { 12 | public string RequestId { get; set; } 13 | 14 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 15 | 16 | public void OnGet() 17 | { 18 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Index.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model IndexModel 3 | @{ 4 | ViewData["Title"] = "Home Page"; 5 | 6 | if (!Model.PostSummaries.Any()) 7 | { 8 |
9 |

Welcome!

10 |

Get started and write your first blog post here.

11 |

Happy blogging!

12 |
13 | } 14 | 15 | else 16 | { 17 | foreach (var post in Model.PostSummaries) 18 | { 19 | 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using BlogTemplate._1.Models; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | 7 | namespace BlogTemplate._1.Pages 8 | { 9 | public class IndexModel : PageModel 10 | { 11 | const string StorageFolder = "BlogFiles"; 12 | 13 | private readonly BlogDataStore _dataStore; 14 | 15 | public IEnumerable PostSummaries { get; private set; } 16 | 17 | public IndexModel(BlogDataStore dataStore) 18 | { 19 | _dataStore = dataStore; 20 | } 21 | 22 | public void OnGet() 23 | { 24 | Func postFilter = p => p.IsPublic; 25 | Func deletedPostFilter = p => !p.IsDeleted; 26 | IEnumerable postModels = _dataStore.GetAllPosts().Where(postFilter).Where(deletedPostFilter); 27 | 28 | PostSummaries = postModels.Select(p => new PostSummaryModel { 29 | Id = p.Id, 30 | Slug = p.Slug, 31 | Title = p.Title, 32 | Excerpt = p.Excerpt, 33 | PublishTime = p.PubDate, 34 | CommentCount = p.Comments.Where(c => c.IsPublic).Count(), 35 | }); 36 | } 37 | 38 | public class PostSummaryModel 39 | { 40 | public Guid Id { get; set; } 41 | public string Slug { get; set; } 42 | public string Title { get; set; } 43 | public DateTimeOffset PublishTime { get; set; } 44 | public string Excerpt { get; set; } 45 | public int CommentCount { get; set; } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/ManageComment.cshtml: -------------------------------------------------------------------------------- 1 | @page "{id}/{handler?}" 2 | @model ManageCommentModel 3 | @{ 4 | } 5 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/ManageComment.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using BlogTemplate._1.Models; 3 | using Microsoft.AspNetCore.Authorization; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | 7 | namespace BlogTemplate._1.Pages 8 | { 9 | [Authorize] 10 | public class ManageCommentModel : PageModel 11 | { 12 | private readonly BlogDataStore _dataStore; 13 | 14 | public ManageCommentModel(BlogDataStore dataStore) 15 | { 16 | _dataStore = dataStore; 17 | } 18 | 19 | [ValidateAntiForgeryToken] 20 | public IActionResult OnPostDeleteComment(Guid commentId, string id) 21 | { 22 | Post post = _dataStore.GetPost(id); 23 | 24 | Comment foundComment = _dataStore.FindComment(commentId, post); 25 | foundComment.IsPublic = false; 26 | 27 | _dataStore.SavePost(post); 28 | return Redirect($"/Post/{id}/{post.Slug}"); 29 | } 30 | 31 | [ValidateAntiForgeryToken] 32 | public IActionResult OnPostUndeleteComment(Guid commentId, string id) 33 | { 34 | Post post = _dataStore.GetPost(id); 35 | 36 | Comment foundComment = _dataStore.FindComment(commentId, post); 37 | foundComment.IsPublic = true; 38 | 39 | _dataStore.SavePost(post); 40 | return Redirect($"/Post/{id}/{post.Slug}"); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/New.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model NewModel 3 | @addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers" 4 | 5 | @using BlogTemplate._1 6 | @using BlogTemplate._1.Models 7 | @using Microsoft.AspNetCore.Mvc.RazorPages 8 | 9 | @{ 10 | ViewData["Title"] = "New Blog Post"; 11 | } 12 | 13 |

Write a New Post

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 | @section Scripts { 45 | 46 | } 47 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/New.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using BlogTemplate._1.Models; 6 | using BlogTemplate._1.Services; 7 | using Microsoft.AspNetCore.Authorization; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.AspNetCore.Mvc.RazorPages; 10 | using Microsoft.AspNetCore.Http; 11 | 12 | namespace BlogTemplate._1.Pages 13 | { 14 | [Authorize] 15 | public class NewModel : PageModel 16 | { 17 | private readonly BlogDataStore _dataStore; 18 | private readonly SlugGenerator _slugGenerator; 19 | private readonly ExcerptGenerator _excerptGenerator; 20 | 21 | public NewModel(BlogDataStore dataStore, SlugGenerator slugGenerator, ExcerptGenerator excerptGenerator) 22 | { 23 | _dataStore = dataStore; 24 | _slugGenerator = slugGenerator; 25 | _excerptGenerator = excerptGenerator; 26 | } 27 | 28 | [BindProperty] 29 | public NewPostViewModel NewPost { get; set; } 30 | 31 | public void OnGet() 32 | { 33 | } 34 | 35 | [ValidateAntiForgeryToken] 36 | public IActionResult OnPostPublish() 37 | { 38 | if (ModelState.IsValid) 39 | { 40 | SavePost(NewPost, true); 41 | return Redirect("/Index"); 42 | } 43 | 44 | return Page(); 45 | } 46 | 47 | [ValidateAntiForgeryToken] 48 | public IActionResult OnPostSaveDraft() 49 | { 50 | if(ModelState.IsValid) 51 | { 52 | SavePost(NewPost, false); 53 | return Redirect("/Drafts"); 54 | } 55 | 56 | return Page(); 57 | } 58 | 59 | private void SavePost(NewPostViewModel newPost, bool publishPost) 60 | { 61 | if (string.IsNullOrEmpty(newPost.Excerpt)) 62 | { 63 | newPost.Excerpt = _excerptGenerator.CreateExcerpt(newPost.Body); 64 | } 65 | 66 | Post post = new Post { 67 | Title = newPost.Title, 68 | Body = newPost.Body, 69 | Excerpt = newPost.Excerpt, 70 | Slug = _slugGenerator.CreateSlug(newPost.Title), 71 | LastModified = DateTimeOffset.Now, 72 | IsDeleted = false, 73 | }; 74 | 75 | if (publishPost) 76 | { 77 | post.PubDate = DateTimeOffset.Now; 78 | post.IsPublic = true; 79 | } 80 | 81 | if (Request != null) 82 | { 83 | _dataStore.SaveFiles(Request.Form.Files.ToList()); 84 | } 85 | 86 | _dataStore.SavePost(post); 87 | } 88 | 89 | public class NewPostViewModel 90 | { 91 | [Required] 92 | public string Title { get; set; } 93 | [Required] 94 | public string Body { get; set; } 95 | public string Excerpt { get; set; } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Trash.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model TrashModel 3 | @{ 4 | ViewData["Title"] = "Trash Bin"; 5 | 6 | if (!Model.PostSummaries.Any()) 7 | { 8 |
9 |

There are no posts in your trash bin.

10 |
11 | }else 12 | { 13 | foreach (var post in Model.PostSummaries) 14 | { 15 | 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/Trash.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using BlogTemplate._1.Models; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | 9 | namespace BlogTemplate._1.Pages 10 | { 11 | public class TrashModel : PageModel 12 | { 13 | const string StorageFolder = "BlogFiles"; 14 | 15 | private readonly BlogDataStore _dataStore; 16 | 17 | public IEnumerable PostSummaries { get; private set; } 18 | 19 | public TrashModel(BlogDataStore dataStore) 20 | { 21 | _dataStore = dataStore; 22 | } 23 | 24 | public void OnGet() 25 | { 26 | Func deletedPostFilter = p => p.IsDeleted; 27 | IEnumerable postModels = _dataStore.GetAllPosts().Where(deletedPostFilter); 28 | 29 | PostSummaries = postModels.Select(p => new PostSummaryModel 30 | { 31 | Id = p.Id, 32 | Slug = p.Slug, 33 | Title = p.Title, 34 | Excerpt = p.Excerpt, 35 | PublishTime = p.PubDate, 36 | CommentCount = p.Comments.Where(c => c.IsPublic).Count(), 37 | }); 38 | } 39 | 40 | public class PostSummaryModel 41 | { 42 | public Guid Id { get; set; } 43 | public string Slug { get; set; } 44 | public string Title { get; set; } 45 | public DateTimeOffset PublishTime { get; set; } 46 | public string Excerpt { get; set; } 47 | public int CommentCount { get; set; } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - BlogTemplate.1 7 | 8 | 9 | 10 | 11 | 12 |
13 | 30 |
31 |
32 | @RenderBody() 33 |
34 | 35 |
36 |

© 2017 - BlogTemplate.1

37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 50 | 56 | 57 | 58 | 59 | 60 | @RenderSection("Scripts", required: false) 61 | 62 | 63 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/_LoginPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @using BlogTemplate._1.Data 3 | @inject SignInManager SignInManager 4 | @inject UserManager UserManager 5 | 6 | @if (SignInManager.IsSignedIn(User)) 7 | { 8 | 9 | 10 | 11 | 12 | 17 | } 18 | else 19 | { 20 | 23 | } 24 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @using BlogTemplate._1 3 | @using BlogTemplate._1.Data 4 | @namespace BlogTemplate._1.Pages 5 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 6 | -------------------------------------------------------------------------------- /BlogTemplate/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /BlogTemplate/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace BlogTemplate._1 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | BuildWebHost(args).Run(); 11 | } 12 | 13 | public static IWebHost BuildWebHost(string[] args) => 14 | WebHost.CreateDefaultBuilder(args) 15 | .UseStartup() 16 | .Build(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /BlogTemplate/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:59937/", 7 | "sslPort": 44356 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "launchUrl": "https://localhost:44356/", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "BlogTemplate.1": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | }, 25 | "applicationUrl": "http://localhost:59938/" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /BlogTemplate/Services/EmailSender.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace BlogTemplate._1.Services 4 | { 5 | // This class is used by the application to send email for account confirmation and password reset. 6 | // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 7 | public class EmailSender : IEmailSender 8 | { 9 | public Task SendEmailAsync(string email, string subject, string message) 10 | { 11 | return Task.CompletedTask; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /BlogTemplate/Services/ExcerptGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace BlogTemplate._1.Services 7 | { 8 | public class ExcerptGenerator 9 | { 10 | private readonly int _maxLength; 11 | 12 | public ExcerptGenerator(int maxLength) 13 | { 14 | _maxLength = maxLength; 15 | } 16 | 17 | public string CreateExcerpt(string body) 18 | { 19 | string excerpt; 20 | if (body.Length > _maxLength) 21 | { 22 | excerpt = body.Substring(0, _maxLength) + "..."; 23 | } 24 | else 25 | { 26 | excerpt = body; 27 | } 28 | return excerpt; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /BlogTemplate/Services/IEmailSender.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace BlogTemplate._1.Services 4 | { 5 | public interface IEmailSender 6 | { 7 | Task SendEmailAsync(string email, string subject, string message); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /BlogTemplate/Services/MarkdownRenderer.cs: -------------------------------------------------------------------------------- 1 | using Markdig; 2 | using Microsoft.AspNetCore.Html; 3 | 4 | namespace BlogTemplate._1.Services 5 | { 6 | public class MarkdownRenderer 7 | { 8 | private static MarkdownPipeline pipeline = new MarkdownPipelineBuilder() 9 | .UseDiagrams() 10 | .UseAdvancedExtensions() 11 | .UseYamlFrontMatter() 12 | .DisableHtml() 13 | .Build(); 14 | 15 | public HtmlString RenderMarkdown(string bodyText) 16 | { 17 | var html = Markdown.ToHtml(bodyText, pipeline); 18 | return new HtmlString(html); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /BlogTemplate/Services/SlugGenerator.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | using BlogTemplate._1.Models; 3 | 4 | namespace BlogTemplate._1.Services 5 | { 6 | public class SlugGenerator 7 | { 8 | private BlogDataStore _dataStore; 9 | 10 | public SlugGenerator(BlogDataStore dataStore) 11 | { 12 | _dataStore = dataStore; 13 | } 14 | 15 | public string CreateSlug(string title) 16 | { 17 | string tempTitle = title; 18 | tempTitle = tempTitle.Replace(" ", "-"); 19 | Regex allowList = new Regex("([^A-Za-z0-9-])"); 20 | string slug = allowList.Replace(tempTitle, ""); 21 | return slug; 22 | } 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /BlogTemplate/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /BlogTemplate/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-BlogTemplate-53bc9b9d-9d6a-45d4-8429-2a2761773502;Trusted_Connection=True;MultipleActiveResultSets=true" 4 | }, 5 | "Logging": { 6 | "IncludeScopes": false, 7 | "LogLevel": { 8 | "Default": "Warning" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /BlogTemplate/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asp.net", 3 | "private": true, 4 | "dependencies": { 5 | "bootstrap": "3.3.7", 6 | "jquery": "2.2.0", 7 | "jquery-validation": "1.14.0", 8 | "jquery-validation-unobtrusive": "3.2.6" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /BlogTemplate/bundleconfig.json: -------------------------------------------------------------------------------- 1 | // Configure bundling and minification for the project. 2 | // More info at https://go.microsoft.com/fwlink/?LinkId=808241 3 | [ 4 | { 5 | "outputFileName": "wwwroot/css/site.min.css", 6 | // An array of relative input file paths. Globbing patterns supported 7 | "inputFiles": [ 8 | "wwwroot/css/site.css" 9 | ] 10 | }, 11 | { 12 | "outputFileName": "wwwroot/js/site.min.js", 13 | "inputFiles": [ 14 | "wwwroot/js/site.js" 15 | ], 16 | // Optionally specify minification options 17 | "minify": { 18 | "enabled": true, 19 | "renameLocals": true 20 | }, 21 | // Optionally generate .map file 22 | "sourceMap": false 23 | } 24 | ] 25 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | footer { 2 | margin-top: 50px; 3 | } 4 | 5 | .navbar-header h2 { 6 | padding-left: 15px; 7 | } 8 | 9 | .cancel-link { 10 | padding-top: 10px; 11 | } 12 | 13 | .add-comment-form { 14 | margin-top: 50px; 15 | } 16 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/css/site.min.css: -------------------------------------------------------------------------------- 1 | footer{margin-top:50px}.navbar-header h2{padding-left:15px}.cancel-link{padding-top:10px}.add-comment-form{margin-top:50px} -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VenusInterns/BlogTemplate/f9ebe414a89891fbef8bfd872e6096da6e9f8cdd/BlogTemplate/wwwroot/favicon.ico -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VenusInterns/BlogTemplate/f9ebe414a89891fbef8bfd872e6096da6e9f8cdd/BlogTemplate/wwwroot/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VenusInterns/BlogTemplate/f9ebe414a89891fbef8bfd872e6096da6e9f8cdd/BlogTemplate/wwwroot/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VenusInterns/BlogTemplate/f9ebe414a89891fbef8bfd872e6096da6e9f8cdd/BlogTemplate/wwwroot/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VenusInterns/BlogTemplate/f9ebe414a89891fbef8bfd872e6096da6e9f8cdd/BlogTemplate/wwwroot/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/js/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') -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Write your Javascript code. 2 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/js/site.min.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VenusInterns/BlogTemplate/f9ebe414a89891fbef8bfd872e6096da6e9f8cdd/BlogTemplate/wwwroot/js/site.min.js -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/js/slugUpdateWarning.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | var editpostform = document.querySelector("#postsave"); 4 | 5 | editpostform.addEventListener("click", function (e) { 6 | 7 | var titleElm = this.form.querySelector("#EditedPost_Title"); 8 | var oldtitle = titleElm.getAttribute("data-oldtitle"); 9 | var newtitle = titleElm.value; 10 | var hasSlug = this.form.getAttribute("data-has-slug") == "True"; 11 | 12 | if (oldtitle !== newtitle && hasSlug) { 13 | if (confirm("Changing the post title will update the post slug and break external links. \nDo you wish to update the slug?")) { 14 | this.form.querySelector("#updateSlug").value = true; 15 | } 16 | else { 17 | this.form.querySelector("#updateSlug").value = false; 18 | } 19 | } 20 | 21 | }, false); 22 | })(); 23 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/js/updateBody.js: -------------------------------------------------------------------------------- 1 | $("#file-input").change(insertFiles, false); 2 | 3 | function insertFiles() { 4 | var selectedFiles = document.getElementById("file-input"); 5 | for (var i = 0; i < selectedFiles.files.length; i++) { 6 | var postBody = $("#post-body"); 7 | var name = selectedFiles.files[i].name; 8 | postBody.val(postBody.val() + "\n![" + name + "](/Uploads/" + name + ")"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bootstrap", 3 | "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.", 4 | "keywords": [ 5 | "css", 6 | "js", 7 | "less", 8 | "mobile-first", 9 | "responsive", 10 | "front-end", 11 | "framework", 12 | "web" 13 | ], 14 | "homepage": "http://getbootstrap.com", 15 | "license": "MIT", 16 | "moduleType": "globals", 17 | "main": [ 18 | "less/bootstrap.less", 19 | "dist/js/bootstrap.js" 20 | ], 21 | "ignore": [ 22 | "/.*", 23 | "_config.yml", 24 | "CNAME", 25 | "composer.json", 26 | "CONTRIBUTING.md", 27 | "docs", 28 | "js/tests", 29 | "test-infra" 30 | ], 31 | "dependencies": { 32 | "jquery": "1.9.1 - 3" 33 | }, 34 | "version": "3.3.7", 35 | "_release": "3.3.7", 36 | "_resolution": { 37 | "type": "version", 38 | "tag": "v3.3.7", 39 | "commit": "0b9c4a4007c44201dce9a6cc1a38407005c26c86" 40 | }, 41 | "_source": "https://github.com/twbs/bootstrap.git", 42 | "_target": "3.3.7", 43 | "_originalSource": "bootstrap" 44 | } -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Bootstrap uses [GitHub's Releases feature](https://github.com/blog/1547-release-your-software) for its changelogs. 2 | 3 | See [the Releases section of our GitHub project](https://github.com/twbs/bootstrap/releases) for changelogs for each release version of Bootstrap. 4 | 5 | Release announcement posts on [the official Bootstrap blog](http://blog.getbootstrap.com) contain summaries of the most noteworthy changes made in each release. 6 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | group :development, :test do 4 | gem 'jekyll', '~> 3.1.2' 5 | gem 'jekyll-sitemap', '~> 0.11.0' 6 | end 7 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | addressable (2.4.0) 5 | colorator (0.1) 6 | ffi (1.9.14-x64-mingw32) 7 | jekyll (3.1.6) 8 | colorator (~> 0.1) 9 | jekyll-sass-converter (~> 1.0) 10 | jekyll-watch (~> 1.1) 11 | kramdown (~> 1.3) 12 | liquid (~> 3.0) 13 | mercenary (~> 0.3.3) 14 | rouge (~> 1.7) 15 | safe_yaml (~> 1.0) 16 | jekyll-sass-converter (1.4.0) 17 | sass (~> 3.4) 18 | jekyll-sitemap (0.11.0) 19 | addressable (~> 2.4.0) 20 | jekyll-watch (1.4.0) 21 | listen (~> 3.0, < 3.1) 22 | kramdown (1.11.1) 23 | liquid (3.0.6) 24 | listen (3.0.8) 25 | rb-fsevent (~> 0.9, >= 0.9.4) 26 | rb-inotify (~> 0.9, >= 0.9.7) 27 | mercenary (0.3.6) 28 | rb-fsevent (0.9.7) 29 | rb-inotify (0.9.7) 30 | ffi (>= 0.5.0) 31 | rouge (1.11.1) 32 | safe_yaml (1.0.4) 33 | sass (3.4.22) 34 | 35 | PLATFORMS 36 | x64-mingw32 37 | 38 | DEPENDENCIES 39 | jekyll (~> 3.1.2) 40 | jekyll-sitemap (~> 0.11.0) 41 | 42 | BUNDLED WITH 43 | 1.12.5 44 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Before opening an issue: 2 | 3 | - [Search for duplicate or closed issues](https://github.com/twbs/bootstrap/issues?utf8=%E2%9C%93&q=is%3Aissue) 4 | - [Validate](http://validator.w3.org/nu/) and [lint](https://github.com/twbs/bootlint#in-the-browser) any HTML to avoid common problems 5 | - Prepare a [reduced test case](https://css-tricks.com/reduced-test-cases/) for any bugs 6 | - Read the [contributing guidelines](https://github.com/twbs/bootstrap/blob/master/CONTRIBUTING.md) 7 | 8 | When asking general "how to" questions: 9 | 10 | - Please do not open an issue here 11 | - Instead, ask for help on [StackOverflow, IRC, or Slack](https://github.com/twbs/bootstrap/blob/master/README.md#community) 12 | 13 | When reporting a bug, include: 14 | 15 | - Operating system and version (Windows, Mac OS X, Android, iOS, Win10 Mobile) 16 | - Browser and version (Chrome, Firefox, Safari, IE, MS Edge, Opera 15+, Android Browser) 17 | - Reduced test cases and potential fixes using [JS Bin](https://jsbin.com) 18 | 19 | When suggesting a feature, include: 20 | 21 | - As much detail as possible for what we should add and why it's important to Bootstrap 22 | - Relevant links to prior art, screenshots, or live demos whenever possible 23 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2016 Twitter, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bootstrap", 3 | "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.", 4 | "keywords": [ 5 | "css", 6 | "js", 7 | "less", 8 | "mobile-first", 9 | "responsive", 10 | "front-end", 11 | "framework", 12 | "web" 13 | ], 14 | "homepage": "http://getbootstrap.com", 15 | "license": "MIT", 16 | "moduleType": "globals", 17 | "main": [ 18 | "less/bootstrap.less", 19 | "dist/js/bootstrap.js" 20 | ], 21 | "ignore": [ 22 | "/.*", 23 | "_config.yml", 24 | "CNAME", 25 | "composer.json", 26 | "CONTRIBUTING.md", 27 | "docs", 28 | "js/tests", 29 | "test-infra" 30 | ], 31 | "dependencies": { 32 | "jquery": "1.9.1 - 3" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VenusInterns/BlogTemplate/f9ebe414a89891fbef8bfd872e6096da6e9f8cdd/BlogTemplate/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VenusInterns/BlogTemplate/f9ebe414a89891fbef8bfd872e6096da6e9f8cdd/BlogTemplate/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VenusInterns/BlogTemplate/f9ebe414a89891fbef8bfd872e6096da6e9f8cdd/BlogTemplate/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VenusInterns/BlogTemplate/f9ebe414a89891fbef8bfd872e6096da6e9f8cdd/BlogTemplate/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/dist/js/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') -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VenusInterns/BlogTemplate/f9ebe414a89891fbef8bfd872e6096da6e9f8cdd/BlogTemplate/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VenusInterns/BlogTemplate/f9ebe414a89891fbef8bfd872e6096da6e9f8cdd/BlogTemplate/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VenusInterns/BlogTemplate/f9ebe414a89891fbef8bfd872e6096da6e9f8cdd/BlogTemplate/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VenusInterns/BlogTemplate/f9ebe414a89891fbef8bfd872e6096da6e9f8cdd/BlogTemplate/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/grunt/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends" : "../js/.jshintrc", 3 | "asi" : false, 4 | "browser" : false, 5 | "es3" : false, 6 | "node" : true 7 | } 8 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/grunt/bs-commonjs-generator.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Grunt task for the CommonJS module generation 3 | * http://getbootstrap.com 4 | * Copyright 2014-2015 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var fs = require('fs'); 11 | var path = require('path'); 12 | 13 | var COMMONJS_BANNER = '// This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment.\n'; 14 | 15 | module.exports = function generateCommonJSModule(grunt, srcFiles, destFilepath) { 16 | var destDir = path.dirname(destFilepath); 17 | 18 | function srcPathToDestRequire(srcFilepath) { 19 | var requirePath = path.relative(destDir, srcFilepath).replace(/\\/g, '/'); 20 | return 'require(\'' + requirePath + '\')'; 21 | } 22 | 23 | var moduleOutputJs = COMMONJS_BANNER + srcFiles.map(srcPathToDestRequire).join('\n'); 24 | try { 25 | fs.writeFileSync(destFilepath, moduleOutputJs); 26 | } catch (err) { 27 | grunt.fail.warn(err); 28 | } 29 | grunt.log.writeln('File ' + destFilepath.cyan + ' created.'); 30 | }; 31 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/grunt/bs-glyphicons-data-generator.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Grunt task for Glyphicons data generation 3 | * http://getbootstrap.com 4 | * Copyright 2014-2015 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var fs = require('fs'); 11 | 12 | module.exports = function generateGlyphiconsData(grunt) { 13 | // Pass encoding, utf8, so `readFileSync` will return a string instead of a 14 | // buffer 15 | var glyphiconsFile = fs.readFileSync('less/glyphicons.less', 'utf8'); 16 | var glyphiconsLines = glyphiconsFile.split('\n'); 17 | 18 | // Use any line that starts with ".glyphicon-" and capture the class name 19 | var iconClassName = /^\.(glyphicon-[a-zA-Z0-9-]+)/; 20 | var glyphiconsData = '# This file is generated via Grunt task. **Do not edit directly.**\n' + 21 | '# See the \'build-glyphicons-data\' task in Gruntfile.js.\n\n'; 22 | var glyphiconsYml = 'docs/_data/glyphicons.yml'; 23 | for (var i = 0, len = glyphiconsLines.length; i < len; i++) { 24 | var match = glyphiconsLines[i].match(iconClassName); 25 | 26 | if (match !== null) { 27 | glyphiconsData += '- ' + match[1] + '\n'; 28 | } 29 | } 30 | 31 | // Create the `_data` directory if it doesn't already exist 32 | if (!fs.existsSync('docs/_data')) { 33 | fs.mkdirSync('docs/_data'); 34 | } 35 | 36 | try { 37 | fs.writeFileSync(glyphiconsYml, glyphiconsData); 38 | } catch (err) { 39 | grunt.fail.warn(err); 40 | } 41 | grunt.log.writeln('File ' + glyphiconsYml.cyan + ' created.'); 42 | }; 43 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/grunt/bs-raw-files-generator.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Grunt task for generating raw-files.min.js for the Customizer 3 | * http://getbootstrap.com 4 | * Copyright 2014-2015 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var fs = require('fs'); 11 | var btoa = require('btoa'); 12 | var glob = require('glob'); 13 | 14 | function getFiles(type) { 15 | var files = {}; 16 | var recursive = type === 'less'; 17 | var globExpr = recursive ? '/**/*' : '/*'; 18 | glob.sync(type + globExpr) 19 | .filter(function (path) { 20 | return type === 'fonts' ? true : new RegExp('\\.' + type + '$').test(path); 21 | }) 22 | .forEach(function (fullPath) { 23 | var relativePath = fullPath.replace(/^[^/]+\//, ''); 24 | files[relativePath] = type === 'fonts' ? btoa(fs.readFileSync(fullPath)) : fs.readFileSync(fullPath, 'utf8'); 25 | }); 26 | return 'var __' + type + ' = ' + JSON.stringify(files) + '\n'; 27 | } 28 | 29 | module.exports = function generateRawFilesJs(grunt, banner) { 30 | if (!banner) { 31 | banner = ''; 32 | } 33 | var dirs = ['js', 'less', 'fonts']; 34 | var files = banner + dirs.map(getFiles).reduce(function (combined, file) { 35 | return combined + file; 36 | }, ''); 37 | var rawFilesJs = 'docs/assets/js/raw-files.min.js'; 38 | try { 39 | fs.writeFileSync(rawFilesJs, files); 40 | } catch (err) { 41 | grunt.fail.warn(err); 42 | } 43 | grunt.log.writeln('File ' + rawFilesJs.cyan + ' created.'); 44 | }; 45 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/grunt/configBridge.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": { 3 | "customizerJs": [ 4 | "../assets/js/vendor/autoprefixer.js", 5 | "../assets/js/vendor/less.min.js", 6 | "../assets/js/vendor/jszip.min.js", 7 | "../assets/js/vendor/uglify.min.js", 8 | "../assets/js/vendor/Blob.js", 9 | "../assets/js/vendor/FileSaver.js", 10 | "../assets/js/raw-files.min.js", 11 | "../assets/js/src/customizer.js" 12 | ], 13 | "docsJs": [ 14 | "../assets/js/vendor/holder.min.js", 15 | "../assets/js/vendor/ZeroClipboard.min.js", 16 | "../assets/js/vendor/anchor.min.js", 17 | "../assets/js/src/application.js" 18 | ] 19 | }, 20 | "config": { 21 | "autoprefixerBrowsers": [ 22 | "Android 2.3", 23 | "Android >= 4", 24 | "Chrome >= 20", 25 | "Firefox >= 24", 26 | "Explorer >= 8", 27 | "iOS >= 6", 28 | "Opera >= 12", 29 | "Safari >= 6" 30 | ], 31 | "jqueryCheck": [ 32 | "if (typeof jQuery === 'undefined') {", 33 | " throw new Error('Bootstrap\\'s JavaScript requires jQuery')", 34 | "}\n" 35 | ], 36 | "jqueryVersionCheck": [ 37 | "+function ($) {", 38 | " 'use strict';", 39 | " var version = $.fn.jquery.split(' ')[0].split('.')", 40 | " if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 3)) {", 41 | " throw new Error('Bootstrap\\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4')", 42 | " }", 43 | "}(jQuery);\n\n" 44 | ] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/grunt/sauce_browsers.yml: -------------------------------------------------------------------------------- 1 | [ 2 | # Docs: https://saucelabs.com/docs/platforms/webdriver 3 | 4 | { 5 | browserName: "safari", 6 | platform: "OS X 10.10" 7 | }, 8 | { 9 | browserName: "chrome", 10 | platform: "OS X 10.10" 11 | }, 12 | { 13 | browserName: "firefox", 14 | platform: "OS X 10.10" 15 | }, 16 | 17 | # Mac Opera not currently supported by Sauce Labs 18 | 19 | { 20 | browserName: "internet explorer", 21 | version: "11", 22 | platform: "Windows 8.1" 23 | }, 24 | { 25 | browserName: "internet explorer", 26 | version: "10", 27 | platform: "Windows 8" 28 | }, 29 | { 30 | browserName: "internet explorer", 31 | version: "9", 32 | platform: "Windows 7" 33 | }, 34 | { 35 | browserName: "internet explorer", 36 | version: "8", 37 | platform: "Windows 7" 38 | }, 39 | 40 | # { # Unofficial 41 | # browserName: "internet explorer", 42 | # version: "7", 43 | # platform: "Windows XP" 44 | # }, 45 | 46 | { 47 | browserName: "chrome", 48 | platform: "Windows 8.1" 49 | }, 50 | { 51 | browserName: "firefox", 52 | platform: "Windows 8.1" 53 | }, 54 | 55 | # Win Opera 15+ not currently supported by Sauce Labs 56 | 57 | { 58 | browserName: "iphone", 59 | platform: "OS X 10.10", 60 | version: "9.2" 61 | }, 62 | 63 | # iOS Chrome not currently supported by Sauce Labs 64 | 65 | # Linux (unofficial) 66 | { 67 | browserName: "chrome", 68 | platform: "Linux" 69 | }, 70 | { 71 | browserName: "firefox", 72 | platform: "Linux" 73 | } 74 | 75 | # Android Chrome not currently supported by Sauce Labs 76 | 77 | # { # Android Browser (super-unofficial) 78 | # browserName: "android", 79 | # version: "4.0", 80 | # platform: "Linux" 81 | # } 82 | ] 83 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/js/.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "disallowEmptyBlocks": true, 3 | "disallowKeywords": ["with"], 4 | "disallowMixedSpacesAndTabs": true, 5 | "disallowMultipleLineStrings": true, 6 | "disallowMultipleVarDecl": true, 7 | "disallowQuotedKeysInObjects": "allButReserved", 8 | "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"], 9 | "disallowSpaceBeforeBinaryOperators": [","], 10 | "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"], 11 | "disallowSpacesInFunctionDeclaration": { "beforeOpeningRoundBrace": true }, 12 | "disallowSpacesInNamedFunctionExpression": { "beforeOpeningRoundBrace": true }, 13 | "disallowSpacesInsideArrayBrackets": true, 14 | "disallowSpacesInsideParentheses": true, 15 | "disallowTrailingComma": true, 16 | "disallowTrailingWhitespace": true, 17 | "requireCamelCaseOrUpperCaseIdentifiers": true, 18 | "requireCapitalizedConstructors": true, 19 | "requireCommaBeforeLineBreak": true, 20 | "requireDollarBeforejQueryAssignment": true, 21 | "requireDotNotation": true, 22 | "requireLineFeedAtFileEnd": true, 23 | "requirePaddingNewLinesAfterUseStrict": true, 24 | "requirePaddingNewLinesBeforeExport": true, 25 | "requireSpaceAfterBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", "<", ">=", "<="], 26 | "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"], 27 | "requireSpaceAfterLineComment": true, 28 | "requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", "<", ">=", "<="], 29 | "requireSpaceBetweenArguments": true, 30 | "requireSpacesInAnonymousFunctionExpression": { "beforeOpeningCurlyBrace": true, "beforeOpeningRoundBrace": true }, 31 | "requireSpacesInConditionalExpression": true, 32 | "requireSpacesInForStatement": true, 33 | "requireSpacesInFunctionDeclaration": { "beforeOpeningCurlyBrace": true }, 34 | "requireSpacesInFunctionExpression": { "beforeOpeningCurlyBrace": true }, 35 | "requireSpacesInNamedFunctionExpression": { "beforeOpeningCurlyBrace": true }, 36 | "requireSpacesInsideObjectBrackets": "allButNested", 37 | "validateAlignedFunctionParameters": true, 38 | "validateIndentation": 2, 39 | "validateLineBreaks": "LF", 40 | "validateNewlineAfterArrayElements": true, 41 | "validateQuoteMarks": "'" 42 | } 43 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/js/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "asi" : true, 3 | "browser" : true, 4 | "eqeqeq" : false, 5 | "eqnull" : true, 6 | "es3" : true, 7 | "expr" : true, 8 | "jquery" : true, 9 | "latedef" : true, 10 | "laxbreak" : true, 11 | "nonbsp" : true, 12 | "strict" : true, 13 | "undef" : true, 14 | "unused" : true 15 | } 16 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/js/alert.js: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * Bootstrap: alert.js v3.3.7 3 | * http://getbootstrap.com/javascript/#alerts 4 | * ======================================================================== 5 | * Copyright 2011-2016 Twitter, Inc. 6 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 7 | * ======================================================================== */ 8 | 9 | 10 | +function ($) { 11 | 'use strict'; 12 | 13 | // ALERT CLASS DEFINITION 14 | // ====================== 15 | 16 | var dismiss = '[data-dismiss="alert"]' 17 | var Alert = function (el) { 18 | $(el).on('click', dismiss, this.close) 19 | } 20 | 21 | Alert.VERSION = '3.3.7' 22 | 23 | Alert.TRANSITION_DURATION = 150 24 | 25 | Alert.prototype.close = function (e) { 26 | var $this = $(this) 27 | var selector = $this.attr('data-target') 28 | 29 | if (!selector) { 30 | selector = $this.attr('href') 31 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 32 | } 33 | 34 | var $parent = $(selector === '#' ? [] : selector) 35 | 36 | if (e) e.preventDefault() 37 | 38 | if (!$parent.length) { 39 | $parent = $this.closest('.alert') 40 | } 41 | 42 | $parent.trigger(e = $.Event('close.bs.alert')) 43 | 44 | if (e.isDefaultPrevented()) return 45 | 46 | $parent.removeClass('in') 47 | 48 | function removeElement() { 49 | // detach from parent, fire event then clean up data 50 | $parent.detach().trigger('closed.bs.alert').remove() 51 | } 52 | 53 | $.support.transition && $parent.hasClass('fade') ? 54 | $parent 55 | .one('bsTransitionEnd', removeElement) 56 | .emulateTransitionEnd(Alert.TRANSITION_DURATION) : 57 | removeElement() 58 | } 59 | 60 | 61 | // ALERT PLUGIN DEFINITION 62 | // ======================= 63 | 64 | function Plugin(option) { 65 | return this.each(function () { 66 | var $this = $(this) 67 | var data = $this.data('bs.alert') 68 | 69 | if (!data) $this.data('bs.alert', (data = new Alert(this))) 70 | if (typeof option == 'string') data[option].call($this) 71 | }) 72 | } 73 | 74 | var old = $.fn.alert 75 | 76 | $.fn.alert = Plugin 77 | $.fn.alert.Constructor = Alert 78 | 79 | 80 | // ALERT NO CONFLICT 81 | // ================= 82 | 83 | $.fn.alert.noConflict = function () { 84 | $.fn.alert = old 85 | return this 86 | } 87 | 88 | 89 | // ALERT DATA-API 90 | // ============== 91 | 92 | $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) 93 | 94 | }(jQuery); 95 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/js/transition.js: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * Bootstrap: transition.js v3.3.7 3 | * http://getbootstrap.com/javascript/#transitions 4 | * ======================================================================== 5 | * Copyright 2011-2016 Twitter, Inc. 6 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 7 | * ======================================================================== */ 8 | 9 | 10 | +function ($) { 11 | 'use strict'; 12 | 13 | // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) 14 | // ============================================================ 15 | 16 | function transitionEnd() { 17 | var el = document.createElement('bootstrap') 18 | 19 | var transEndEventNames = { 20 | WebkitTransition : 'webkitTransitionEnd', 21 | MozTransition : 'transitionend', 22 | OTransition : 'oTransitionEnd otransitionend', 23 | transition : 'transitionend' 24 | } 25 | 26 | for (var name in transEndEventNames) { 27 | if (el.style[name] !== undefined) { 28 | return { end: transEndEventNames[name] } 29 | } 30 | } 31 | 32 | return false // explicit for ie8 ( ._.) 33 | } 34 | 35 | // http://blog.alexmaccaw.com/css-transitions 36 | $.fn.emulateTransitionEnd = function (duration) { 37 | var called = false 38 | var $el = this 39 | $(this).one('bsTransitionEnd', function () { called = true }) 40 | var callback = function () { if (!called) $($el).trigger($.support.transition.end) } 41 | setTimeout(callback, duration) 42 | return this 43 | } 44 | 45 | $(function () { 46 | $.support.transition = transitionEnd() 47 | 48 | if (!$.support.transition) return 49 | 50 | $.event.special.bsTransitionEnd = { 51 | bindType: $.support.transition.end, 52 | delegateType: $.support.transition.end, 53 | handle: function (e) { 54 | if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments) 55 | } 56 | } 57 | }) 58 | 59 | }(jQuery); 60 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/less/.csslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "adjoining-classes": false, 3 | "box-sizing": false, 4 | "box-model": false, 5 | "compatible-vendor-prefixes": false, 6 | "floats": false, 7 | "font-sizes": false, 8 | "gradients": false, 9 | "important": false, 10 | "known-properties": false, 11 | "outline-none": false, 12 | "qualified-headings": false, 13 | "regex-selectors": false, 14 | "shorthand": false, 15 | "text-indent": false, 16 | "unique-headings": false, 17 | "universal-selector": false, 18 | "unqualified-attributes": false 19 | } 20 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/less/alerts.less: -------------------------------------------------------------------------------- 1 | // 2 | // Alerts 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base styles 7 | // ------------------------- 8 | 9 | .alert { 10 | padding: @alert-padding; 11 | margin-bottom: @line-height-computed; 12 | border: 1px solid transparent; 13 | border-radius: @alert-border-radius; 14 | 15 | // Headings for larger alerts 16 | h4 { 17 | margin-top: 0; 18 | // Specified for the h4 to prevent conflicts of changing @headings-color 19 | color: inherit; 20 | } 21 | 22 | // Provide class for links that match alerts 23 | .alert-link { 24 | font-weight: @alert-link-font-weight; 25 | } 26 | 27 | // Improve alignment and spacing of inner content 28 | > p, 29 | > ul { 30 | margin-bottom: 0; 31 | } 32 | 33 | > p + p { 34 | margin-top: 5px; 35 | } 36 | } 37 | 38 | // Dismissible alerts 39 | // 40 | // Expand the right padding and account for the close button's positioning. 41 | 42 | .alert-dismissable, // The misspelled .alert-dismissable was deprecated in 3.2.0. 43 | .alert-dismissible { 44 | padding-right: (@alert-padding + 20); 45 | 46 | // Adjust close link position 47 | .close { 48 | position: relative; 49 | top: -2px; 50 | right: -21px; 51 | color: inherit; 52 | } 53 | } 54 | 55 | // Alternate styles 56 | // 57 | // Generate contextual modifier classes for colorizing the alert. 58 | 59 | .alert-success { 60 | .alert-variant(@alert-success-bg; @alert-success-border; @alert-success-text); 61 | } 62 | 63 | .alert-info { 64 | .alert-variant(@alert-info-bg; @alert-info-border; @alert-info-text); 65 | } 66 | 67 | .alert-warning { 68 | .alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text); 69 | } 70 | 71 | .alert-danger { 72 | .alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text); 73 | } 74 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/less/badges.less: -------------------------------------------------------------------------------- 1 | // 2 | // Badges 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base class 7 | .badge { 8 | display: inline-block; 9 | min-width: 10px; 10 | padding: 3px 7px; 11 | font-size: @font-size-small; 12 | font-weight: @badge-font-weight; 13 | color: @badge-color; 14 | line-height: @badge-line-height; 15 | vertical-align: middle; 16 | white-space: nowrap; 17 | text-align: center; 18 | background-color: @badge-bg; 19 | border-radius: @badge-border-radius; 20 | 21 | // Empty badges collapse automatically (not available in IE8) 22 | &:empty { 23 | display: none; 24 | } 25 | 26 | // Quick fix for badges in buttons 27 | .btn & { 28 | position: relative; 29 | top: -1px; 30 | } 31 | 32 | .btn-xs &, 33 | .btn-group-xs > .btn & { 34 | top: 0; 35 | padding: 1px 5px; 36 | } 37 | 38 | // Hover state, but only for links 39 | a& { 40 | &:hover, 41 | &:focus { 42 | color: @badge-link-hover-color; 43 | text-decoration: none; 44 | cursor: pointer; 45 | } 46 | } 47 | 48 | // Account for badges in navs 49 | .list-group-item.active > &, 50 | .nav-pills > .active > a > & { 51 | color: @badge-active-color; 52 | background-color: @badge-active-bg; 53 | } 54 | 55 | .list-group-item > & { 56 | float: right; 57 | } 58 | 59 | .list-group-item > & + & { 60 | margin-right: 5px; 61 | } 62 | 63 | .nav-pills > li > a > & { 64 | margin-left: 3px; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/less/bootstrap.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.7 (http://getbootstrap.com) 3 | * Copyright 2011-2016 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | // Core variables and mixins 8 | @import "variables.less"; 9 | @import "mixins.less"; 10 | 11 | // Reset and dependencies 12 | @import "normalize.less"; 13 | @import "print.less"; 14 | @import "glyphicons.less"; 15 | 16 | // Core CSS 17 | @import "scaffolding.less"; 18 | @import "type.less"; 19 | @import "code.less"; 20 | @import "grid.less"; 21 | @import "tables.less"; 22 | @import "forms.less"; 23 | @import "buttons.less"; 24 | 25 | // Components 26 | @import "component-animations.less"; 27 | @import "dropdowns.less"; 28 | @import "button-groups.less"; 29 | @import "input-groups.less"; 30 | @import "navs.less"; 31 | @import "navbar.less"; 32 | @import "breadcrumbs.less"; 33 | @import "pagination.less"; 34 | @import "pager.less"; 35 | @import "labels.less"; 36 | @import "badges.less"; 37 | @import "jumbotron.less"; 38 | @import "thumbnails.less"; 39 | @import "alerts.less"; 40 | @import "progress-bars.less"; 41 | @import "media.less"; 42 | @import "list-group.less"; 43 | @import "panels.less"; 44 | @import "responsive-embed.less"; 45 | @import "wells.less"; 46 | @import "close.less"; 47 | 48 | // Components w/ JavaScript 49 | @import "modals.less"; 50 | @import "tooltip.less"; 51 | @import "popovers.less"; 52 | @import "carousel.less"; 53 | 54 | // Utility classes 55 | @import "utilities.less"; 56 | @import "responsive-utilities.less"; 57 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/less/breadcrumbs.less: -------------------------------------------------------------------------------- 1 | // 2 | // Breadcrumbs 3 | // -------------------------------------------------- 4 | 5 | 6 | .breadcrumb { 7 | padding: @breadcrumb-padding-vertical @breadcrumb-padding-horizontal; 8 | margin-bottom: @line-height-computed; 9 | list-style: none; 10 | background-color: @breadcrumb-bg; 11 | border-radius: @border-radius-base; 12 | 13 | > li { 14 | display: inline-block; 15 | 16 | + li:before { 17 | content: "@{breadcrumb-separator}\00a0"; // Unicode space added since inline-block means non-collapsing white-space 18 | padding: 0 5px; 19 | color: @breadcrumb-color; 20 | } 21 | } 22 | 23 | > .active { 24 | color: @breadcrumb-active-color; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/less/close.less: -------------------------------------------------------------------------------- 1 | // 2 | // Close icons 3 | // -------------------------------------------------- 4 | 5 | 6 | .close { 7 | float: right; 8 | font-size: (@font-size-base * 1.5); 9 | font-weight: @close-font-weight; 10 | line-height: 1; 11 | color: @close-color; 12 | text-shadow: @close-text-shadow; 13 | .opacity(.2); 14 | 15 | &:hover, 16 | &:focus { 17 | color: @close-color; 18 | text-decoration: none; 19 | cursor: pointer; 20 | .opacity(.5); 21 | } 22 | 23 | // Additional properties for button version 24 | // iOS requires the button element instead of an anchor tag. 25 | // If you want the anchor version, it requires `href="#"`. 26 | // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile 27 | button& { 28 | padding: 0; 29 | cursor: pointer; 30 | background: transparent; 31 | border: 0; 32 | -webkit-appearance: none; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/less/code.less: -------------------------------------------------------------------------------- 1 | // 2 | // Code (inline and block) 3 | // -------------------------------------------------- 4 | 5 | 6 | // Inline and block code styles 7 | code, 8 | kbd, 9 | pre, 10 | samp { 11 | font-family: @font-family-monospace; 12 | } 13 | 14 | // Inline code 15 | code { 16 | padding: 2px 4px; 17 | font-size: 90%; 18 | color: @code-color; 19 | background-color: @code-bg; 20 | border-radius: @border-radius-base; 21 | } 22 | 23 | // User input typically entered via keyboard 24 | kbd { 25 | padding: 2px 4px; 26 | font-size: 90%; 27 | color: @kbd-color; 28 | background-color: @kbd-bg; 29 | border-radius: @border-radius-small; 30 | box-shadow: inset 0 -1px 0 rgba(0,0,0,.25); 31 | 32 | kbd { 33 | padding: 0; 34 | font-size: 100%; 35 | font-weight: bold; 36 | box-shadow: none; 37 | } 38 | } 39 | 40 | // Blocks of code 41 | pre { 42 | display: block; 43 | padding: ((@line-height-computed - 1) / 2); 44 | margin: 0 0 (@line-height-computed / 2); 45 | font-size: (@font-size-base - 1); // 14px to 13px 46 | line-height: @line-height-base; 47 | word-break: break-all; 48 | word-wrap: break-word; 49 | color: @pre-color; 50 | background-color: @pre-bg; 51 | border: 1px solid @pre-border-color; 52 | border-radius: @border-radius-base; 53 | 54 | // Account for some code outputs that place code tags in pre tags 55 | code { 56 | padding: 0; 57 | font-size: inherit; 58 | color: inherit; 59 | white-space: pre-wrap; 60 | background-color: transparent; 61 | border-radius: 0; 62 | } 63 | } 64 | 65 | // Enable scrollable blocks of code 66 | .pre-scrollable { 67 | max-height: @pre-scrollable-max-height; 68 | overflow-y: scroll; 69 | } 70 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/less/component-animations.less: -------------------------------------------------------------------------------- 1 | // 2 | // Component animations 3 | // -------------------------------------------------- 4 | 5 | // Heads up! 6 | // 7 | // We don't use the `.opacity()` mixin here since it causes a bug with text 8 | // fields in IE7-8. Source: https://github.com/twbs/bootstrap/pull/3552. 9 | 10 | .fade { 11 | opacity: 0; 12 | .transition(opacity .15s linear); 13 | &.in { 14 | opacity: 1; 15 | } 16 | } 17 | 18 | .collapse { 19 | display: none; 20 | 21 | &.in { display: block; } 22 | tr&.in { display: table-row; } 23 | tbody&.in { display: table-row-group; } 24 | } 25 | 26 | .collapsing { 27 | position: relative; 28 | height: 0; 29 | overflow: hidden; 30 | .transition-property(~"height, visibility"); 31 | .transition-duration(.35s); 32 | .transition-timing-function(ease); 33 | } 34 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/less/grid.less: -------------------------------------------------------------------------------- 1 | // 2 | // Grid system 3 | // -------------------------------------------------- 4 | 5 | 6 | // Container widths 7 | // 8 | // Set the container width, and override it for fixed navbars in media queries. 9 | 10 | .container { 11 | .container-fixed(); 12 | 13 | @media (min-width: @screen-sm-min) { 14 | width: @container-sm; 15 | } 16 | @media (min-width: @screen-md-min) { 17 | width: @container-md; 18 | } 19 | @media (min-width: @screen-lg-min) { 20 | width: @container-lg; 21 | } 22 | } 23 | 24 | 25 | // Fluid container 26 | // 27 | // Utilizes the mixin meant for fixed width containers, but without any defined 28 | // width for fluid, full width layouts. 29 | 30 | .container-fluid { 31 | .container-fixed(); 32 | } 33 | 34 | 35 | // Row 36 | // 37 | // Rows contain and clear the floats of your columns. 38 | 39 | .row { 40 | .make-row(); 41 | } 42 | 43 | 44 | // Columns 45 | // 46 | // Common styles for small and large grid columns 47 | 48 | .make-grid-columns(); 49 | 50 | 51 | // Extra small grid 52 | // 53 | // Columns, offsets, pushes, and pulls for extra small devices like 54 | // smartphones. 55 | 56 | .make-grid(xs); 57 | 58 | 59 | // Small grid 60 | // 61 | // Columns, offsets, pushes, and pulls for the small device range, from phones 62 | // to tablets. 63 | 64 | @media (min-width: @screen-sm-min) { 65 | .make-grid(sm); 66 | } 67 | 68 | 69 | // Medium grid 70 | // 71 | // Columns, offsets, pushes, and pulls for the desktop device range. 72 | 73 | @media (min-width: @screen-md-min) { 74 | .make-grid(md); 75 | } 76 | 77 | 78 | // Large grid 79 | // 80 | // Columns, offsets, pushes, and pulls for the large desktop device range. 81 | 82 | @media (min-width: @screen-lg-min) { 83 | .make-grid(lg); 84 | } 85 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/less/jumbotron.less: -------------------------------------------------------------------------------- 1 | // 2 | // Jumbotron 3 | // -------------------------------------------------- 4 | 5 | 6 | .jumbotron { 7 | padding-top: @jumbotron-padding; 8 | padding-bottom: @jumbotron-padding; 9 | margin-bottom: @jumbotron-padding; 10 | color: @jumbotron-color; 11 | background-color: @jumbotron-bg; 12 | 13 | h1, 14 | .h1 { 15 | color: @jumbotron-heading-color; 16 | } 17 | 18 | p { 19 | margin-bottom: (@jumbotron-padding / 2); 20 | font-size: @jumbotron-font-size; 21 | font-weight: 200; 22 | } 23 | 24 | > hr { 25 | border-top-color: darken(@jumbotron-bg, 10%); 26 | } 27 | 28 | .container &, 29 | .container-fluid & { 30 | border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container 31 | padding-left: (@grid-gutter-width / 2); 32 | padding-right: (@grid-gutter-width / 2); 33 | } 34 | 35 | .container { 36 | max-width: 100%; 37 | } 38 | 39 | @media screen and (min-width: @screen-sm-min) { 40 | padding-top: (@jumbotron-padding * 1.6); 41 | padding-bottom: (@jumbotron-padding * 1.6); 42 | 43 | .container &, 44 | .container-fluid & { 45 | padding-left: (@jumbotron-padding * 2); 46 | padding-right: (@jumbotron-padding * 2); 47 | } 48 | 49 | h1, 50 | .h1 { 51 | font-size: @jumbotron-heading-font-size; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/less/labels.less: -------------------------------------------------------------------------------- 1 | // 2 | // Labels 3 | // -------------------------------------------------- 4 | 5 | .label { 6 | display: inline; 7 | padding: .2em .6em .3em; 8 | font-size: 75%; 9 | font-weight: bold; 10 | line-height: 1; 11 | color: @label-color; 12 | text-align: center; 13 | white-space: nowrap; 14 | vertical-align: baseline; 15 | border-radius: .25em; 16 | 17 | // Add hover effects, but only for links 18 | a& { 19 | &:hover, 20 | &:focus { 21 | color: @label-link-hover-color; 22 | text-decoration: none; 23 | cursor: pointer; 24 | } 25 | } 26 | 27 | // Empty labels collapse automatically (not available in IE8) 28 | &:empty { 29 | display: none; 30 | } 31 | 32 | // Quick fix for labels in buttons 33 | .btn & { 34 | position: relative; 35 | top: -1px; 36 | } 37 | } 38 | 39 | // Colors 40 | // Contextual variations (linked labels get darker on :hover) 41 | 42 | .label-default { 43 | .label-variant(@label-default-bg); 44 | } 45 | 46 | .label-primary { 47 | .label-variant(@label-primary-bg); 48 | } 49 | 50 | .label-success { 51 | .label-variant(@label-success-bg); 52 | } 53 | 54 | .label-info { 55 | .label-variant(@label-info-bg); 56 | } 57 | 58 | .label-warning { 59 | .label-variant(@label-warning-bg); 60 | } 61 | 62 | .label-danger { 63 | .label-variant(@label-danger-bg); 64 | } 65 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/less/media.less: -------------------------------------------------------------------------------- 1 | .media { 2 | // Proper spacing between instances of .media 3 | margin-top: 15px; 4 | 5 | &:first-child { 6 | margin-top: 0; 7 | } 8 | } 9 | 10 | .media, 11 | .media-body { 12 | zoom: 1; 13 | overflow: hidden; 14 | } 15 | 16 | .media-body { 17 | width: 10000px; 18 | } 19 | 20 | .media-object { 21 | display: block; 22 | 23 | // Fix collapse in webkit from max-width: 100% and display: table-cell. 24 | &.img-thumbnail { 25 | max-width: none; 26 | } 27 | } 28 | 29 | .media-right, 30 | .media > .pull-right { 31 | padding-left: 10px; 32 | } 33 | 34 | .media-left, 35 | .media > .pull-left { 36 | padding-right: 10px; 37 | } 38 | 39 | .media-left, 40 | .media-right, 41 | .media-body { 42 | display: table-cell; 43 | vertical-align: top; 44 | } 45 | 46 | .media-middle { 47 | vertical-align: middle; 48 | } 49 | 50 | .media-bottom { 51 | vertical-align: bottom; 52 | } 53 | 54 | // Reset margins on headings for tighter default spacing 55 | .media-heading { 56 | margin-top: 0; 57 | margin-bottom: 5px; 58 | } 59 | 60 | // Media list variation 61 | // 62 | // Undo default ul/ol styles 63 | .media-list { 64 | padding-left: 0; 65 | list-style: none; 66 | } 67 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------------------------------- 3 | 4 | // Utilities 5 | @import "mixins/hide-text.less"; 6 | @import "mixins/opacity.less"; 7 | @import "mixins/image.less"; 8 | @import "mixins/labels.less"; 9 | @import "mixins/reset-filter.less"; 10 | @import "mixins/resize.less"; 11 | @import "mixins/responsive-visibility.less"; 12 | @import "mixins/size.less"; 13 | @import "mixins/tab-focus.less"; 14 | @import "mixins/reset-text.less"; 15 | @import "mixins/text-emphasis.less"; 16 | @import "mixins/text-overflow.less"; 17 | @import "mixins/vendor-prefixes.less"; 18 | 19 | // Components 20 | @import "mixins/alerts.less"; 21 | @import "mixins/buttons.less"; 22 | @import "mixins/panels.less"; 23 | @import "mixins/pagination.less"; 24 | @import "mixins/list-group.less"; 25 | @import "mixins/nav-divider.less"; 26 | @import "mixins/forms.less"; 27 | @import "mixins/progress-bar.less"; 28 | @import "mixins/table-row.less"; 29 | 30 | // Skins 31 | @import "mixins/background-variant.less"; 32 | @import "mixins/border-radius.less"; 33 | @import "mixins/gradients.less"; 34 | 35 | // Layout 36 | @import "mixins/clearfix.less"; 37 | @import "mixins/center-block.less"; 38 | @import "mixins/nav-vertical-align.less"; 39 | @import "mixins/grid-framework.less"; 40 | @import "mixins/grid.less"; 41 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/less/mixins/alerts.less: -------------------------------------------------------------------------------- 1 | // Alerts 2 | 3 | .alert-variant(@background; @border; @text-color) { 4 | background-color: @background; 5 | border-color: @border; 6 | color: @text-color; 7 | 8 | hr { 9 | border-top-color: darken(@border, 5%); 10 | } 11 | .alert-link { 12 | color: darken(@text-color, 10%); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/less/mixins/background-variant.less: -------------------------------------------------------------------------------- 1 | // Contextual backgrounds 2 | 3 | .bg-variant(@color) { 4 | background-color: @color; 5 | a&:hover, 6 | a&:focus { 7 | background-color: darken(@color, 10%); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/less/mixins/border-radius.less: -------------------------------------------------------------------------------- 1 | // Single side border-radius 2 | 3 | .border-top-radius(@radius) { 4 | border-top-right-radius: @radius; 5 | border-top-left-radius: @radius; 6 | } 7 | .border-right-radius(@radius) { 8 | border-bottom-right-radius: @radius; 9 | border-top-right-radius: @radius; 10 | } 11 | .border-bottom-radius(@radius) { 12 | border-bottom-right-radius: @radius; 13 | border-bottom-left-radius: @radius; 14 | } 15 | .border-left-radius(@radius) { 16 | border-bottom-left-radius: @radius; 17 | border-top-left-radius: @radius; 18 | } 19 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/less/mixins/buttons.less: -------------------------------------------------------------------------------- 1 | // Button variants 2 | // 3 | // Easily pump out default styles, as well as :hover, :focus, :active, 4 | // and disabled options for all buttons 5 | 6 | .button-variant(@color; @background; @border) { 7 | color: @color; 8 | background-color: @background; 9 | border-color: @border; 10 | 11 | &:focus, 12 | &.focus { 13 | color: @color; 14 | background-color: darken(@background, 10%); 15 | border-color: darken(@border, 25%); 16 | } 17 | &:hover { 18 | color: @color; 19 | background-color: darken(@background, 10%); 20 | border-color: darken(@border, 12%); 21 | } 22 | &:active, 23 | &.active, 24 | .open > .dropdown-toggle& { 25 | color: @color; 26 | background-color: darken(@background, 10%); 27 | border-color: darken(@border, 12%); 28 | 29 | &:hover, 30 | &:focus, 31 | &.focus { 32 | color: @color; 33 | background-color: darken(@background, 17%); 34 | border-color: darken(@border, 25%); 35 | } 36 | } 37 | &:active, 38 | &.active, 39 | .open > .dropdown-toggle& { 40 | background-image: none; 41 | } 42 | &.disabled, 43 | &[disabled], 44 | fieldset[disabled] & { 45 | &:hover, 46 | &:focus, 47 | &.focus { 48 | background-color: @background; 49 | border-color: @border; 50 | } 51 | } 52 | 53 | .badge { 54 | color: @background; 55 | background-color: @color; 56 | } 57 | } 58 | 59 | // Button sizes 60 | .button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) { 61 | padding: @padding-vertical @padding-horizontal; 62 | font-size: @font-size; 63 | line-height: @line-height; 64 | border-radius: @border-radius; 65 | } 66 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/less/mixins/center-block.less: -------------------------------------------------------------------------------- 1 | // Center-align a block level element 2 | 3 | .center-block() { 4 | display: block; 5 | margin-left: auto; 6 | margin-right: auto; 7 | } 8 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/less/mixins/clearfix.less: -------------------------------------------------------------------------------- 1 | // Clearfix 2 | // 3 | // For modern browsers 4 | // 1. The space content is one way to avoid an Opera bug when the 5 | // contenteditable attribute is included anywhere else in the document. 6 | // Otherwise it causes space to appear at the top and bottom of elements 7 | // that are clearfixed. 8 | // 2. The use of `table` rather than `block` is only necessary if using 9 | // `:before` to contain the top-margins of child elements. 10 | // 11 | // Source: http://nicolasgallagher.com/micro-clearfix-hack/ 12 | 13 | .clearfix() { 14 | &:before, 15 | &:after { 16 | content: " "; // 1 17 | display: table; // 2 18 | } 19 | &:after { 20 | clear: both; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /BlogTemplate/wwwroot/lib/bootstrap/less/mixins/forms.less: -------------------------------------------------------------------------------- 1 | // Form validation states 2 | // 3 | // Used in forms.less to generate the form validation CSS for warnings, errors, 4 | // and successes. 5 | 6 | .form-control-validation(@text-color: #555; @border-color: #ccc; @background-color: #f5f5f5) { 7 | // Color the label and help text 8 | .help-block, 9 | .control-label, 10 | .radio, 11 | .checkbox, 12 | .radio-inline, 13 | .checkbox-inline, 14 | &.radio label, 15 | &.checkbox label, 16 | &.radio-inline label, 17 | &.checkbox-inline label { 18 | color: @text-color; 19 | } 20 | // Set the border and box shadow on specific inputs to match 21 | .form-control { 22 | border-color: @border-color; 23 | .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work 24 | &:focus { 25 | border-color: darken(@border-color, 10%); 26 | @shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@border-color, 20%); 27 | .box-shadow(@shadow); 28 | } 29 | } 30 | // Set validation states also for addons 31 | .input-group-addon { 32 | color: @text-color; 33 | border-color: @border-color; 34 | background-color: @background-color; 35 | } 36 | // Optional feedback icon 37 | .form-control-feedback { 38 | color: @text-color; 39 | } 40 | } 41 | 42 | 43 | // Form control focus state 44 | // 45 | // Generate a customized focus state and for any input with the specified color, 46 | // which defaults to the `@input-border-focus` variable. 47 | // 48 | // We highly encourage you to not customize the default value, but instead use 49 | // this to tweak colors on an as-needed basis. This aesthetic change is based on 50 | // WebKit's default styles, but applicable to a wider range of browsers. Its 51 | // usability and accessibility should be taken into account with any change. 52 | // 53 | // Example usage: change the default blue border and shadow to white for better 54 | // contrast against a dark gray background. 55 | .form-control-focus(@color: @input-border-focus) { 56 | @color-rgba: rgba(red(@color), green(@color), blue(@color), .6); 57 | &:focus { 58 | border-color: @color; 59 | outline: 0; 60 | .box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}"); 61 | } 62 | } 63 | 64 | // Form control sizing 65 | // 66 | // Relative text size, padding, and border-radii changes for form controls. For 67 | // horizontal sizing, wrap controls in the predefined grid classes. `