├── src
└── Bloggy
│ ├── Pages
│ ├── Account
│ │ ├── Logout.cshtml
│ │ ├── Logout.cshtml.cs
│ │ ├── Login.cshtml
│ │ ├── Posts
│ │ │ ├── Create.cshtml.cs
│ │ │ ├── Edit.cshtml.cs
│ │ │ ├── Create.cshtml
│ │ │ └── Edit.cshtml
│ │ └── Login.cshtml.cs
│ ├── _ViewStart.cshtml
│ ├── _ViewImports.cshtml
│ ├── Error.cshtml
│ ├── _LoginPartial.cshtml
│ ├── Contact.cshtml
│ ├── Archive.cshtml
│ ├── Tags.cshtml
│ ├── About.cshtml
│ ├── Index.cshtml
│ ├── Archive.cshtml.cs
│ ├── Index.cshtml.cs
│ ├── _CommentFormPartial.cshtml
│ ├── Tags.cshtml.cs
│ ├── Post.cshtml
│ ├── Post.cshtml.cs
│ └── Themes
│ │ ├── Dark
│ │ ├── _Layout.cshtml
│ │ └── styles
│ │ │ └── bloggy.css
│ │ └── Light
│ │ ├── _Layout.cshtml
│ │ └── styles
│ │ └── bloggy.css
│ ├── wwwroot
│ ├── images
│ │ └── logo.png
│ ├── fonts
│ │ ├── Roboto-Black.ttf
│ │ ├── Roboto-Bold.ttf
│ │ ├── Roboto-Light.ttf
│ │ ├── Roboto-Thin.ttf
│ │ ├── Roboto-Italic.ttf
│ │ ├── Roboto-Medium.ttf
│ │ ├── Roboto-Regular.ttf
│ │ ├── Roboto-BoldItalic.ttf
│ │ ├── Roboto-ThinItalic.ttf
│ │ ├── Roboto-BlackItalic.ttf
│ │ ├── Roboto-LightItalic.ttf
│ │ └── Roboto-MediumItalic.ttf
│ ├── scripts
│ │ ├── codemirror
│ │ │ └── tablist.js
│ │ └── simplemde.js
│ └── styles
│ │ ├── simplemde.css
│ │ └── simplemde.min.css
│ ├── Models
│ ├── PasswordFormat.cs
│ ├── Credential.cs
│ ├── User.cs
│ ├── BloggingContext.cs
│ ├── Blog.cs
│ ├── Post.cs
│ └── Comment.cs
│ ├── TagHelpers
│ ├── AlertType.cs
│ ├── AlertTagHelper.cs
│ ├── PartialTagHelper.cs
│ ├── GravatarTagHelper.cs
│ └── MarkdownTagHelper.cs
│ ├── AppSettings.cs
│ ├── Program.cs
│ ├── web.config
│ ├── Properties
│ └── launchSettings.json
│ ├── appsettings.json
│ ├── Bloggy.csproj
│ ├── ThemeViewLocationExpander.cs
│ ├── Migrations
│ ├── BloggingContextModelSnapshot.cs
│ ├── 20161227000614_Initial.Designer.cs
│ └── 20161227000614_Initial.cs
│ └── Startup.cs
├── .gitignore
├── Bloggy.sln
├── README.md
└── .gitattributes
/src/Bloggy/Pages/Account/Logout.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 |
3 | @model LogoutModel
--------------------------------------------------------------------------------
/src/Bloggy/Pages/_ViewStart.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "_Layout";
3 | }
4 |
--------------------------------------------------------------------------------
/src/Bloggy/wwwroot/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hishamco/Bloggy/HEAD/src/Bloggy/wwwroot/images/logo.png
--------------------------------------------------------------------------------
/src/Bloggy/wwwroot/fonts/Roboto-Black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hishamco/Bloggy/HEAD/src/Bloggy/wwwroot/fonts/Roboto-Black.ttf
--------------------------------------------------------------------------------
/src/Bloggy/wwwroot/fonts/Roboto-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hishamco/Bloggy/HEAD/src/Bloggy/wwwroot/fonts/Roboto-Bold.ttf
--------------------------------------------------------------------------------
/src/Bloggy/wwwroot/fonts/Roboto-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hishamco/Bloggy/HEAD/src/Bloggy/wwwroot/fonts/Roboto-Light.ttf
--------------------------------------------------------------------------------
/src/Bloggy/wwwroot/fonts/Roboto-Thin.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hishamco/Bloggy/HEAD/src/Bloggy/wwwroot/fonts/Roboto-Thin.ttf
--------------------------------------------------------------------------------
/src/Bloggy/wwwroot/fonts/Roboto-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hishamco/Bloggy/HEAD/src/Bloggy/wwwroot/fonts/Roboto-Italic.ttf
--------------------------------------------------------------------------------
/src/Bloggy/wwwroot/fonts/Roboto-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hishamco/Bloggy/HEAD/src/Bloggy/wwwroot/fonts/Roboto-Medium.ttf
--------------------------------------------------------------------------------
/src/Bloggy/wwwroot/fonts/Roboto-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hishamco/Bloggy/HEAD/src/Bloggy/wwwroot/fonts/Roboto-Regular.ttf
--------------------------------------------------------------------------------
/src/Bloggy/wwwroot/fonts/Roboto-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hishamco/Bloggy/HEAD/src/Bloggy/wwwroot/fonts/Roboto-BoldItalic.ttf
--------------------------------------------------------------------------------
/src/Bloggy/wwwroot/fonts/Roboto-ThinItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hishamco/Bloggy/HEAD/src/Bloggy/wwwroot/fonts/Roboto-ThinItalic.ttf
--------------------------------------------------------------------------------
/src/Bloggy/Pages/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @namespace Bloggy.Pages
2 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
3 | @addTagHelper *, Bloggy
--------------------------------------------------------------------------------
/src/Bloggy/wwwroot/fonts/Roboto-BlackItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hishamco/Bloggy/HEAD/src/Bloggy/wwwroot/fonts/Roboto-BlackItalic.ttf
--------------------------------------------------------------------------------
/src/Bloggy/wwwroot/fonts/Roboto-LightItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hishamco/Bloggy/HEAD/src/Bloggy/wwwroot/fonts/Roboto-LightItalic.ttf
--------------------------------------------------------------------------------
/src/Bloggy/wwwroot/fonts/Roboto-MediumItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hishamco/Bloggy/HEAD/src/Bloggy/wwwroot/fonts/Roboto-MediumItalic.ttf
--------------------------------------------------------------------------------
/src/Bloggy/Models/PasswordFormat.cs:
--------------------------------------------------------------------------------
1 | namespace Bloggy.Models
2 | {
3 | public enum PasswordFormat
4 | {
5 | Clear,
6 | MD5,
7 | SHA1,
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Bloggy/TagHelpers/AlertType.cs:
--------------------------------------------------------------------------------
1 | namespace Bloggy.TagHelpers
2 | {
3 | public enum AlertType
4 | {
5 | Error,
6 | Information,
7 | Success,
8 | Warning
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Bloggy/Models/Credential.cs:
--------------------------------------------------------------------------------
1 | namespace Bloggy.Models
2 | {
3 | public class Credential
4 | {
5 | public PasswordFormat PasswordFormat { get; set; }
6 |
7 | public User User { get; set; }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Bloggy/Pages/Error.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 |
3 | @{
4 | ViewBag.Title = "Error";
5 | }
6 |
7 |
8 |
@ViewBag.Title
9 |
An error occurred while processing your request.
10 |
--------------------------------------------------------------------------------
/src/Bloggy/AppSettings.cs:
--------------------------------------------------------------------------------
1 | using Bloggy.Models;
2 |
3 | namespace Bloggy
4 | {
5 | public class AppSettings
6 | {
7 | public Blog Blog { get; set; }
8 |
9 | public Credential Credential { get; set; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bin
2 | obj
3 | *.suo
4 | *.user
5 | _ReSharper.*
6 | *.DS_Store
7 | *.userprefs
8 | *.pidb
9 | *.vspx
10 | *.psess
11 | packages
12 | target
13 | artifacts
14 | StyleCop.Cache
15 | node_modules
16 | *.snk
17 | .nuget/NuGet.exe
18 | project.lock.json
--------------------------------------------------------------------------------
/src/Bloggy/Models/User.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace Bloggy.Models
4 | {
5 | public class User
6 | {
7 | [Required]
8 | public string Name { get; set; }
9 |
10 | [Required]
11 | [DataType(DataType.Password)]
12 | public string Password { get; set; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Bloggy/Pages/_LoginPartial.cshtml:
--------------------------------------------------------------------------------
1 | @using System.Security.Principal
2 |
3 | @if (User.Identity.IsAuthenticated)
4 | {
5 | [ @User.Identity.Name | Log Out ]
6 | }
7 |
8 |
9 | @if (!User.Identity.IsAuthenticated)
10 | {
11 | Log In
12 | }
13 |
--------------------------------------------------------------------------------
/src/Bloggy/Models/BloggingContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 |
3 | namespace Bloggy.Models
4 | {
5 | public class BloggingContext : DbContext
6 | {
7 | public BloggingContext(DbContextOptions options)
8 | : base(options)
9 | {
10 |
11 | }
12 |
13 | public DbSet Posts { get; set; }
14 |
15 | public DbSet Comments { get; set; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Bloggy/Models/Blog.cs:
--------------------------------------------------------------------------------
1 | namespace Bloggy.Models
2 | {
3 | public class Blog
4 | {
5 | public string Name { get; set; }
6 |
7 | public string Description { get; set; }
8 |
9 | public string Image { get; set; }
10 |
11 | public int PostsPerPage { get; set; }
12 |
13 | public bool AllowComments { get; set; }
14 |
15 | public int DaysToComment { get; set; }
16 |
17 | public string Theme { get; set; }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Bloggy/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Hosting;
2 | using System.IO;
3 |
4 | namespace Bloggy
5 | {
6 | public class Program
7 | {
8 | public static void Main(string[] args)
9 | {
10 | var host = new WebHostBuilder()
11 | .UseKestrel()
12 | .UseContentRoot(Directory.GetCurrentDirectory())
13 | .UseIISIntegration()
14 | .UseStartup()
15 | .Build();
16 |
17 | host.Run();
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Bloggy/Pages/Contact.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 |
3 | @{
4 | ViewBag.Title = "Contact";
5 | }
6 |
7 |
--------------------------------------------------------------------------------
/src/Bloggy/web.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/Bloggy/Pages/Account/Logout.cshtml.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Authentication;
2 | using Microsoft.AspNetCore.Authentication.Cookies;
3 | using Microsoft.AspNetCore.Mvc;
4 | using Microsoft.AspNetCore.Mvc.RazorPages;
5 | using System.Threading.Tasks;
6 |
7 | namespace Bloggy.Pages.Account
8 | {
9 | public class LogoutModel : PageModel
10 | {
11 | public async Task OnGet(string returnUrl = null)
12 | {
13 | await PageContext.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
14 | return RedirectToPage("/Index");
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/src/Bloggy/Pages/Archive.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 |
3 | @model ArchiveModel
4 |
5 | @{
6 | ViewBag.Title = "Archive";
7 | }
8 |
9 |
10 | @foreach (var group in Model.GroupedPostsByMonth)
11 | {
12 |
13 |
@group.Item1
14 |
15 | @foreach (var post in group.Item2)
16 | {
17 | @post.Title @if (User.Identity.IsAuthenticated){[ Edit ] }
18 | }
19 |
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/Bloggy/Pages/Tags.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 |
3 | @model TagsModel
4 |
5 | @{
6 | ViewBag.Title = "Tags";
7 | }
8 |
9 |
--------------------------------------------------------------------------------
/src/Bloggy/Pages/About.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 |
3 | @{
4 | ViewBag.Title = "About";
5 | }
6 |
7 |
8 |
@ViewBag.Title
9 |
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
10 |
--------------------------------------------------------------------------------
/src/Bloggy/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:8739/",
7 | "sslPort": 0
8 | }
9 | },
10 | "profiles": {
11 | "IIS Express": {
12 | "commandName": "IISExpress",
13 | "launchBrowser": true,
14 | "environmentVariables": {
15 | "ASPNETCORE_ENVIRONMENT": "Development"
16 | }
17 | },
18 | "Bloggy": {
19 | "commandName": "Project",
20 | "launchBrowser": true,
21 | "launchUrl": "http://localhost:5000",
22 | "environmentVariables": {
23 | "ASPNETCORE_ENVIRONMENT": "Development"
24 | }
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/src/Bloggy/Pages/Index.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 |
3 | @model IndexModel
4 |
5 | @{
6 | ViewBag.Title = "Home";
7 | }
8 |
9 |
10 | @foreach (var post in Model.Posts)
11 | {
12 |
13 |
14 | @post.PublishedAt.ToString("MMMM dd, yyyy")
15 | @post.Excerpt
16 | @if (User.Identity.IsAuthenticated)
17 | {
18 | [ Edit ]
19 | }
20 |
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/src/Bloggy/Models/Post.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 |
5 | namespace Bloggy.Models
6 | {
7 | public class Post
8 | {
9 | public int Id { get; set; }
10 |
11 | [Required]
12 | public string Title { get; set; }
13 |
14 | [Required]
15 | public string Slug { get; set; }
16 |
17 | public string Excerpt { get; set; }
18 |
19 | [Required]
20 | public string Content { get; set; }
21 |
22 | public DateTime LastModified { get; set; }
23 |
24 | public bool IsPublished { get; set; }
25 |
26 | public DateTime PublishedAt { get; set; }
27 |
28 | [Required]
29 | public string Tags { get; set; }
30 |
31 | public virtual ICollection Comments { get; set; }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Bloggy/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "AppSettings": {
3 | "Blog": {
4 | "Name": "bloggy",
5 | "Description": "A simple ASP.NET Core blog engine",
6 | "Image": "images/logo.png",
7 | "PostsPerPage": "5",
8 | "AllowComments": true,
9 | "DaysToComment": 7,
10 | "Theme": "Light"
11 | },
12 | "Credential": {
13 | "PasswordFormat": "SHA1",
14 | "User": {
15 | "Name": "hishamco",
16 | "Password": "21bd12dc183f740ee76f27b78eb39c8ad972a757" // P@ssw0rd
17 | }
18 | }
19 | },
20 | "Data": {
21 | "DefaultConnection": {
22 | "ConnectionString": "Filename=Blogging.db"
23 | }
24 | },
25 | "Logging": {
26 | "IncludeScopes": false,
27 | "LogLevel": {
28 | "Default": "Debug",
29 | "System": "Information",
30 | "Microsoft": "Information"
31 | }
32 | },
33 | "ReCaptcha": {
34 | "SiteKey": "Your ReCaptcha Key"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Bloggy/Bloggy.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.0
5 | true
6 | Bloggy
7 | Exe
8 | Bloggy
9 | bloggy-77a2c865-d54e-49bd-a25e-37df2334c17e
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/Bloggy/Models/Comment.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.DataAnnotations;
3 | using System.ComponentModel.DataAnnotations.Schema;
4 |
5 | namespace Bloggy.Models
6 | {
7 | public class Comment
8 | {
9 | public int Id { get; set; }
10 |
11 | [Required]
12 | [StringLength(100, MinimumLength = 2)]
13 | [Display(Name = "Name")]
14 | public string Author { get; set; }
15 |
16 | [Required]
17 | [DataType(DataType.EmailAddress)]
18 | public string Email { get; set; }
19 |
20 | [DataType(DataType.Url)]
21 | public string Website { get; set; }
22 |
23 | [Required(ErrorMessage = "The Comment field is required.")]
24 | [Display(Name = "Comment")]
25 | public string Content { get; set; }
26 |
27 | public DateTime PublishedAt { get; set; }
28 |
29 | public int PostId { get; set; }
30 |
31 | [ForeignKey("PostId")]
32 | public Post Post { get; set; }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Bloggy/Pages/Archive.cshtml.cs:
--------------------------------------------------------------------------------
1 | using Bloggy.Models;
2 | using Microsoft.AspNetCore.Mvc.RazorPages;
3 | using Microsoft.EntityFrameworkCore;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Threading.Tasks;
8 |
9 | namespace Bloggy.Pages
10 | {
11 | public class ArchiveModel : PageModel
12 | {
13 | private readonly BloggingContext _db;
14 |
15 | public ArchiveModel(BloggingContext db)
16 | {
17 | _db = db;
18 | }
19 |
20 | public IList>> GroupedPostsByMonth { get; private set; }
21 |
22 | public async Task OnGetAsync()
23 | {
24 | var notPublished = DateTime.MinValue.ToString("MMMM yyyy");
25 |
26 |
27 | GroupedPostsByMonth = await _db.Posts.GroupBy(p => p.PublishedAt.ToString("MMMM yyyy"),
28 | (k, g) => Tuple.Create>(k == notPublished ? "Not Published" : k, g.ToList())).ToListAsync();
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/src/Bloggy/Pages/Index.cshtml.cs:
--------------------------------------------------------------------------------
1 | using Bloggy.Models;
2 | using Microsoft.AspNetCore.Mvc.RazorPages;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.Extensions.Options;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Threading.Tasks;
8 |
9 | namespace Bloggy.Pages
10 | {
11 | public class IndexModel : PageModel
12 | {
13 | private readonly BloggingContext _db;
14 | private Blog _blog;
15 |
16 | public IndexModel(BloggingContext db, IOptions appSettings)
17 | {
18 | _db = db;
19 | _blog = appSettings.Value.Blog;
20 | }
21 |
22 | public IList Posts { get; private set; }
23 |
24 | public async Task OnGetAsync()
25 | {
26 | var postsNo = _blog.PostsPerPage;
27 | Posts = await _db.Posts
28 | .Where(p => p.IsPublished)
29 | .OrderByDescending(p => p.PublishedAt)
30 | .Take(postsNo)
31 | .ToListAsync();
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/src/Bloggy/Pages/Account/Login.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 |
3 | @model LoginModel
4 |
5 | @{
6 | ViewBag.Title = "Log In";
7 | }
8 |
9 |
10 |
@ViewBag.Title
11 |
27 |
--------------------------------------------------------------------------------
/src/Bloggy/Pages/Account/Posts/Create.cshtml.cs:
--------------------------------------------------------------------------------
1 | using Bloggy.Models;
2 | using Microsoft.AspNetCore.Mvc;
3 | using Microsoft.AspNetCore.Mvc.RazorPages;
4 | using System;
5 | using System.Threading.Tasks;
6 |
7 | namespace Bloggy.Pages.Account.Posts
8 | {
9 | public class CreatePostModel : PageModel
10 | {
11 | private readonly BloggingContext _db;
12 |
13 | public CreatePostModel(BloggingContext db)
14 | {
15 | _db = db;
16 | }
17 |
18 | [BindProperty]
19 | public Post Post { get; set; }
20 |
21 | [ValidateAntiForgeryToken]
22 | public async Task OnPostAsync()
23 | {
24 | if (!ModelState.IsValid)
25 | {
26 | return Page();
27 | }
28 |
29 | if (Post.IsPublished)
30 | {
31 | Post.PublishedAt = DateTime.Now;
32 | }
33 | _db.Posts.Add(Post);
34 | await _db.SaveChangesAsync();
35 |
36 | return RedirectToPage("/Index");
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/src/Bloggy/Pages/_CommentFormPartial.cshtml:
--------------------------------------------------------------------------------
1 | @using Bloggy.Models
2 |
3 | @model Comment
4 |
5 |
--------------------------------------------------------------------------------
/src/Bloggy/Pages/Tags.cshtml.cs:
--------------------------------------------------------------------------------
1 | using Bloggy.Models;
2 | using Microsoft.AspNetCore.Mvc.RazorPages;
3 | using Microsoft.EntityFrameworkCore;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Threading.Tasks;
8 |
9 | namespace Bloggy.Pages
10 | {
11 | public class TagsModel : PageModel
12 | {
13 | private readonly BloggingContext _db;
14 |
15 | public TagsModel(BloggingContext db)
16 | {
17 | _db = db;
18 | }
19 |
20 | public IList Tags { get; set; }
21 |
22 | public async Task OnGetAsync()
23 | {
24 | Tags = await _db.Posts
25 | .SelectMany(p => p.Tags.Split(',', StringSplitOptions.RemoveEmptyEntries))
26 | .Distinct()
27 | .OrderBy(t => t)
28 | .ToListAsync();
29 | }
30 |
31 | public IList GetPosts(string tag)
32 | {
33 | return _db.Posts
34 | .Where(p => p.Tags.Contains(tag))
35 | .OrderBy(p => p.PublishedAt)
36 | .ToList();
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/src/Bloggy/TagHelpers/AlertTagHelper.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Razor.TagHelpers;
2 |
3 | namespace Bloggy.TagHelpers
4 | {
5 | [HtmlTargetElement("alert")]
6 | public class AlertTagHelper : TagHelper
7 | {
8 | public AlertType Type { get; set; }
9 |
10 | public override void Process(TagHelperContext context, TagHelperOutput output)
11 | {
12 | var @class = "alert alert-";
13 | output.TagName = "div";
14 |
15 | switch (Type)
16 | {
17 | case AlertType.Error:
18 | @class += "danger";
19 | break;
20 | case AlertType.Information:
21 | @class += "info";
22 | break;
23 | case AlertType.Success:
24 | @class += "success";
25 | break;
26 | case AlertType.Warning:
27 | @class += "warning";
28 | break;
29 | }
30 | output.Attributes.Add("class", @class);
31 | output.Attributes.Add("role", "alert");
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Bloggy/TagHelpers/PartialTagHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Microsoft.AspNetCore.Mvc.Rendering;
3 | using Microsoft.AspNetCore.Mvc.ViewFeatures;
4 | using Microsoft.AspNetCore.Razor.TagHelpers;
5 |
6 | namespace Bloggy.TagHelpers
7 | {
8 | [HtmlTargetElement("partial", Attributes = "name")]
9 | public class PartialTagHelper : TagHelper
10 | {
11 | private readonly IHtmlHelper _htmlHelper;
12 |
13 | public PartialTagHelper(IHtmlHelper htmlHelper)
14 | {
15 | _htmlHelper = htmlHelper;
16 | }
17 |
18 | [ViewContext]
19 | public ViewContext ViewContext { get; set; }
20 |
21 | public string Name { get; set; }
22 |
23 | public object Model { get; set; }
24 |
25 | public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
26 | {
27 | ((IViewContextAware)_htmlHelper).Contextualize(ViewContext);
28 |
29 | output.TagName = null;
30 |
31 | var content = await _htmlHelper.PartialAsync(Name, Model);
32 | output.Content.SetHtmlContent(content);
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/src/Bloggy/TagHelpers/GravatarTagHelper.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Razor.TagHelpers;
2 | using System.Security.Cryptography;
3 | using System.Text;
4 |
5 | namespace Bloggy.TagHelpers
6 | {
7 | [HtmlTargetElement("gravatar", TagStructure = TagStructure.NormalOrSelfClosing)]
8 | public class GravatarTagHelper : TagHelper
9 | {
10 | public string Email { get; set; }
11 |
12 | public override void Process(TagHelperContext context, TagHelperOutput output)
13 | {
14 | output.TagName = null;
15 |
16 | var hash = ComputeHash(Email);
17 | output.Content.SetHtmlContent($" ");
18 | }
19 |
20 | private string ComputeHash(string email)
21 | {
22 | var md5 = MD5.Create();
23 | var bytes = Encoding.ASCII.GetBytes(email);
24 | var hash = md5.ComputeHash(bytes);
25 | var sb = new StringBuilder();
26 | for (int i = 0; i < hash.Length; i++)
27 | {
28 | sb.Append(hash[i].ToString("x2"));
29 | }
30 |
31 | return sb.ToString();
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Bloggy/wwwroot/scripts/codemirror/tablist.js:
--------------------------------------------------------------------------------
1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 | // Distributed under an MIT license: http://codemirror.net/LICENSE
3 |
4 | var CodeMirror = require("codemirror");
5 |
6 | CodeMirror.commands.tabAndIndentMarkdownList = function (cm) {
7 | var ranges = cm.listSelections();
8 | var pos = ranges[0].head;
9 | var eolState = cm.getStateAfter(pos.line);
10 | var inList = eolState.list !== false;
11 |
12 | if (inList) {
13 | cm.execCommand("indentMore");
14 | return;
15 | }
16 |
17 | if (cm.options.indentWithTabs) {
18 | cm.execCommand("insertTab");
19 | }
20 | else {
21 | var spaces = Array(cm.options.tabSize + 1).join(" ");
22 | cm.replaceSelection(spaces);
23 | }
24 | };
25 |
26 | CodeMirror.commands.shiftTabAndUnindentMarkdownList = function (cm) {
27 | var ranges = cm.listSelections();
28 | var pos = ranges[0].head;
29 | var eolState = cm.getStateAfter(pos.line);
30 | var inList = eolState.list !== false;
31 |
32 | if (inList) {
33 | cm.execCommand("indentLess");
34 | return;
35 | }
36 |
37 | if (cm.options.indentWithTabs) {
38 | cm.execCommand("insertTab");
39 | }
40 | else {
41 | var spaces = Array(cm.options.tabSize + 1).join(" ");
42 | cm.replaceSelection(spaces);
43 | }
44 | };
45 |
--------------------------------------------------------------------------------
/src/Bloggy/TagHelpers/MarkdownTagHelper.cs:
--------------------------------------------------------------------------------
1 | using CommonMark;
2 | using Microsoft.AspNetCore.Mvc.ViewFeatures;
3 | using Microsoft.AspNetCore.Razor.TagHelpers;
4 | using System.Threading.Tasks;
5 |
6 | namespace Bloggy.TagHelpers
7 | {
8 | [HtmlTargetElement("markdown", TagStructure = TagStructure.NormalOrSelfClosing)]
9 | [HtmlTargetElement(Attributes = "markdown")]
10 | public class MarkdownTagHelper : TagHelper
11 | {
12 | public ModelExpression Content { get; set; }
13 |
14 | public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
15 | {
16 | if (output.TagName == "markdown")
17 | {
18 | output.TagName = null;
19 | }
20 | output.Attributes.RemoveAll("markdown");
21 |
22 | var content = await GetContent(output);
23 | var markdown = content;
24 | var html = CommonMarkConverter.Convert(markdown);
25 | output.Content.SetHtmlContent(html ?? string.Empty);
26 | }
27 |
28 | private async Task GetContent(TagHelperOutput output)
29 | {
30 | if (Content == null)
31 | return (await output.GetChildContentAsync()).GetContent();
32 |
33 | return Content.Model?.ToString();
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Bloggy.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26228.4
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C119D035-FEE1-4DBF-9046-2C1329F2DE0B}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6C77F36F-50BA-402B-88C1-73A8341FF76C}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bloggy", "src\Bloggy\Bloggy.csproj", "{77A2C865-D54E-49BD-A25E-37DF2334C17E}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Release|Any CPU = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {77A2C865-D54E-49BD-A25E-37DF2334C17E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {77A2C865-D54E-49BD-A25E-37DF2334C17E}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {77A2C865-D54E-49BD-A25E-37DF2334C17E}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {77A2C865-D54E-49BD-A25E-37DF2334C17E}.Release|Any CPU.Build.0 = Release|Any CPU
22 | EndGlobalSection
23 | GlobalSection(SolutionProperties) = preSolution
24 | HideSolutionNode = FALSE
25 | EndGlobalSection
26 | GlobalSection(NestedProjects) = preSolution
27 | {77A2C865-D54E-49BD-A25E-37DF2334C17E} = {C119D035-FEE1-4DBF-9046-2C1329F2DE0B}
28 | EndGlobalSection
29 | EndGlobal
30 |
--------------------------------------------------------------------------------
/src/Bloggy/Pages/Account/Posts/Edit.cshtml.cs:
--------------------------------------------------------------------------------
1 | using Bloggy.Models;
2 | using Microsoft.AspNetCore.Mvc;
3 | using Microsoft.AspNetCore.Mvc.RazorPages;
4 | using Microsoft.EntityFrameworkCore;
5 | using System;
6 | using System.Linq;
7 | using System.Threading.Tasks;
8 |
9 | namespace Bloggy.Pages.Account.Posts
10 | {
11 | public class EditPostModel : PageModel
12 | {
13 | private readonly BloggingContext _db;
14 |
15 | public EditPostModel(BloggingContext db)
16 | {
17 | _db = db;
18 | }
19 |
20 | [BindProperty]
21 | public Post Post { get; set; }
22 |
23 | public IActionResult OnGet(int id)
24 | {
25 | Post = _db.Posts.SingleOrDefault(p => p.Id == id);
26 |
27 | if (Post != null)
28 | {
29 | return Page();
30 | }
31 |
32 | return RedirectToPage("/Error");
33 | }
34 |
35 | [ValidateAntiForgeryToken]
36 | public async Task OnPostAsync()
37 | {
38 | if (!ModelState.IsValid)
39 | {
40 | return Page();
41 | }
42 |
43 | if (Post.IsPublished)
44 | {
45 | Post.PublishedAt = DateTime.Now;
46 | }
47 | Post.LastModified = DateTime.Now;
48 | _db.Entry(Post).State = EntityState.Modified;
49 | await _db.SaveChangesAsync();
50 |
51 | return RedirectToPage("/Post", new { slug = Post.Slug});
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Bloggy
2 |
3 | ## Why another blog engine?
4 |
5 | > It's always time to start a new blog engine
6 |
7 | That's the retweet from Mads Kristensen @madskristensen after I tweeted him on 7 May 2016 to start a new blog engine using the latest and greates technologies. Unfortunately he don't have a time!! I'm sure that he is busy with building a new cool extensions for Visual Studio as usual :)
8 |
9 | I take the initiative to continue with what he built last few years in both BlogEngine & MiniBlog, so I decide to build a new blog engine called **bloggy** using the new ASP.NET Core.
10 |
11 | I'm sure this is a great lesson for me to use almost the things that I know and love in ASP.NET Core to build such application.
12 |
13 | ## Features
14 |
15 | - Performance
16 | - Response Compression
17 | - Response Caching
18 | - Support CDN for scripts & styles in Production Environment
19 | - Authentication
20 | - Cookie-based authentication
21 | - Social Media authentication
22 | - Configuration
23 | - Almost the blog settings are configurable in appsettings.json
24 | - Themable
25 | - Easy to create and customize the themes
26 | - The conjunction with localization make it easy to support different layouts LTR & RTL
27 | - Comes with light & black theme
28 | - Localization
29 | - Support both English & Arabic out of the box
30 | - Url Culture Provider
31 | - Schedule posts to be published on a future date
32 | - Comments
33 | - Gravatar support
34 | - reCaptcha support
35 | - Comment moderation
36 | - Live preview on commenting
37 | - Data Store
38 | - Shipped with InMemory (for Testing) & SQLite, but you can easily switch to SQL Server or any EntityFramework Database Provider
39 | - Web Feeds
40 | - RSS
41 | - ATOM
42 | - Markdown support as first class citizen
43 | - Mobile friendly
44 | - Works on Azure
--------------------------------------------------------------------------------
/src/Bloggy/Pages/Post.cshtml:
--------------------------------------------------------------------------------
1 | @page "{slug}"
2 |
3 | @model PostModel
4 |
5 | @{
6 | ViewBag.Title = Model.Post.Slug;
7 | }
8 |
9 |
10 |
11 | @Model.Post.PublishedAt.ToString("MMMM dd, yyyy")
12 | #@Model.Post.Tags
13 | @Model.Post.Content
14 |
15 |
16 |
38 |
39 | @if (Model.Blog.AllowComments && (DateTime.Now - Model.Post.PublishedAt).Days <= Model.Blog.DaysToComment)
40 | {
41 | if (Model.ShowErrorMessage)
42 | {
43 | @Model.ErrorMessage
44 | }
45 |
46 | if (Model.ShowSuccessMessage)
47 | {
48 | @Model.SuccessMessage
49 | }
50 | else
51 | {
52 |
53 | }
54 | }
--------------------------------------------------------------------------------
/src/Bloggy/Pages/Account/Posts/Create.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 |
3 | @model CreatePostModel
4 |
5 | @{
6 | ViewBag.Title = "Create Post";
7 | }
8 |
9 |
10 |
@ViewBag.Title
11 |
44 |
--------------------------------------------------------------------------------
/src/Bloggy/ThemeViewLocationExpander.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc.Razor;
2 | using Microsoft.Extensions.Options;
3 | using System;
4 | using System.Collections.Generic;
5 |
6 | namespace Bloggy
7 | {
8 | public class ThemeViewLocationExpander : IViewLocationExpander
9 | {
10 | private const string ValueKey = "theme";
11 |
12 | public IEnumerable ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable viewLocations)
13 | {
14 | if (context == null)
15 | {
16 | throw new ArgumentNullException(nameof(context));
17 | }
18 |
19 | if (viewLocations == null)
20 | {
21 | throw new ArgumentNullException(nameof(viewLocations));
22 | }
23 |
24 | context.Values.TryGetValue(ValueKey, out string theme);
25 |
26 | if (!string.IsNullOrEmpty(theme))
27 | {
28 | return ExpandViewLocationsCore(viewLocations, theme);
29 | }
30 |
31 | return viewLocations;
32 | }
33 |
34 | public void PopulateValues(ViewLocationExpanderContext context)
35 | {
36 | if (context == null)
37 | {
38 | throw new ArgumentNullException(nameof(context));
39 | }
40 |
41 | var appSettings = context.ActionContext.HttpContext.RequestServices
42 | .GetService(typeof(IOptions)) as IOptions;
43 |
44 | context.Values[ValueKey] = appSettings.Value.Blog.Theme;
45 | }
46 |
47 | private IEnumerable ExpandViewLocationsCore(IEnumerable viewLocations, string theme)
48 | {
49 | foreach (var location in viewLocations)
50 | {
51 | yield return location;
52 | yield return location.Replace("/Pages/", $"/Pages/Themes/{theme}/");
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Bloggy/Pages/Account/Posts/Edit.cshtml:
--------------------------------------------------------------------------------
1 | @page "{id:int}"
2 |
3 | @model EditPostModel
4 |
5 | @{
6 | ViewBag.Title = "Edit Post";
7 | }
8 |
9 |
10 |
@ViewBag.Title
11 |
45 |
--------------------------------------------------------------------------------
/src/Bloggy/Migrations/BloggingContextModelSnapshot.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.EntityFrameworkCore;
3 | using Microsoft.EntityFrameworkCore.Infrastructure;
4 | using Microsoft.EntityFrameworkCore.Metadata;
5 | using Microsoft.EntityFrameworkCore.Migrations;
6 | using Bloggy.Models;
7 |
8 | namespace Bloggy.Migrations
9 | {
10 | [DbContext(typeof(BloggingContext))]
11 | partial class BloggingContextModelSnapshot : ModelSnapshot
12 | {
13 | protected override void BuildModel(ModelBuilder modelBuilder)
14 | {
15 | modelBuilder
16 | .HasAnnotation("ProductVersion", "1.1.0-rtm-22752");
17 |
18 | modelBuilder.Entity("Bloggy.Models.Comment", b =>
19 | {
20 | b.Property("Id")
21 | .ValueGeneratedOnAdd();
22 |
23 | b.Property("Author")
24 | .IsRequired()
25 | .HasMaxLength(100);
26 |
27 | b.Property("Content")
28 | .IsRequired();
29 |
30 | b.Property("Email")
31 | .IsRequired();
32 |
33 | b.Property("PostId");
34 |
35 | b.Property("PublishedAt");
36 |
37 | b.Property("Website");
38 |
39 | b.HasKey("Id");
40 |
41 | b.HasIndex("PostId");
42 |
43 | b.ToTable("Comments");
44 | });
45 |
46 | modelBuilder.Entity("Bloggy.Models.Post", b =>
47 | {
48 | b.Property("Id")
49 | .ValueGeneratedOnAdd();
50 |
51 | b.Property("Content");
52 |
53 | b.Property("Excerpt");
54 |
55 | b.Property("IsPublished");
56 |
57 | b.Property("LastModified");
58 |
59 | b.Property("PublishedAt");
60 |
61 | b.Property("Slug");
62 |
63 | b.Property("Tags");
64 |
65 | b.Property("Title");
66 |
67 | b.HasKey("Id");
68 |
69 | b.ToTable("Posts");
70 | });
71 |
72 | modelBuilder.Entity("Bloggy.Models.Comment", b =>
73 | {
74 | b.HasOne("Bloggy.Models.Post", "Post")
75 | .WithMany("Comments")
76 | .HasForeignKey("PostId")
77 | .OnDelete(DeleteBehavior.Cascade);
78 | });
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Bloggy/Migrations/20161227000614_Initial.Designer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.EntityFrameworkCore;
3 | using Microsoft.EntityFrameworkCore.Infrastructure;
4 | using Microsoft.EntityFrameworkCore.Metadata;
5 | using Microsoft.EntityFrameworkCore.Migrations;
6 | using Bloggy.Models;
7 |
8 | namespace Bloggy.Migrations
9 | {
10 | [DbContext(typeof(BloggingContext))]
11 | [Migration("20161227000614_Initial")]
12 | partial class Initial
13 | {
14 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
15 | {
16 | modelBuilder
17 | .HasAnnotation("ProductVersion", "1.1.0-rtm-22752");
18 |
19 | modelBuilder.Entity("Bloggy.Models.Comment", b =>
20 | {
21 | b.Property("Id")
22 | .ValueGeneratedOnAdd();
23 |
24 | b.Property("Author")
25 | .IsRequired()
26 | .HasMaxLength(100);
27 |
28 | b.Property("Content")
29 | .IsRequired();
30 |
31 | b.Property("Email")
32 | .IsRequired();
33 |
34 | b.Property("PostId");
35 |
36 | b.Property("PublishedAt");
37 |
38 | b.Property("Website");
39 |
40 | b.HasKey("Id");
41 |
42 | b.HasIndex("PostId");
43 |
44 | b.ToTable("Comments");
45 | });
46 |
47 | modelBuilder.Entity("Bloggy.Models.Post", b =>
48 | {
49 | b.Property("Id")
50 | .ValueGeneratedOnAdd();
51 |
52 | b.Property("Content");
53 |
54 | b.Property("Excerpt");
55 |
56 | b.Property("IsPublished");
57 |
58 | b.Property("LastModified");
59 |
60 | b.Property("PublishedAt");
61 |
62 | b.Property("Slug");
63 |
64 | b.Property("Tags");
65 |
66 | b.Property("Title");
67 |
68 | b.HasKey("Id");
69 |
70 | b.ToTable("Posts");
71 | });
72 |
73 | modelBuilder.Entity("Bloggy.Models.Comment", b =>
74 | {
75 | b.HasOne("Bloggy.Models.Post", "Post")
76 | .WithMany("Comments")
77 | .HasForeignKey("PostId")
78 | .OnDelete(DeleteBehavior.Cascade);
79 | });
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/Bloggy/Pages/Post.cshtml.cs:
--------------------------------------------------------------------------------
1 | using Bloggy.Models;
2 | using Microsoft.AspNetCore.Mvc;
3 | using Microsoft.AspNetCore.Mvc.RazorPages;
4 | using Microsoft.AspNetCore.Mvc.ViewFeatures;
5 | using Microsoft.EntityFrameworkCore;
6 | using Microsoft.Extensions.Options;
7 | using System;
8 | using System.Linq;
9 | using System.Threading.Tasks;
10 |
11 | namespace Bloggy.Pages
12 | {
13 | public class PostModel : PageModel
14 | {
15 | private readonly BloggingContext _db;
16 | private Blog _blog;
17 |
18 | public PostModel(BloggingContext db, IOptions appSettings)
19 | {
20 | _db = db;
21 | _blog = appSettings.Value.Blog;
22 | }
23 |
24 | public Post Post { get; private set; }
25 |
26 | public Blog Blog => _blog;
27 |
28 | [BindProperty]
29 | public Comment Comment { get; set; }
30 |
31 | [TempData]
32 | public string SuccessMessage { get; set; }
33 |
34 | public bool ShowSuccessMessage => !string.IsNullOrEmpty(SuccessMessage);
35 |
36 | [TempData]
37 | public string ErrorMessage { get; set; }
38 |
39 | public bool ShowErrorMessage => !string.IsNullOrEmpty(ErrorMessage);
40 |
41 | public IActionResult OnGet(string slug)
42 | {
43 | Post = _db.Posts.Include(p => p.Comments)
44 | .SingleOrDefault(p => p.Slug.Equals(slug, StringComparison.OrdinalIgnoreCase));
45 |
46 | if (Post != null)
47 | {
48 | return Page();
49 | }
50 |
51 | return RedirectToPage("/Error");
52 | }
53 |
54 | public async Task OnPostAddCommentAsync(string slug)
55 | {
56 | Post = _db.Posts.Include(p => p.Comments)
57 | .Single(p => p.Slug.Equals(slug, StringComparison.OrdinalIgnoreCase));
58 |
59 | if (!ModelState.IsValid)
60 | {
61 | ErrorMessage = "Unable to add comment";
62 | return Page();
63 | }
64 |
65 | Comment.PostId = Post.Id;
66 | Comment.PublishedAt = DateTime.UtcNow;
67 | Comment.Content = Comment.Content.Replace(Environment.NewLine, " ");
68 | _db.Comments.Add(Comment);
69 | await _db.SaveChangesAsync();
70 | SuccessMessage = "Your comment has been added";
71 |
72 | return RedirectToPage();
73 | }
74 |
75 | public async Task OnPostDeleteCommentAsync(int id)
76 | {
77 | var comment = _db.Comments.Include(c => c.Post)
78 | .SingleOrDefault(c => c.Id == id);
79 |
80 | if (comment != null)
81 | {
82 | _db.Comments.Remove(comment);
83 | await _db.SaveChangesAsync();
84 | }
85 |
86 | return RedirectToPage();
87 | }
88 | }
89 | }
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Make sh files under the build directory always have LF as line endings
8 | ###############################################################################
9 | build/*.sh eol=lf
10 |
11 |
12 | ###############################################################################
13 | # Set default behavior for command prompt diff.
14 | #
15 | # This is need for earlier builds of msysgit that does not have it on by
16 | # default for csharp files.
17 | # Note: This is only used by command line
18 | ###############################################################################
19 | #*.cs diff=csharp
20 |
21 | ###############################################################################
22 | # Set the merge driver for project and solution files
23 | #
24 | # Merging from the command prompt will add diff markers to the files if there
25 | # are conflicts (Merging from VS is not affected by the settings below, in VS
26 | # the diff markers are never inserted). Diff markers may cause the following
27 | # file extensions to fail to load in VS. An alternative would be to treat
28 | # these files as binary and thus will always conflict and require user
29 | # intervention with every merge. To do so, just uncomment the entries below
30 | ###############################################################################
31 | #*.sln merge=binary
32 | #*.csproj merge=binary
33 | #*.vbproj merge=binary
34 | #*.vcxproj merge=binary
35 | #*.vcproj merge=binary
36 | #*.dbproj merge=binary
37 | #*.fsproj merge=binary
38 | #*.lsproj merge=binary
39 | #*.wixproj merge=binary
40 | #*.modelproj merge=binary
41 | #*.sqlproj merge=binary
42 | #*.wwaproj merge=binary
43 |
44 | ###############################################################################
45 | # behavior for image files
46 | #
47 | # image files are treated as binary by default.
48 | ###############################################################################
49 | #*.jpg binary
50 | #*.png binary
51 | #*.gif binary
52 |
53 | ###############################################################################
54 | # diff behavior for common document formats
55 | #
56 | # Convert binary document formats to text before diffing them. This feature
57 | # is only available from the command line. Turn it on by uncommenting the
58 | # entries below.
59 | ###############################################################################
60 | #*.doc diff=astextplain
61 | #*.DOC diff=astextplain
62 | #*.docx diff=astextplain
63 | #*.DOCX diff=astextplain
64 | #*.dot diff=astextplain
65 | #*.DOT diff=astextplain
66 | #*.pdf diff=astextplain
67 | #*.PDF diff=astextplain
68 | #*.rtf diff=astextplain
69 | #*.RTF diff=astextplain
70 |
--------------------------------------------------------------------------------
/src/Bloggy/Pages/Themes/Dark/_Layout.cshtml:
--------------------------------------------------------------------------------
1 | @using Bloggy
2 | @using Bloggy.Models
3 | @using Microsoft.Extensions.Options
4 |
5 | @inject IOptions AppSettings
6 |
7 | @{
8 | var activePage = ViewContext.RouteData.Values["page"].ToString();
9 | }
10 |
11 | @functions
12 | {
13 | public Blog Blog
14 | {
15 | get
16 | {
17 | return AppSettings.Value.Blog;
18 | }
19 | }
20 | }
21 |
22 |
23 |
24 |
25 |
26 |
27 | @Blog.Name - @ViewBag.Title
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
58 |
59 | @RenderBody()
60 |
61 |
64 |
65 |
71 |
72 |
--------------------------------------------------------------------------------
/src/Bloggy/Pages/Themes/Light/_Layout.cshtml:
--------------------------------------------------------------------------------
1 | @using Bloggy
2 | @using Bloggy.Models
3 | @using Microsoft.Extensions.Options
4 |
5 | @inject IOptions AppSettings
6 |
7 | @{
8 | var activePage = ViewContext.RouteData.Values["page"].ToString();
9 | }
10 |
11 | @functions
12 | {
13 | public Blog Blog
14 | {
15 | get
16 | {
17 | return AppSettings.Value.Blog;
18 | }
19 | }
20 | }
21 |
22 |
23 |
24 |
25 |
26 |
27 | @Blog.Name - @ViewBag.Title
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
58 |
59 | @RenderBody()
60 |
61 |
64 |
65 |
71 |
72 |
--------------------------------------------------------------------------------
/src/Bloggy/Migrations/20161227000614_Initial.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Microsoft.EntityFrameworkCore.Migrations;
4 |
5 | namespace Bloggy.Migrations
6 | {
7 | public partial class Initial : Migration
8 | {
9 | protected override void Up(MigrationBuilder migrationBuilder)
10 | {
11 | migrationBuilder.CreateTable(
12 | name: "Posts",
13 | columns: table => new
14 | {
15 | Id = table.Column(nullable: false)
16 | .Annotation("Sqlite:Autoincrement", true),
17 | Content = table.Column(nullable: true),
18 | Excerpt = table.Column(nullable: true),
19 | IsPublished = table.Column(nullable: false),
20 | LastModified = table.Column(nullable: false),
21 | PublishedAt = table.Column(nullable: true),
22 | Slug = table.Column(nullable: true),
23 | Tags = table.Column(nullable: true),
24 | Title = table.Column(nullable: true)
25 | },
26 | constraints: table =>
27 | {
28 | table.PrimaryKey("PK_Posts", x => x.Id);
29 | });
30 |
31 | migrationBuilder.CreateTable(
32 | name: "Comments",
33 | columns: table => new
34 | {
35 | Id = table.Column(nullable: false)
36 | .Annotation("Sqlite:Autoincrement", true),
37 | Author = table.Column(maxLength: 100, nullable: false),
38 | Content = table.Column(nullable: false),
39 | Email = table.Column(nullable: false),
40 | PostId = table.Column(nullable: false),
41 | PublishedAt = table.Column(nullable: false),
42 | Website = table.Column(nullable: true)
43 | },
44 | constraints: table =>
45 | {
46 | table.PrimaryKey("PK_Comments", x => x.Id);
47 | table.ForeignKey(
48 | name: "FK_Comments_Posts_PostId",
49 | column: x => x.PostId,
50 | principalTable: "Posts",
51 | principalColumn: "Id",
52 | onDelete: ReferentialAction.Cascade);
53 | });
54 |
55 | migrationBuilder.CreateIndex(
56 | name: "IX_Comments_PostId",
57 | table: "Comments",
58 | column: "PostId");
59 | }
60 |
61 | protected override void Down(MigrationBuilder migrationBuilder)
62 | {
63 | migrationBuilder.DropTable(
64 | name: "Comments");
65 |
66 | migrationBuilder.DropTable(
67 | name: "Posts");
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Bloggy/Pages/Account/Login.cshtml.cs:
--------------------------------------------------------------------------------
1 | using Bloggy.Models;
2 | using Microsoft.AspNetCore.Authentication;
3 | using Microsoft.AspNetCore.Authentication.Cookies;
4 | using Microsoft.AspNetCore.Mvc;
5 | using Microsoft.AspNetCore.Mvc.RazorPages;
6 | using Microsoft.Extensions.Options;
7 | using System;
8 | using System.Security.Claims;
9 | using System.Security.Cryptography;
10 | using System.Text;
11 | using System.Threading.Tasks;
12 |
13 | namespace Bloggy.Pages.Account
14 | {
15 | public class LoginModel : PageModel
16 | {
17 | private Credential _credential;
18 |
19 | public LoginModel(IOptions appSettings)
20 | {
21 | _credential = appSettings.Value.Credential;
22 | }
23 |
24 | [BindProperty]
25 | public Bloggy.Models.User AdminUser { get; set; }
26 |
27 | public IActionResult OnGet(string returnUrl = null)
28 | {
29 | ViewData["ReturnUrl"] = returnUrl;
30 | return Page();
31 | }
32 |
33 | [ValidateAntiForgeryToken]
34 | public async Task OnPost(string returnUrl = null)
35 | {
36 | ViewData["ReturnUrl"] = returnUrl;
37 | if (ModelState.IsValid)
38 | {
39 | if (_credential.PasswordFormat != PasswordFormat.SHA1)
40 | {
41 | throw new NotSupportedException();
42 | }
43 |
44 | if (AdminUser.Name == _credential.User.Name && GetHashedPassword(AdminUser.Password).Equals(_credential.User.Password, StringComparison.OrdinalIgnoreCase))
45 | {
46 | var adminUser = new ClaimsPrincipal(
47 | new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, _credential.User.Name) },
48 | CookieAuthenticationDefaults.AuthenticationScheme));
49 | await PageContext.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, adminUser);
50 |
51 | if (string.IsNullOrEmpty(returnUrl))
52 | {
53 | return RedirectToPage("/Index");
54 | }
55 | else
56 | {
57 | return LocalRedirect(returnUrl);
58 | }
59 | }
60 | else
61 | {
62 | ModelState.AddModelError(string.Empty, "Invalid login attempt.");
63 | return Page();
64 | }
65 | }
66 |
67 | return Page();
68 | }
69 |
70 | private string GetHashedPassword(string password)
71 | {
72 | var bytes = Encoding.UTF8.GetBytes(password);
73 | var hashBytes = SHA1.Create().ComputeHash(bytes);
74 |
75 | return BitConverter.ToString(hashBytes).Replace("-", string.Empty);
76 | }
77 |
78 | //private IActionResult RedirectToLocal(string returnUrl)
79 | //{
80 | // if (Url.IsLocalUrl(returnUrl))
81 | // {
82 | // return LocalRedirect(returnUrl);
83 | // }
84 | // else
85 | // {
86 | // return RedirectToPage("/Index");
87 | // }
88 | //}
89 | }
90 | }
--------------------------------------------------------------------------------
/src/Bloggy/Startup.cs:
--------------------------------------------------------------------------------
1 | using Bloggy.Models;
2 | using Microsoft.AspNetCore.Authentication.Cookies;
3 | using Microsoft.AspNetCore.Builder;
4 | using Microsoft.AspNetCore.Hosting;
5 | using Microsoft.AspNetCore.Mvc.Razor;
6 | using Microsoft.AspNetCore.Mvc.ViewFeatures;
7 | using Microsoft.AspNetCore.ResponseCompression;
8 | using Microsoft.EntityFrameworkCore;
9 | using Microsoft.Extensions.Configuration;
10 | using Microsoft.Extensions.DependencyInjection;
11 | using Microsoft.Extensions.FileProviders;
12 | using Microsoft.Extensions.Logging;
13 | using Microsoft.Extensions.Options;
14 | using System;
15 | using System.Collections.Generic;
16 | using System.IO;
17 | using System.IO.Compression;
18 |
19 | namespace Bloggy
20 | {
21 | public class Startup
22 | {
23 | public Startup(IHostingEnvironment env)
24 | {
25 | Environment = env;
26 | var builder = new ConfigurationBuilder()
27 | .SetBasePath(Environment.ContentRootPath)
28 | .AddJsonFile("appsettings.json");
29 |
30 | Configuration = builder.Build();
31 | }
32 |
33 | public IConfigurationRoot Configuration { get; set; }
34 |
35 | public IHostingEnvironment Environment { get; set; }
36 |
37 | public BloggingContext Db { get; set; }
38 |
39 | public void ConfigureServices(IServiceCollection services)
40 | {
41 | if (Environment.IsDevelopment())
42 | {
43 | services.AddDbContext(options => options.UseInMemoryDatabase("bloggy"));
44 | }
45 | else
46 | {
47 | services.AddDbContext(options => options.UseSqlite(Configuration["Data:DefaultConnection:ConnectionString"]));
48 | }
49 |
50 | services.Configure(options => options.Level = CompressionLevel.Fastest);
51 | services.AddResponseCompression(options =>
52 | {
53 | options.Providers.Add();
54 | });
55 |
56 | services.Configure(options => Configuration.GetSection("AppSettings").Bind(options));
57 |
58 | services.AddAuthentication(options =>
59 | {
60 | options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
61 | options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
62 | });
63 |
64 | services.AddAuthentication(options =>
65 | {
66 | options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
67 | options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
68 | }).AddCookie();
69 |
70 | services.AddMvc()
71 | .AddRazorPagesOptions(options =>
72 | {
73 | options.Conventions.AuthorizeFolder("/Account");
74 | options.Conventions.AllowAnonymousToPage("/Account/Login");
75 | });
76 |
77 | services.Configure(options =>
78 | {
79 | options.ViewLocationExpanders.Add(new ThemeViewLocationExpander());
80 | });
81 |
82 | services.AddSingleton();
83 | }
84 |
85 | public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, BloggingContext db)
86 | {
87 | if (Environment.IsDevelopment())
88 | {
89 | Db = db;
90 | loggerFactory.AddConsole(Configuration.GetSection("Logging"));
91 | app.UseDeveloperExceptionPage();
92 | app.UseDatabaseErrorPage();
93 | AddSeedData();
94 | }
95 | else
96 | {
97 | app.UseExceptionHandler("/error");
98 | }
99 |
100 | app.UseAuthentication();
101 |
102 | app.UseResponseCompression();
103 |
104 | app.UseStaticFiles();
105 |
106 | var appSettings = app.ApplicationServices.GetRequiredService>();
107 |
108 | app.UseStaticFiles(new StaticFileOptions()
109 | {
110 | FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(),
111 | $@"Pages/Themes/{appSettings.Value.Blog.Theme}")),
112 | RequestPath = string.Empty
113 | });
114 |
115 | app.UseMvcWithDefaultRoute();
116 | }
117 |
118 | private void AddSeedData()
119 | {
120 | for (int i = 1; i <= 10; i++)
121 | {
122 | var post = new Post
123 | {
124 | Title = "What is Lorem Ipsum?",
125 | Slug = "what-is-lorem-ipsum-" + i,
126 | Excerpt = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s",
127 | Content = @"**Lorem Ipsum** is simply dummy text of the printing and typesetting industry. **Lorem Ipsum** has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing **Lorem Ipsum** passages, and more recently with desktop publishing software like Aldus PageMaker including versions of **Lorem Ipsum**.",
128 | IsPublished = true,
129 | PublishedAt = new DateTime(2016, 12, 2),
130 | Tags = "Lorem Ipsum,lorem,ipsum"
131 | };
132 | post.Comments = new List();
133 |
134 | for (int j = 1; j < 3; j++)
135 | {
136 | var comment = new Comment()
137 | {
138 | Author = "Lorem Ipsum",
139 | Email = "lorem@ipsum.com",
140 | Content = "Lorem Ipsum is simply dummy text of the printing and typesetting industry.",
141 | Website = "http://www.lipsum.com",
142 | PublishedAt = new DateTime(2016, 12, 14),
143 | PostId = i,
144 | Post = post
145 | };
146 | post.Comments.Add(comment);
147 | }
148 | Db.Posts.Add(post);
149 | }
150 |
151 | Db.SaveChanges();
152 | }
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/src/Bloggy/Pages/Themes/Light/styles/bloggy.css:
--------------------------------------------------------------------------------
1 | /* Thin */
2 | @font-face {
3 | font-family: "Roboto";
4 | font-style: normal;
5 | font-weight: 100;
6 | src: url('../fonts/Roboto-Thin.ttf') format('truetype');
7 | }
8 |
9 | @font-face {
10 | font-family: "Roboto";
11 | font-style: italic;
12 | font-weight: 100;
13 | src: url('../fonts/Roboto-ThinItalic.ttf') format('truetype');
14 | }
15 |
16 | /* Light */
17 | @font-face {
18 | font-family: "Roboto";
19 | font-style: normal;
20 | font-weight: 300;
21 | src: url('../fonts/Roboto-Light.ttf') format('truetype');
22 | }
23 |
24 | @font-face {
25 | font-family: "Roboto";
26 | font-style: italic;
27 | font-weight: 300;
28 | src: url('../fonts/Roboto-LightItalic.ttf') format('truetype');
29 | }
30 |
31 | /* Normal */
32 | @font-face {
33 | font-family: "Roboto";
34 | font-style: normal;
35 | font-weight: 400;
36 | src: url('../fonts/Roboto-Regular.ttf') format('truetype');
37 | }
38 |
39 | @font-face {
40 | font-family: "Roboto";
41 | font-style: italic;
42 | font-weight: 400;
43 | src: url('../fonts/Roboto-Italic.ttf') format('truetype');
44 | }
45 |
46 | /* Medium */
47 | @font-face {
48 | font-family: "Roboto";
49 | font-style: normal;
50 | font-weight: 500;
51 | src: url('../fonts/Roboto-Medium.ttf') format('truetype');
52 | }
53 |
54 | @font-face {
55 | font-family: "Roboto";
56 | font-style: italic;
57 | font-weight: 500;
58 | src: url('../fonts/Roboto-MediumItalic.ttf') format('truetype');
59 | }
60 |
61 | /* Bold */
62 | @font-face {
63 | font-family: "Roboto";
64 | font-style: normal;
65 | font-weight: 700;
66 | src: url('../fonts/Roboto-Bold.ttf') format('truetype');
67 | }
68 |
69 | @font-face {
70 | font-family: "Roboto";
71 | font-style: italic;
72 | font-weight: 700;
73 | src: url('../fonts/Roboto-BoldItalic.ttf') format('truetype');
74 | }
75 |
76 | /* Black */
77 | @font-face {
78 | font-family: "Roboto";
79 | font-style: normal;
80 | font-weight: 900;
81 | src: url('../fonts/Roboto-Black.ttf') format('truetype');
82 | }
83 |
84 | @font-face {
85 | font-family: "Roboto";
86 | font-style: italic;
87 | font-weight: 900;
88 | src: url('../fonts/Roboto-BlackItalic.ttf') format('truetype');
89 | }
90 |
91 | body {
92 | background-color: #F5F5F5;
93 | font-family: 'Roboto', sans-serif;
94 | }
95 |
96 | .container {
97 | width: 670px;
98 | }
99 |
100 | header {
101 | margin: 30px auto;
102 | text-align: center;
103 | }
104 |
105 | header h1 {
106 | font-size: 60px;
107 | font-weight: lighter;
108 | }
109 |
110 | header nav {
111 | margin: auto;
112 | text-align: center;
113 | margin-top: 30px;
114 | margin-bottom: 10px;
115 | }
116 |
117 | header nav a {
118 | color: #999;
119 | text-transform: uppercase;
120 | text-decoration: none;
121 | font-size: 18px;
122 | margin-right: 15px;
123 | }
124 |
125 | header nav a.active, a:hover {
126 | color: #111;
127 | text-decoration: none;
128 | }
129 |
130 | header a {
131 | color: #1BA0FF;
132 | text-decoration: none;
133 | }
134 |
135 | header a:hover {
136 | color: #111;
137 | text-decoration: none;
138 | }
139 |
140 | article {
141 | margin-bottom: 30px;
142 | }
143 |
144 | article a {
145 | color: #111;
146 | font-size: 28px;
147 | font-weight: normal;
148 | outline: none;
149 | }
150 |
151 | article a:hover {
152 | color: #1BA0FF;
153 | text-decoration: none;
154 | }
155 |
156 | article time {
157 | color: #999;
158 | }
159 |
160 | article span#tags > a {
161 | color: #1BA0FF;
162 | font-size: 16px;
163 | }
164 |
165 | article span#tags > a:hover {
166 | text-decoration: underline;
167 | }
168 |
169 | article p, time {
170 | font-size: 16px;
171 | line-height: 24px;
172 | }
173 |
174 | #comments {
175 | margin-left: -15px;
176 | }
177 |
178 | .comment {
179 | margin-top: 20px;
180 | padding-bottom: 20px;
181 | border-bottom: 1px solid #ddd;
182 | }
183 |
184 | .comment:nth-last-child(1) {
185 | border-bottom: none;
186 | }
187 |
188 | .comment img {
189 | float: left;
190 | width: 100px;
191 | padding-right: 15px;
192 | }
193 |
194 | .comment div {
195 | overflow: auto;
196 | }
197 |
198 | .comment a {
199 | color: #111;
200 | font-size: 20px;
201 | font-weight: normal;
202 | outline: none;
203 | }
204 |
205 | .comment a:hover {
206 | color: #1BA0FF;
207 | text-decoration: none;
208 | }
209 |
210 | .comment time {
211 | color: #999;
212 | display: block;
213 | }
214 |
215 | .comment p, time {
216 | font-size: 16px;
217 | line-height: 24px;
218 | }
219 |
220 | #commentForm, .alert-success, .alert-error {
221 | margin-left: -15px;
222 | margin-top: 20px;
223 | }
224 |
225 | #commentForm ul {
226 | margin-left: -25px;
227 | }
228 |
229 | #commentForm button[type=submit] {
230 | background-color: #1BA0FF;
231 | color: #fff;
232 | }
233 |
234 | #archive span.label, #tags span.label {
235 | background-color: #999;
236 | }
237 |
238 | #archive span.label:hover, #tags span.label:hover {
239 | background-color: #1BA0FF;
240 | }
241 |
242 | #archive h3, #tags h3 {
243 | color: #1BA0FF;
244 | }
245 |
246 | #archive li > a, #tags li > a {
247 | font-size: 16px;
248 | color: #111;
249 | text-decoration: underline;
250 | }
251 |
252 | #archive li > a:hover, #tags li > a:hover {
253 | color: #1BA0FF;
254 | }
255 |
256 | #contact a {
257 | color: #1BA0FF;
258 | text-decoration: none;
259 | font-size: 14px;
260 | }
261 |
262 | #contact a:hover {
263 | color: #111;
264 | }
265 |
266 | #login button[type=submit] {
267 | background-color: #1BA0FF;
268 | color: #fff;
269 | }
270 |
271 | #new-post {
272 | margin-left: -15px;
273 | }
274 |
275 | .actions {
276 | color: #000;
277 | }
278 |
279 | .actions > a {
280 | font-size: 14px;
281 | color: #1BA0FF;
282 | }
283 |
284 | .actions > a:hover {
285 | font-size: 14px;
286 | color: #1BA0FF;
287 | text-decoration: underline;
288 | }
289 |
290 | .actions > button {
291 | background: none;
292 | border: none;
293 | padding: 0;
294 | color: #1BA0FF;
295 | cursor: pointer;
296 | }
297 |
298 | .actions > button:hover {
299 | background: none;
300 | border: none;
301 | padding: 0;
302 | color: #1BA0FF;
303 | cursor: pointer;
304 | text-decoration: underline;
305 | }
306 |
307 | footer {
308 | text-align: center;
309 | color: #999;
310 | margin-bottom: 30px;
311 | }
312 |
313 | footer a {
314 | color: #111;
315 | text-decoration: none;
316 | font-size: 14px;
317 | }
318 |
319 | footer a:hover {
320 | color: #1BA0FF;
321 | }
322 |
--------------------------------------------------------------------------------
/src/Bloggy/Pages/Themes/Dark/styles/bloggy.css:
--------------------------------------------------------------------------------
1 | /* Thin */
2 | @font-face {
3 | font-family: "Roboto";
4 | font-style: normal;
5 | font-weight: 100;
6 | src: url('../fonts/Roboto-Thin.ttf') format('truetype');
7 | }
8 |
9 | @font-face {
10 | font-family: "Roboto";
11 | font-style: italic;
12 | font-weight: 100;
13 | src: url('../fonts/Roboto-ThinItalic.ttf') format('truetype');
14 | }
15 |
16 | /* Light */
17 | @font-face {
18 | font-family: "Roboto";
19 | font-style: normal;
20 | font-weight: 300;
21 | src: url('../fonts/Roboto-Light.ttf') format('truetype');
22 | }
23 |
24 | @font-face {
25 | font-family: "Roboto";
26 | font-style: italic;
27 | font-weight: 300;
28 | src: url('../fonts/Roboto-LightItalic.ttf') format('truetype');
29 | }
30 |
31 | /* Normal */
32 | @font-face {
33 | font-family: "Roboto";
34 | font-style: normal;
35 | font-weight: 400;
36 | src: url('../fonts/Roboto-Regular.ttf') format('truetype');
37 | }
38 |
39 | @font-face {
40 | font-family: "Roboto";
41 | font-style: italic;
42 | font-weight: 400;
43 | src: url('../fonts/Roboto-Italic.ttf') format('truetype');
44 | }
45 |
46 | /* Medium */
47 | @font-face {
48 | font-family: "Roboto";
49 | font-style: normal;
50 | font-weight: 500;
51 | src: url('../fonts/Roboto-Medium.ttf') format('truetype');
52 | }
53 |
54 | @font-face {
55 | font-family: "Roboto";
56 | font-style: italic;
57 | font-weight: 500;
58 | src: url('../fonts/Roboto-MediumItalic.ttf') format('truetype');
59 | }
60 |
61 | /* Bold */
62 | @font-face {
63 | font-family: "Roboto";
64 | font-style: normal;
65 | font-weight: 700;
66 | src: url('../fonts/Roboto-Bold.ttf') format('truetype');
67 | }
68 |
69 | @font-face {
70 | font-family: "Roboto";
71 | font-style: italic;
72 | font-weight: 700;
73 | src: url('../fonts/Roboto-BoldItalic.ttf') format('truetype');
74 | }
75 |
76 | /* Black */
77 | @font-face {
78 | font-family: "Roboto";
79 | font-style: normal;
80 | font-weight: 900;
81 | src: url('../fonts/Roboto-Black.ttf') format('truetype');
82 | }
83 |
84 | @font-face {
85 | font-family: "Roboto";
86 | font-style: italic;
87 | font-weight: 900;
88 | src: url('../fonts/Roboto-BlackItalic.ttf') format('truetype');
89 | }
90 |
91 | body {
92 | background-color: #1f1f1f;
93 | font-family: 'Roboto', sans-serif;
94 | }
95 |
96 | .container {
97 | width: 670px;
98 | }
99 |
100 |
101 | header {
102 | margin: 30px auto;
103 | text-align: center;
104 | }
105 |
106 | header h1 {
107 | font-size: 60px;
108 | font-weight: lighter;
109 | color:#fff;
110 | }
111 |
112 | header nav {
113 | margin: auto;
114 | text-align: center;
115 | margin-top: 30px;
116 | margin-bottom: 10px;
117 | }
118 |
119 | header nav a {
120 | color: #fff;
121 | text-transform: uppercase;
122 | text-decoration: none;
123 | font-size: 18px;
124 | margin-right: 15px;
125 | }
126 |
127 | header nav a.active, a:hover {
128 | color: #1ba0ff;
129 | text-decoration: none;
130 | }
131 |
132 | header a {
133 | color: #1BA0FF;
134 | text-decoration: none;
135 | }
136 |
137 | header a:hover {
138 | color: #1ba0ff;
139 | text-decoration: none;
140 | }
141 |
142 | article {
143 | margin-bottom: 30px;
144 | }
145 |
146 | article a {
147 | color: #1ba0ff;
148 | font-size: 28px;
149 | font-weight: normal;
150 | outline: none;
151 | }
152 |
153 | article a:hover {
154 | color: #1BA0FF;
155 | text-decoration: underline;
156 | }
157 |
158 | article time {
159 | color: #999;
160 | }
161 |
162 | article span#tags > a {
163 | color: #1BA0FF;
164 | font-size: 16px;
165 | }
166 |
167 | article span#tags > a:hover {
168 | text-decoration: underline;
169 | }
170 |
171 | article p, time {
172 | color: #fff;
173 | font-size: 16px;
174 | line-height: 24px;
175 | }
176 |
177 | #comments {
178 | margin-left: -15px;
179 | }
180 |
181 | #comments h3 {
182 | color: #999;
183 | }
184 |
185 | .comment {
186 | margin-top: 20px;
187 | padding-bottom: 20px;
188 | border-bottom: 1px solid #ddd;
189 | }
190 |
191 | .comment:nth-last-child(1) {
192 | border-bottom: none;
193 | }
194 |
195 | .comment img {
196 | float: left;
197 | width: 100px;
198 | padding-right: 15px;
199 | }
200 |
201 | .comment div {
202 | overflow: auto;
203 | }
204 |
205 | .comment a {
206 | color: #1BA0FF;
207 | font-size: 20px;
208 | font-weight: normal;
209 | outline: none;
210 | }
211 |
212 | .comment a:hover {
213 | color: #1BA0FF;
214 | text-decoration: underline;
215 | }
216 |
217 | .comment time {
218 | color: #999;
219 | display: block;
220 | }
221 |
222 | .comment p, time {
223 | color: #fff;
224 | font-size: 16px;
225 | line-height: 24px;
226 | }
227 |
228 | #commentForm, .alert-success, .alert-error {
229 | margin-left: -15px;
230 | margin-top: 20px;
231 | }
232 |
233 | #commentForm label {
234 | color: #fff;
235 | }
236 |
237 | #commentForm span {
238 | color: #999;
239 | }
240 |
241 | #commentForm a {
242 | color: #1ba0ff !important;
243 | }
244 |
245 | #commentForm ul {
246 | margin-left: -25px;
247 | }
248 |
249 | #commentForm button[type=submit] {
250 | background-color: #1BA0FF;
251 | color: #fff;
252 | }
253 |
254 | #archive span.label, #tags span.label {
255 | background-color: #1BA0FF;
256 | }
257 |
258 | #archive span.label:hover, #tags span.label:hover {
259 | background-color: #999;
260 | }
261 |
262 | #archive h3, #tags h3 {
263 | color: #999;
264 | }
265 |
266 | #archive li > a, #tags li > a {
267 | font-size: 16px;
268 | color: #1BA0FF;
269 | text-decoration: underline;
270 | }
271 |
272 | #archive li > a:hover, #tags li > a:hover {
273 | color: #fff;
274 | }
275 |
276 | #contact h2, #about h2, #login h2 {
277 | color: #999;
278 | }
279 |
280 | #contact p, #about p {
281 | color: #fff;
282 | }
283 |
284 | #contact a {
285 | color: #1BA0FF;
286 | text-decoration: none;
287 | font-size: 14px;
288 | }
289 |
290 | #contact a:hover {
291 | color: #111;
292 | }
293 |
294 | #login label {
295 | color: #fff;
296 | }
297 |
298 | #login button[type=submit] {
299 | background-color: #1BA0FF;
300 | color: #fff;
301 | }
302 |
303 | #new-post {
304 | margin-left: -15px;
305 | }
306 |
307 | .actions {
308 | color : #fff;
309 | }
310 |
311 | .actions > a {
312 | font-size: 14px;
313 | color: #1BA0FF;
314 | }
315 |
316 | .actions > a:hover {
317 | font-size: 14px;
318 | color: #1BA0FF;
319 | text-decoration: underline;
320 | }
321 |
322 | .actions > button {
323 | background: none;
324 | border: none;
325 | padding: 0;
326 | color: #1BA0FF;
327 | cursor: pointer;
328 | }
329 |
330 | .actions > button:hover {
331 | background: none;
332 | border: none;
333 | padding: 0;
334 | color: #1BA0FF;
335 | cursor: pointer;
336 | text-decoration: underline;
337 | }
338 |
339 | footer {
340 | text-align: center;
341 | color: #999;
342 | margin-bottom: 30px;
343 | }
344 |
345 | footer a {
346 | color: #1BA0FF;
347 | text-decoration: none;
348 | font-size: 14px;
349 | }
350 |
351 | footer a:hover {
352 | color: #111;
353 | }
354 |
--------------------------------------------------------------------------------
/src/Bloggy/wwwroot/styles/simplemde.css:
--------------------------------------------------------------------------------
1 | .CodeMirror {
2 | height: auto;
3 | min-height: 300px;
4 | border: 1px solid #ddd;
5 | border-bottom-left-radius: 4px;
6 | border-bottom-right-radius: 4px;
7 | padding: 10px;
8 | font: inherit;
9 | z-index: 1;
10 | }
11 |
12 | .CodeMirror-scroll {
13 | min-height: 300px
14 | }
15 |
16 | .CodeMirror-fullscreen {
17 | background: #fff;
18 | position: fixed !important;
19 | top: 50px;
20 | left: 0;
21 | right: 0;
22 | bottom: 0;
23 | height: auto;
24 | z-index: 9;
25 | }
26 |
27 | .CodeMirror-sided {
28 | width: 50% !important;
29 | }
30 |
31 | .editor-toolbar {
32 | position: relative;
33 | opacity: .6;
34 | -webkit-user-select: none;
35 | -moz-user-select: none;
36 | -ms-user-select: none;
37 | -o-user-select: none;
38 | user-select: none;
39 | padding: 0 10px;
40 | border-top: 1px solid #bbb;
41 | border-left: 1px solid #bbb;
42 | border-right: 1px solid #bbb;
43 | border-top-left-radius: 4px;
44 | border-top-right-radius: 4px;
45 | }
46 |
47 | .editor-toolbar:after,
48 | .editor-toolbar:before {
49 | display: block;
50 | content: ' ';
51 | height: 1px;
52 | }
53 |
54 | .editor-toolbar:before {
55 | margin-bottom: 8px
56 | }
57 |
58 | .editor-toolbar:after {
59 | margin-top: 8px
60 | }
61 |
62 | .editor-toolbar:hover,
63 | .editor-wrapper input.title:focus,
64 | .editor-wrapper input.title:hover {
65 | opacity: .8
66 | }
67 |
68 | .editor-toolbar.fullscreen {
69 | width: 100%;
70 | height: 50px;
71 | overflow-x: auto;
72 | overflow-y: hidden;
73 | white-space: nowrap;
74 | padding-top: 10px;
75 | padding-bottom: 10px;
76 | box-sizing: border-box;
77 | background: #fff;
78 | border: 0;
79 | position: fixed;
80 | top: 0;
81 | left: 0;
82 | opacity: 1;
83 | z-index: 9;
84 | }
85 |
86 | .editor-toolbar.fullscreen::before {
87 | width: 20px;
88 | height: 50px;
89 | background: -moz-linear-gradient(left, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
90 | background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 1)), color-stop(100%, rgba(255, 255, 255, 0)));
91 | background: -webkit-linear-gradient(left, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
92 | background: -o-linear-gradient(left, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
93 | background: -ms-linear-gradient(left, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
94 | background: linear-gradient(to right, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
95 | position: fixed;
96 | top: 0;
97 | left: 0;
98 | margin: 0;
99 | padding: 0;
100 | }
101 |
102 | .editor-toolbar.fullscreen::after {
103 | width: 20px;
104 | height: 50px;
105 | background: -moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%);
106 | background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(100%, rgba(255, 255, 255, 1)));
107 | background: -webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%);
108 | background: -o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%);
109 | background: -ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%);
110 | background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%);
111 | position: fixed;
112 | top: 0;
113 | right: 0;
114 | margin: 0;
115 | padding: 0;
116 | }
117 |
118 | .editor-toolbar a {
119 | display: inline-block;
120 | text-align: center;
121 | text-decoration: none!important;
122 | color: #2c3e50!important;
123 | width: 30px;
124 | height: 30px;
125 | margin: 0;
126 | border: 1px solid transparent;
127 | border-radius: 3px;
128 | cursor: pointer;
129 | }
130 |
131 | .editor-toolbar a.active,
132 | .editor-toolbar a:hover {
133 | background: #fcfcfc;
134 | border-color: #95a5a6;
135 | }
136 |
137 | .editor-toolbar a:before {
138 | line-height: 30px
139 | }
140 |
141 | .editor-toolbar i.separator {
142 | display: inline-block;
143 | width: 0;
144 | border-left: 1px solid #d9d9d9;
145 | border-right: 1px solid #fff;
146 | color: transparent;
147 | text-indent: -10px;
148 | margin: 0 6px;
149 | }
150 |
151 | .editor-toolbar a.fa-header-x:after {
152 | font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
153 | font-size: 65%;
154 | vertical-align: text-bottom;
155 | position: relative;
156 | top: 2px;
157 | }
158 |
159 | .editor-toolbar a.fa-header-1:after {
160 | content: "1";
161 | }
162 |
163 | .editor-toolbar a.fa-header-2:after {
164 | content: "2";
165 | }
166 |
167 | .editor-toolbar a.fa-header-3:after {
168 | content: "3";
169 | }
170 |
171 | .editor-toolbar a.fa-header-bigger:after {
172 | content: "▲";
173 | }
174 |
175 | .editor-toolbar a.fa-header-smaller:after {
176 | content: "▼";
177 | }
178 |
179 | .editor-toolbar.disabled-for-preview a:not(.no-disable) {
180 | pointer-events: none;
181 | background: #fff;
182 | border-color: transparent;
183 | text-shadow: inherit;
184 | }
185 |
186 | @media only screen and (max-width: 700px) {
187 | .editor-toolbar a.no-mobile {
188 | display: none;
189 | }
190 | }
191 |
192 | .editor-statusbar {
193 | padding: 8px 10px;
194 | font-size: 12px;
195 | color: #959694;
196 | text-align: right;
197 | }
198 |
199 | .editor-statusbar span {
200 | display: inline-block;
201 | min-width: 4em;
202 | margin-left: 1em;
203 | }
204 |
205 | .editor-statusbar .lines:before {
206 | content: 'lines: '
207 | }
208 |
209 | .editor-statusbar .words:before {
210 | content: 'words: '
211 | }
212 |
213 | .editor-statusbar .characters:before {
214 | content: 'characters: '
215 | }
216 |
217 | .editor-preview {
218 | padding: 10px;
219 | position: absolute;
220 | width: 100%;
221 | height: 100%;
222 | top: 0;
223 | left: 0;
224 | background: #fafafa;
225 | z-index: 7;
226 | overflow: auto;
227 | display: none;
228 | box-sizing: border-box;
229 | }
230 |
231 | .editor-preview-side {
232 | padding: 10px;
233 | position: fixed;
234 | bottom: 0;
235 | width: 50%;
236 | top: 50px;
237 | right: 0;
238 | background: #fafafa;
239 | z-index: 9;
240 | overflow: auto;
241 | display: none;
242 | box-sizing: border-box;
243 | border: 1px solid #ddd;
244 | }
245 |
246 | .editor-preview-active-side {
247 | display: block
248 | }
249 |
250 | .editor-preview-active {
251 | display: block
252 | }
253 |
254 | .editor-preview>p,
255 | .editor-preview-side>p {
256 | margin-top: 0
257 | }
258 |
259 | .editor-preview pre,
260 | .editor-preview-side pre {
261 | background: #eee;
262 | margin-bottom: 10px;
263 | }
264 |
265 | .editor-preview table td,
266 | .editor-preview table th,
267 | .editor-preview-side table td,
268 | .editor-preview-side table th {
269 | border: 1px solid #ddd;
270 | padding: 5px;
271 | }
272 |
273 | .CodeMirror .CodeMirror-code .cm-tag {
274 | color: #63a35c;
275 | }
276 |
277 | .CodeMirror .CodeMirror-code .cm-attribute {
278 | color: #795da3;
279 | }
280 |
281 | .CodeMirror .CodeMirror-code .cm-string {
282 | color: #183691;
283 | }
284 |
285 | .CodeMirror .CodeMirror-selected {
286 | background: #d9d9d9;
287 | }
288 |
289 | .CodeMirror .CodeMirror-code .cm-header-1 {
290 | font-size: 200%;
291 | line-height: 200%;
292 | }
293 |
294 | .CodeMirror .CodeMirror-code .cm-header-2 {
295 | font-size: 160%;
296 | line-height: 160%;
297 | }
298 |
299 | .CodeMirror .CodeMirror-code .cm-header-3 {
300 | font-size: 125%;
301 | line-height: 125%;
302 | }
303 |
304 | .CodeMirror .CodeMirror-code .cm-header-4 {
305 | font-size: 110%;
306 | line-height: 110%;
307 | }
308 |
309 | .CodeMirror .CodeMirror-code .cm-comment {
310 | background: rgba(0, 0, 0, .05);
311 | border-radius: 2px;
312 | }
313 |
314 | .CodeMirror .CodeMirror-code .cm-link {
315 | color: #7f8c8d;
316 | }
317 |
318 | .CodeMirror .CodeMirror-code .cm-url {
319 | color: #aab2b3;
320 | }
321 |
322 | .CodeMirror .CodeMirror-code .cm-strikethrough {
323 | text-decoration: line-through;
324 | }
325 |
326 | .CodeMirror .CodeMirror-placeholder {
327 | opacity: .5;
328 | }
--------------------------------------------------------------------------------
/src/Bloggy/wwwroot/styles/simplemde.min.css:
--------------------------------------------------------------------------------
1 | /**
2 | * simplemde v1.11.2
3 | * Copyright Next Step Webs, Inc.
4 | * @link https://github.com/NextStepWebs/simplemde-markdown-editor
5 | * @license MIT
6 | */
7 | .CodeMirror{color:#000}.CodeMirror-lines{padding:4px 0}.CodeMirror pre{padding:0 4px}.CodeMirror-gutter-filler,.CodeMirror-scrollbar-filler{background-color:#fff}.CodeMirror-gutters{border-right:1px solid #ddd;background-color:#f7f7f7;white-space:nowrap}.CodeMirror-linenumber{padding:0 3px 0 5px;min-width:20px;text-align:right;color:#999;white-space:nowrap}.CodeMirror-guttermarker{color:#000}.CodeMirror-guttermarker-subtle{color:#999}.CodeMirror-cursor{border-left:1px solid #000;border-right:none;width:0}.CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.cm-fat-cursor .CodeMirror-cursor{width:auto;border:0!important;background:#7e7}.cm-fat-cursor div.CodeMirror-cursors{z-index:1}.cm-animate-fat-cursor{width:auto;border:0;-webkit-animation:blink 1.06s steps(1) infinite;-moz-animation:blink 1.06s steps(1) infinite;animation:blink 1.06s steps(1) infinite;background-color:#7e7}@-moz-keyframes blink{50%{background-color:transparent}}@-webkit-keyframes blink{50%{background-color:transparent}}@keyframes blink{50%{background-color:transparent}}.cm-tab{display:inline-block;text-decoration:inherit}.CodeMirror-ruler{border-left:1px solid #ccc;position:absolute}.cm-s-default .cm-header{color:#00f}.cm-s-default .cm-quote{color:#090}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:700}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-strikethrough{text-decoration:line-through}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-variable-3{color:#085}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta,.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-invalidchar,.cm-s-default .cm-error{color:red}.CodeMirror-composing{border-bottom:2px solid}div.CodeMirror span.CodeMirror-matchingbracket{color:#0f0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#f22}.CodeMirror-matchingtag{background:rgba(255,150,0,.3)}.CodeMirror-activeline-background{background:#e8f2ff}.CodeMirror{position:relative;overflow:hidden;background:#fff}.CodeMirror-scroll{overflow:scroll!important;margin-bottom:-30px;margin-right:-30px;padding-bottom:30px;height:100%;outline:0;position:relative}.CodeMirror-sizer{position:relative;border-right:30px solid transparent}.CodeMirror-gutter-filler,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-vscrollbar{position:absolute;z-index:6;display:none}.CodeMirror-vscrollbar{right:0;top:0;overflow-x:hidden;overflow-y:scroll}.CodeMirror-hscrollbar{bottom:0;left:0;overflow-y:hidden;overflow-x:scroll}.CodeMirror-scrollbar-filler{right:0;bottom:0}.CodeMirror-gutter-filler{left:0;bottom:0}.CodeMirror-gutters{position:absolute;left:0;top:0;min-height:100%;z-index:3}.CodeMirror-gutter{white-space:normal;height:100%;display:inline-block;vertical-align:top;margin-bottom:-30px}.CodeMirror-gutter-wrapper{position:absolute;z-index:4;background:0 0!important;border:none!important;-webkit-user-select:none;-moz-user-select:none;user-select:none}.CodeMirror-gutter-background{position:absolute;top:0;bottom:0;z-index:4}.CodeMirror-gutter-elt{position:absolute;cursor:default;z-index:4}.CodeMirror-lines{cursor:text;min-height:1px}.CodeMirror pre{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;border-width:0;background:0 0;font-family:inherit;font-size:inherit;margin:0;white-space:pre;word-wrap:normal;line-height:inherit;color:inherit;z-index:2;position:relative;overflow:visible;-webkit-tap-highlight-color:transparent;-webkit-font-variant-ligatures:none;font-variant-ligatures:none}.CodeMirror-wrap pre{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.CodeMirror-linebackground{position:absolute;left:0;right:0;top:0;bottom:0;z-index:0}.CodeMirror-linewidget{position:relative;z-index:2;overflow:auto}.CodeMirror-code{outline:0}.CodeMirror-gutter,.CodeMirror-gutters,.CodeMirror-linenumber,.CodeMirror-scroll,.CodeMirror-sizer{-moz-box-sizing:content-box;box-sizing:content-box}.CodeMirror-measure{position:absolute;width:100%;height:0;overflow:hidden;visibility:hidden}.CodeMirror-cursor{position:absolute}.CodeMirror-measure pre{position:static}div.CodeMirror-cursors{visibility:hidden;position:relative;z-index:3}.CodeMirror-focused div.CodeMirror-cursors,div.CodeMirror-dragcursors{visibility:visible}.CodeMirror-selected{background:#d9d9d9}.CodeMirror-focused .CodeMirror-selected,.CodeMirror-line::selection,.CodeMirror-line>span::selection,.CodeMirror-line>span>span::selection{background:#d7d4f0}.CodeMirror-crosshair{cursor:crosshair}.CodeMirror-line::-moz-selection,.CodeMirror-line>span::-moz-selection,.CodeMirror-line>span>span::-moz-selection{background:#d7d4f0}.cm-searching{background:#ffa;background:rgba(255,255,0,.4)}.cm-force-border{padding-right:.1px}@media print{.CodeMirror div.CodeMirror-cursors{visibility:hidden}}.cm-tab-wrap-hack:after{content:''}span.CodeMirror-selectedtext{background:0 0}.CodeMirror{height:auto;min-height:300px;border:1px solid #ddd;border-bottom-left-radius:4px;border-bottom-right-radius:4px;padding:10px;font:inherit;z-index:1}.CodeMirror-scroll{min-height:300px}.CodeMirror-fullscreen{background:#fff;position:fixed!important;top:50px;left:0;right:0;bottom:0;height:auto;z-index:9}.CodeMirror-sided{width:50%!important}.editor-toolbar{position:relative;opacity:.6;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none;padding:0 10px;border-top:1px solid #bbb;border-left:1px solid #bbb;border-right:1px solid #bbb;border-top-left-radius:4px;border-top-right-radius:4px}.editor-toolbar:after,.editor-toolbar:before{display:block;content:' ';height:1px}.editor-toolbar:before{margin-bottom:8px}.editor-toolbar:after{margin-top:8px}.editor-toolbar:hover,.editor-wrapper input.title:focus,.editor-wrapper input.title:hover{opacity:.8}.editor-toolbar.fullscreen{width:100%;height:50px;overflow-x:auto;overflow-y:hidden;white-space:nowrap;padding-top:10px;padding-bottom:10px;box-sizing:border-box;background:#fff;border:0;position:fixed;top:0;left:0;opacity:1;z-index:9}.editor-toolbar.fullscreen::before{width:20px;height:50px;background:-moz-linear-gradient(left,rgba(255,255,255,1) 0,rgba(255,255,255,0) 100%);background:-webkit-gradient(linear,left top,right top,color-stop(0,rgba(255,255,255,1)),color-stop(100%,rgba(255,255,255,0)));background:-webkit-linear-gradient(left,rgba(255,255,255,1) 0,rgba(255,255,255,0) 100%);background:-o-linear-gradient(left,rgba(255,255,255,1) 0,rgba(255,255,255,0) 100%);background:-ms-linear-gradient(left,rgba(255,255,255,1) 0,rgba(255,255,255,0) 100%);background:linear-gradient(to right,rgba(255,255,255,1) 0,rgba(255,255,255,0) 100%);position:fixed;top:0;left:0;margin:0;padding:0}.editor-toolbar.fullscreen::after{width:20px;height:50px;background:-moz-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,1) 100%);background:-webkit-gradient(linear,left top,right top,color-stop(0,rgba(255,255,255,0)),color-stop(100%,rgba(255,255,255,1)));background:-webkit-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,1) 100%);background:-o-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,1) 100%);background:-ms-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,1) 100%);background:linear-gradient(to right,rgba(255,255,255,0) 0,rgba(255,255,255,1) 100%);position:fixed;top:0;right:0;margin:0;padding:0}.editor-toolbar a{display:inline-block;text-align:center;text-decoration:none!important;color:#2c3e50!important;width:30px;height:30px;margin:0;border:1px solid transparent;border-radius:3px;cursor:pointer}.editor-toolbar a.active,.editor-toolbar a:hover{background:#fcfcfc;border-color:#95a5a6}.editor-toolbar a:before{line-height:30px}.editor-toolbar i.separator{display:inline-block;width:0;border-left:1px solid #d9d9d9;border-right:1px solid #fff;color:transparent;text-indent:-10px;margin:0 6px}.editor-toolbar a.fa-header-x:after{font-family:Arial,"Helvetica Neue",Helvetica,sans-serif;font-size:65%;vertical-align:text-bottom;position:relative;top:2px}.editor-toolbar a.fa-header-1:after{content:"1"}.editor-toolbar a.fa-header-2:after{content:"2"}.editor-toolbar a.fa-header-3:after{content:"3"}.editor-toolbar a.fa-header-bigger:after{content:"▲"}.editor-toolbar a.fa-header-smaller:after{content:"▼"}.editor-toolbar.disabled-for-preview a:not(.no-disable){pointer-events:none;background:#fff;border-color:transparent;text-shadow:inherit}@media only screen and (max-width:700px){.editor-toolbar a.no-mobile{display:none}}.editor-statusbar{padding:8px 10px;font-size:12px;color:#959694;text-align:right}.editor-statusbar span{display:inline-block;min-width:4em;margin-left:1em}.editor-preview,.editor-preview-side{padding:10px;background:#fafafa;overflow:auto;display:none;box-sizing:border-box}.editor-statusbar .lines:before{content:'lines: '}.editor-statusbar .words:before{content:'words: '}.editor-statusbar .characters:before{content:'characters: '}.editor-preview{position:absolute;width:100%;height:100%;top:0;left:0;z-index:7}.editor-preview-side{position:fixed;bottom:0;width:50%;top:50px;right:0;z-index:9;border:1px solid #ddd}.editor-preview-active,.editor-preview-active-side{display:block}.editor-preview-side>p,.editor-preview>p{margin-top:0}.editor-preview pre,.editor-preview-side pre{background:#eee;margin-bottom:10px}.editor-preview table td,.editor-preview table th,.editor-preview-side table td,.editor-preview-side table th{border:1px solid #ddd;padding:5px}.CodeMirror .CodeMirror-code .cm-tag{color:#63a35c}.CodeMirror .CodeMirror-code .cm-attribute{color:#795da3}.CodeMirror .CodeMirror-code .cm-string{color:#183691}.CodeMirror .CodeMirror-selected{background:#d9d9d9}.CodeMirror .CodeMirror-code .cm-header-1{font-size:200%;line-height:200%}.CodeMirror .CodeMirror-code .cm-header-2{font-size:160%;line-height:160%}.CodeMirror .CodeMirror-code .cm-header-3{font-size:125%;line-height:125%}.CodeMirror .CodeMirror-code .cm-header-4{font-size:110%;line-height:110%}.CodeMirror .CodeMirror-code .cm-comment{background:rgba(0,0,0,.05);border-radius:2px}.CodeMirror .CodeMirror-code .cm-link{color:#7f8c8d}.CodeMirror .CodeMirror-code .cm-url{color:#aab2b3}.CodeMirror .CodeMirror-code .cm-strikethrough{text-decoration:line-through}.CodeMirror .CodeMirror-placeholder{opacity:.5}.CodeMirror .cm-spell-error:not(.cm-url):not(.cm-comment):not(.cm-tag):not(.cm-word){background:rgba(255,0,0,.15)}
--------------------------------------------------------------------------------
/src/Bloggy/wwwroot/scripts/simplemde.js:
--------------------------------------------------------------------------------
1 | /*global require,module*/
2 | "use strict";
3 | var CodeMirror = require("codemirror");
4 | require("codemirror/addon/edit/continuelist.js");
5 | require("./codemirror/tablist");
6 | require("codemirror/addon/display/fullscreen.js");
7 | require("codemirror/mode/markdown/markdown.js");
8 | require("codemirror/addon/mode/overlay.js");
9 | require("codemirror/addon/display/placeholder.js");
10 | require("codemirror/addon/selection/mark-selection.js");
11 | require("codemirror/mode/gfm/gfm.js");
12 | require("codemirror/mode/xml/xml.js");
13 | var CodeMirrorSpellChecker = require("codemirror-spell-checker");
14 | var marked = require("marked");
15 |
16 |
17 | // Some variables
18 | var isMac = /Mac/.test(navigator.platform);
19 |
20 | // Mapping of actions that can be bound to keyboard shortcuts or toolbar buttons
21 | var bindings = {
22 | "toggleBold": toggleBold,
23 | "toggleItalic": toggleItalic,
24 | "drawLink": drawLink,
25 | "toggleHeadingSmaller": toggleHeadingSmaller,
26 | "toggleHeadingBigger": toggleHeadingBigger,
27 | "drawImage": drawImage,
28 | "toggleBlockquote": toggleBlockquote,
29 | "toggleOrderedList": toggleOrderedList,
30 | "toggleUnorderedList": toggleUnorderedList,
31 | "toggleCodeBlock": toggleCodeBlock,
32 | "togglePreview": togglePreview,
33 | "toggleStrikethrough": toggleStrikethrough,
34 | "toggleHeading1": toggleHeading1,
35 | "toggleHeading2": toggleHeading2,
36 | "toggleHeading3": toggleHeading3,
37 | "cleanBlock": cleanBlock,
38 | "drawTable": drawTable,
39 | "drawHorizontalRule": drawHorizontalRule,
40 | "undo": undo,
41 | "redo": redo,
42 | "toggleSideBySide": toggleSideBySide,
43 | "toggleFullScreen": toggleFullScreen
44 | };
45 |
46 | var shortcuts = {
47 | "toggleBold": "Cmd-B",
48 | "toggleItalic": "Cmd-I",
49 | "drawLink": "Cmd-K",
50 | "toggleHeadingSmaller": "Cmd-H",
51 | "toggleHeadingBigger": "Shift-Cmd-H",
52 | "cleanBlock": "Cmd-E",
53 | "drawImage": "Cmd-Alt-I",
54 | "toggleBlockquote": "Cmd-'",
55 | "toggleOrderedList": "Cmd-Alt-L",
56 | "toggleUnorderedList": "Cmd-L",
57 | "toggleCodeBlock": "Cmd-Alt-C",
58 | "togglePreview": "Cmd-P",
59 | "toggleSideBySide": "F9",
60 | "toggleFullScreen": "F11"
61 | };
62 |
63 | var getBindingName = function(f) {
64 | for(var key in bindings) {
65 | if(bindings[key] === f) {
66 | return key;
67 | }
68 | }
69 | return null;
70 | };
71 |
72 | var isMobile = function() {
73 | var check = false;
74 | (function(a) {
75 | if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) check = true;
76 | })(navigator.userAgent || navigator.vendor || window.opera);
77 | return check;
78 | };
79 |
80 |
81 | /**
82 | * Fix shortcut. Mac use Command, others use Ctrl.
83 | */
84 | function fixShortcut(name) {
85 | if(isMac) {
86 | name = name.replace("Ctrl", "Cmd");
87 | } else {
88 | name = name.replace("Cmd", "Ctrl");
89 | }
90 | return name;
91 | }
92 |
93 |
94 | /**
95 | * Create icon element for toolbar.
96 | */
97 | function createIcon(options, enableTooltips, shortcuts) {
98 | options = options || {};
99 | var el = document.createElement("a");
100 | enableTooltips = (enableTooltips == undefined) ? true : enableTooltips;
101 |
102 | if(options.title && enableTooltips) {
103 | el.title = createTootlip(options.title, options.action, shortcuts);
104 |
105 | if(isMac) {
106 | el.title = el.title.replace("Ctrl", "⌘");
107 | el.title = el.title.replace("Alt", "⌥");
108 | }
109 | }
110 |
111 | el.tabIndex = -1;
112 | el.className = options.className;
113 | return el;
114 | }
115 |
116 | function createSep() {
117 | var el = document.createElement("i");
118 | el.className = "separator";
119 | el.innerHTML = "|";
120 | return el;
121 | }
122 |
123 | function createTootlip(title, action, shortcuts) {
124 | var actionName;
125 | var tooltip = title;
126 |
127 | if(action) {
128 | actionName = getBindingName(action);
129 | if(shortcuts[actionName]) {
130 | tooltip += " (" + fixShortcut(shortcuts[actionName]) + ")";
131 | }
132 | }
133 |
134 | return tooltip;
135 | }
136 |
137 | /**
138 | * The state of CodeMirror at the given position.
139 | */
140 | function getState(cm, pos) {
141 | pos = pos || cm.getCursor("start");
142 | var stat = cm.getTokenAt(pos);
143 | if(!stat.type) return {};
144 |
145 | var types = stat.type.split(" ");
146 |
147 | var ret = {},
148 | data, text;
149 | for(var i = 0; i < types.length; i++) {
150 | data = types[i];
151 | if(data === "strong") {
152 | ret.bold = true;
153 | } else if(data === "variable-2") {
154 | text = cm.getLine(pos.line);
155 | if(/^\s*\d+\.\s/.test(text)) {
156 | ret["ordered-list"] = true;
157 | } else {
158 | ret["unordered-list"] = true;
159 | }
160 | } else if(data === "atom") {
161 | ret.quote = true;
162 | } else if(data === "em") {
163 | ret.italic = true;
164 | } else if(data === "quote") {
165 | ret.quote = true;
166 | } else if(data === "strikethrough") {
167 | ret.strikethrough = true;
168 | } else if(data === "comment") {
169 | ret.code = true;
170 | } else if(data === "link") {
171 | ret.link = true;
172 | } else if(data === "tag") {
173 | ret.image = true;
174 | } else if(data.match(/^header(\-[1-6])?$/)) {
175 | ret[data.replace("header", "heading")] = true;
176 | }
177 | }
178 | return ret;
179 | }
180 |
181 |
182 | // Saved overflow setting
183 | var saved_overflow = "";
184 |
185 | /**
186 | * Toggle full screen of the editor.
187 | */
188 | function toggleFullScreen(editor) {
189 | // Set fullscreen
190 | var cm = editor.codemirror;
191 | cm.setOption("fullScreen", !cm.getOption("fullScreen"));
192 |
193 |
194 | // Prevent scrolling on body during fullscreen active
195 | if(cm.getOption("fullScreen")) {
196 | saved_overflow = document.body.style.overflow;
197 | document.body.style.overflow = "hidden";
198 | } else {
199 | document.body.style.overflow = saved_overflow;
200 | }
201 |
202 |
203 | // Update toolbar class
204 | var wrap = cm.getWrapperElement();
205 |
206 | if(!/fullscreen/.test(wrap.previousSibling.className)) {
207 | wrap.previousSibling.className += " fullscreen";
208 | } else {
209 | wrap.previousSibling.className = wrap.previousSibling.className.replace(/\s*fullscreen\b/, "");
210 | }
211 |
212 |
213 | // Update toolbar button
214 | var toolbarButton = editor.toolbarElements.fullscreen;
215 |
216 | if(!/active/.test(toolbarButton.className)) {
217 | toolbarButton.className += " active";
218 | } else {
219 | toolbarButton.className = toolbarButton.className.replace(/\s*active\s*/g, "");
220 | }
221 |
222 |
223 | // Hide side by side if needed
224 | var sidebyside = cm.getWrapperElement().nextSibling;
225 | if(/editor-preview-active-side/.test(sidebyside.className))
226 | toggleSideBySide(editor);
227 | }
228 |
229 |
230 | /**
231 | * Action for toggling bold.
232 | */
233 | function toggleBold(editor) {
234 | _toggleBlock(editor, "bold", editor.options.blockStyles.bold);
235 | }
236 |
237 |
238 | /**
239 | * Action for toggling italic.
240 | */
241 | function toggleItalic(editor) {
242 | _toggleBlock(editor, "italic", editor.options.blockStyles.italic);
243 | }
244 |
245 |
246 | /**
247 | * Action for toggling strikethrough.
248 | */
249 | function toggleStrikethrough(editor) {
250 | _toggleBlock(editor, "strikethrough", "~~");
251 | }
252 |
253 | /**
254 | * Action for toggling code block.
255 | */
256 | function toggleCodeBlock(editor) {
257 | var fenceCharsToInsert = editor.options.blockStyles.code;
258 |
259 | function fencing_line(line) {
260 | /* return true, if this is a ``` or ~~~ line */
261 | if(typeof line !== "object") {
262 | throw "fencing_line() takes a 'line' object (not a line number, or line text). Got: " + typeof line + ": " + line;
263 | }
264 | return line.styles && line.styles[2] && line.styles[2].indexOf("formatting-code-block") !== -1;
265 | }
266 |
267 | function token_state(token) {
268 | // base goes an extra level deep when mode backdrops are used, e.g. spellchecker on
269 | return token.state.base.base || token.state.base;
270 | }
271 |
272 | function code_type(cm, line_num, line, firstTok, lastTok) {
273 | /*
274 | * Return "single", "indented", "fenced" or false
275 | *
276 | * cm and line_num are required. Others are optional for efficiency
277 | * To check in the middle of a line, pass in firstTok yourself.
278 | */
279 | line = line || cm.getLineHandle(line_num);
280 | firstTok = firstTok || cm.getTokenAt({
281 | line: line_num,
282 | ch: 1
283 | });
284 | lastTok = lastTok || (!!line.text && cm.getTokenAt({
285 | line: line_num,
286 | ch: line.text.length - 1
287 | }));
288 | var types = firstTok.type ? firstTok.type.split(" ") : [];
289 | if(lastTok && token_state(lastTok).indentedCode) {
290 | // have to check last char, since first chars of first line aren"t marked as indented
291 | return "indented";
292 | } else if(types.indexOf("comment") === -1) {
293 | // has to be after "indented" check, since first chars of first indented line aren"t marked as such
294 | return false;
295 | } else if(token_state(firstTok).fencedChars || token_state(lastTok).fencedChars || fencing_line(line)) {
296 | return "fenced";
297 | } else {
298 | return "single";
299 | }
300 | }
301 |
302 | function insertFencingAtSelection(cm, cur_start, cur_end, fenceCharsToInsert) {
303 | var start_line_sel = cur_start.line + 1,
304 | end_line_sel = cur_end.line + 1,
305 | sel_multi = cur_start.line !== cur_end.line,
306 | repl_start = fenceCharsToInsert + "\n",
307 | repl_end = "\n" + fenceCharsToInsert;
308 | if(sel_multi) {
309 | end_line_sel++;
310 | }
311 | // handle last char including \n or not
312 | if(sel_multi && cur_end.ch === 0) {
313 | repl_end = fenceCharsToInsert + "\n";
314 | end_line_sel--;
315 | }
316 | _replaceSelection(cm, false, [repl_start, repl_end]);
317 | cm.setSelection({
318 | line: start_line_sel,
319 | ch: 0
320 | }, {
321 | line: end_line_sel,
322 | ch: 0
323 | });
324 | }
325 |
326 | var cm = editor.codemirror,
327 | cur_start = cm.getCursor("start"),
328 | cur_end = cm.getCursor("end"),
329 | tok = cm.getTokenAt({
330 | line: cur_start.line,
331 | ch: cur_start.ch || 1
332 | }), // avoid ch 0 which is a cursor pos but not token
333 | line = cm.getLineHandle(cur_start.line),
334 | is_code = code_type(cm, cur_start.line, line, tok);
335 | var block_start, block_end, lineCount;
336 |
337 | if(is_code === "single") {
338 | // similar to some SimpleMDE _toggleBlock logic
339 | var start = line.text.slice(0, cur_start.ch).replace("`", ""),
340 | end = line.text.slice(cur_start.ch).replace("`", "");
341 | cm.replaceRange(start + end, {
342 | line: cur_start.line,
343 | ch: 0
344 | }, {
345 | line: cur_start.line,
346 | ch: 99999999999999
347 | });
348 | cur_start.ch--;
349 | if(cur_start !== cur_end) {
350 | cur_end.ch--;
351 | }
352 | cm.setSelection(cur_start, cur_end);
353 | cm.focus();
354 | } else if(is_code === "fenced") {
355 | if(cur_start.line !== cur_end.line || cur_start.ch !== cur_end.ch) {
356 | // use selection
357 |
358 | // find the fenced line so we know what type it is (tilde, backticks, number of them)
359 | for(block_start = cur_start.line; block_start >= 0; block_start--) {
360 | line = cm.getLineHandle(block_start);
361 | if(fencing_line(line)) {
362 | break;
363 | }
364 | }
365 | var fencedTok = cm.getTokenAt({
366 | line: block_start,
367 | ch: 1
368 | });
369 | var fence_chars = token_state(fencedTok).fencedChars;
370 | var start_text, start_line;
371 | var end_text, end_line;
372 | // check for selection going up against fenced lines, in which case we don't want to add more fencing
373 | if(fencing_line(cm.getLineHandle(cur_start.line))) {
374 | start_text = "";
375 | start_line = cur_start.line;
376 | } else if(fencing_line(cm.getLineHandle(cur_start.line - 1))) {
377 | start_text = "";
378 | start_line = cur_start.line - 1;
379 | } else {
380 | start_text = fence_chars + "\n";
381 | start_line = cur_start.line;
382 | }
383 | if(fencing_line(cm.getLineHandle(cur_end.line))) {
384 | end_text = "";
385 | end_line = cur_end.line;
386 | if(cur_end.ch === 0) {
387 | end_line += 1;
388 | }
389 | } else if(cur_end.ch !== 0 && fencing_line(cm.getLineHandle(cur_end.line + 1))) {
390 | end_text = "";
391 | end_line = cur_end.line + 1;
392 | } else {
393 | end_text = fence_chars + "\n";
394 | end_line = cur_end.line + 1;
395 | }
396 | if(cur_end.ch === 0) {
397 | // full last line selected, putting cursor at beginning of next
398 | end_line -= 1;
399 | }
400 | cm.operation(function() {
401 | // end line first, so that line numbers don't change
402 | cm.replaceRange(end_text, {
403 | line: end_line,
404 | ch: 0
405 | }, {
406 | line: end_line + (end_text ? 0 : 1),
407 | ch: 0
408 | });
409 | cm.replaceRange(start_text, {
410 | line: start_line,
411 | ch: 0
412 | }, {
413 | line: start_line + (start_text ? 0 : 1),
414 | ch: 0
415 | });
416 | });
417 | cm.setSelection({
418 | line: start_line + (start_text ? 1 : 0),
419 | ch: 0
420 | }, {
421 | line: end_line + (start_text ? 1 : -1),
422 | ch: 0
423 | });
424 | cm.focus();
425 | } else {
426 | // no selection, search for ends of this fenced block
427 | var search_from = cur_start.line;
428 | if(fencing_line(cm.getLineHandle(cur_start.line))) { // gets a little tricky if cursor is right on a fenced line
429 | if(code_type(cm, cur_start.line + 1) === "fenced") {
430 | block_start = cur_start.line;
431 | search_from = cur_start.line + 1; // for searching for "end"
432 | } else {
433 | block_end = cur_start.line;
434 | search_from = cur_start.line - 1; // for searching for "start"
435 | }
436 | }
437 | if(block_start === undefined) {
438 | for(block_start = search_from; block_start >= 0; block_start--) {
439 | line = cm.getLineHandle(block_start);
440 | if(fencing_line(line)) {
441 | break;
442 | }
443 | }
444 | }
445 | if(block_end === undefined) {
446 | lineCount = cm.lineCount();
447 | for(block_end = search_from; block_end < lineCount; block_end++) {
448 | line = cm.getLineHandle(block_end);
449 | if(fencing_line(line)) {
450 | break;
451 | }
452 | }
453 | }
454 | cm.operation(function() {
455 | cm.replaceRange("", {
456 | line: block_start,
457 | ch: 0
458 | }, {
459 | line: block_start + 1,
460 | ch: 0
461 | });
462 | cm.replaceRange("", {
463 | line: block_end - 1,
464 | ch: 0
465 | }, {
466 | line: block_end,
467 | ch: 0
468 | });
469 | });
470 | cm.focus();
471 | }
472 | } else if(is_code === "indented") {
473 | if(cur_start.line !== cur_end.line || cur_start.ch !== cur_end.ch) {
474 | // use selection
475 | block_start = cur_start.line;
476 | block_end = cur_end.line;
477 | if(cur_end.ch === 0) {
478 | block_end--;
479 | }
480 | } else {
481 | // no selection, search for ends of this indented block
482 | for(block_start = cur_start.line; block_start >= 0; block_start--) {
483 | line = cm.getLineHandle(block_start);
484 | if(line.text.match(/^\s*$/)) {
485 | // empty or all whitespace - keep going
486 | continue;
487 | } else {
488 | if(code_type(cm, block_start, line) !== "indented") {
489 | block_start += 1;
490 | break;
491 | }
492 | }
493 | }
494 | lineCount = cm.lineCount();
495 | for(block_end = cur_start.line; block_end < lineCount; block_end++) {
496 | line = cm.getLineHandle(block_end);
497 | if(line.text.match(/^\s*$/)) {
498 | // empty or all whitespace - keep going
499 | continue;
500 | } else {
501 | if(code_type(cm, block_end, line) !== "indented") {
502 | block_end -= 1;
503 | break;
504 | }
505 | }
506 | }
507 | }
508 | // if we are going to un-indent based on a selected set of lines, and the next line is indented too, we need to
509 | // insert a blank line so that the next line(s) continue to be indented code
510 | var next_line = cm.getLineHandle(block_end + 1),
511 | next_line_last_tok = next_line && cm.getTokenAt({
512 | line: block_end + 1,
513 | ch: next_line.text.length - 1
514 | }),
515 | next_line_indented = next_line_last_tok && token_state(next_line_last_tok).indentedCode;
516 | if(next_line_indented) {
517 | cm.replaceRange("\n", {
518 | line: block_end + 1,
519 | ch: 0
520 | });
521 | }
522 |
523 | for(var i = block_start; i <= block_end; i++) {
524 | cm.indentLine(i, "subtract"); // TODO: this doesn't get tracked in the history, so can't be undone :(
525 | }
526 | cm.focus();
527 | } else {
528 | // insert code formatting
529 | var no_sel_and_starting_of_line = (cur_start.line === cur_end.line && cur_start.ch === cur_end.ch && cur_start.ch === 0);
530 | var sel_multi = cur_start.line !== cur_end.line;
531 | if(no_sel_and_starting_of_line || sel_multi) {
532 | insertFencingAtSelection(cm, cur_start, cur_end, fenceCharsToInsert);
533 | } else {
534 | _replaceSelection(cm, false, ["`", "`"]);
535 | }
536 | }
537 | }
538 |
539 | /**
540 | * Action for toggling blockquote.
541 | */
542 | function toggleBlockquote(editor) {
543 | var cm = editor.codemirror;
544 | _toggleLine(cm, "quote");
545 | }
546 |
547 | /**
548 | * Action for toggling heading size: normal -> h1 -> h2 -> h3 -> h4 -> h5 -> h6 -> normal
549 | */
550 | function toggleHeadingSmaller(editor) {
551 | var cm = editor.codemirror;
552 | _toggleHeading(cm, "smaller");
553 | }
554 |
555 | /**
556 | * Action for toggling heading size: normal -> h6 -> h5 -> h4 -> h3 -> h2 -> h1 -> normal
557 | */
558 | function toggleHeadingBigger(editor) {
559 | var cm = editor.codemirror;
560 | _toggleHeading(cm, "bigger");
561 | }
562 |
563 | /**
564 | * Action for toggling heading size 1
565 | */
566 | function toggleHeading1(editor) {
567 | var cm = editor.codemirror;
568 | _toggleHeading(cm, undefined, 1);
569 | }
570 |
571 | /**
572 | * Action for toggling heading size 2
573 | */
574 | function toggleHeading2(editor) {
575 | var cm = editor.codemirror;
576 | _toggleHeading(cm, undefined, 2);
577 | }
578 |
579 | /**
580 | * Action for toggling heading size 3
581 | */
582 | function toggleHeading3(editor) {
583 | var cm = editor.codemirror;
584 | _toggleHeading(cm, undefined, 3);
585 | }
586 |
587 |
588 | /**
589 | * Action for toggling ul.
590 | */
591 | function toggleUnorderedList(editor) {
592 | var cm = editor.codemirror;
593 | _toggleLine(cm, "unordered-list");
594 | }
595 |
596 |
597 | /**
598 | * Action for toggling ol.
599 | */
600 | function toggleOrderedList(editor) {
601 | var cm = editor.codemirror;
602 | _toggleLine(cm, "ordered-list");
603 | }
604 |
605 | /**
606 | * Action for clean block (remove headline, list, blockquote code, markers)
607 | */
608 | function cleanBlock(editor) {
609 | var cm = editor.codemirror;
610 | _cleanBlock(cm);
611 | }
612 |
613 | /**
614 | * Action for drawing a link.
615 | */
616 | function drawLink(editor) {
617 | var cm = editor.codemirror;
618 | var stat = getState(cm);
619 | var options = editor.options;
620 | var url = "http://";
621 | if(options.promptURLs) {
622 | url = prompt(options.promptTexts.link);
623 | if(!url) {
624 | return false;
625 | }
626 | }
627 | _replaceSelection(cm, stat.link, options.insertTexts.link, url);
628 | }
629 |
630 | /**
631 | * Action for drawing an img.
632 | */
633 | function drawImage(editor) {
634 | var cm = editor.codemirror;
635 | var stat = getState(cm);
636 | var options = editor.options;
637 | var url = "http://";
638 | if(options.promptURLs) {
639 | url = prompt(options.promptTexts.image);
640 | if(!url) {
641 | return false;
642 | }
643 | }
644 | _replaceSelection(cm, stat.image, options.insertTexts.image, url);
645 | }
646 |
647 | /**
648 | * Action for drawing a table.
649 | */
650 | function drawTable(editor) {
651 | var cm = editor.codemirror;
652 | var stat = getState(cm);
653 | var options = editor.options;
654 | _replaceSelection(cm, stat.table, options.insertTexts.table);
655 | }
656 |
657 | /**
658 | * Action for drawing a horizontal rule.
659 | */
660 | function drawHorizontalRule(editor) {
661 | var cm = editor.codemirror;
662 | var stat = getState(cm);
663 | var options = editor.options;
664 | _replaceSelection(cm, stat.image, options.insertTexts.horizontalRule);
665 | }
666 |
667 |
668 | /**
669 | * Undo action.
670 | */
671 | function undo(editor) {
672 | var cm = editor.codemirror;
673 | cm.undo();
674 | cm.focus();
675 | }
676 |
677 |
678 | /**
679 | * Redo action.
680 | */
681 | function redo(editor) {
682 | var cm = editor.codemirror;
683 | cm.redo();
684 | cm.focus();
685 | }
686 |
687 |
688 | /**
689 | * Toggle side by side preview
690 | */
691 | function toggleSideBySide(editor) {
692 | var cm = editor.codemirror;
693 | var wrapper = cm.getWrapperElement();
694 | var preview = wrapper.nextSibling;
695 | var toolbarButton = editor.toolbarElements["side-by-side"];
696 | var useSideBySideListener = false;
697 | if(/editor-preview-active-side/.test(preview.className)) {
698 | preview.className = preview.className.replace(
699 | /\s*editor-preview-active-side\s*/g, ""
700 | );
701 | toolbarButton.className = toolbarButton.className.replace(/\s*active\s*/g, "");
702 | wrapper.className = wrapper.className.replace(/\s*CodeMirror-sided\s*/g, " ");
703 | } else {
704 | // When the preview button is clicked for the first time,
705 | // give some time for the transition from editor.css to fire and the view to slide from right to left,
706 | // instead of just appearing.
707 | setTimeout(function() {
708 | if(!cm.getOption("fullScreen"))
709 | toggleFullScreen(editor);
710 | preview.className += " editor-preview-active-side";
711 | }, 1);
712 | toolbarButton.className += " active";
713 | wrapper.className += " CodeMirror-sided";
714 | useSideBySideListener = true;
715 | }
716 |
717 | // Hide normal preview if active
718 | var previewNormal = wrapper.lastChild;
719 | if(/editor-preview-active/.test(previewNormal.className)) {
720 | previewNormal.className = previewNormal.className.replace(
721 | /\s*editor-preview-active\s*/g, ""
722 | );
723 | var toolbar = editor.toolbarElements.preview;
724 | var toolbar_div = wrapper.previousSibling;
725 | toolbar.className = toolbar.className.replace(/\s*active\s*/g, "");
726 | toolbar_div.className = toolbar_div.className.replace(/\s*disabled-for-preview*/g, "");
727 | }
728 |
729 | var sideBySideRenderingFunction = function() {
730 | preview.innerHTML = editor.options.previewRender(editor.value(), preview);
731 | };
732 |
733 | if(!cm.sideBySideRenderingFunction) {
734 | cm.sideBySideRenderingFunction = sideBySideRenderingFunction;
735 | }
736 |
737 | if(useSideBySideListener) {
738 | preview.innerHTML = editor.options.previewRender(editor.value(), preview);
739 | cm.on("update", cm.sideBySideRenderingFunction);
740 | } else {
741 | cm.off("update", cm.sideBySideRenderingFunction);
742 | }
743 |
744 | // Refresh to fix selection being off (#309)
745 | cm.refresh();
746 | }
747 |
748 |
749 | /**
750 | * Preview action.
751 | */
752 | function togglePreview(editor) {
753 | var cm = editor.codemirror;
754 | var wrapper = cm.getWrapperElement();
755 | var toolbar_div = wrapper.previousSibling;
756 | var toolbar = editor.options.toolbar ? editor.toolbarElements.preview : false;
757 | var preview = wrapper.lastChild;
758 | if(!preview || !/editor-preview/.test(preview.className)) {
759 | preview = document.createElement("div");
760 | preview.className = "editor-preview";
761 | wrapper.appendChild(preview);
762 | }
763 | if(/editor-preview-active/.test(preview.className)) {
764 | preview.className = preview.className.replace(
765 | /\s*editor-preview-active\s*/g, ""
766 | );
767 | if(toolbar) {
768 | toolbar.className = toolbar.className.replace(/\s*active\s*/g, "");
769 | toolbar_div.className = toolbar_div.className.replace(/\s*disabled-for-preview*/g, "");
770 | }
771 | } else {
772 | // When the preview button is clicked for the first time,
773 | // give some time for the transition from editor.css to fire and the view to slide from right to left,
774 | // instead of just appearing.
775 | setTimeout(function() {
776 | preview.className += " editor-preview-active";
777 | }, 1);
778 | if(toolbar) {
779 | toolbar.className += " active";
780 | toolbar_div.className += " disabled-for-preview";
781 | }
782 | }
783 | preview.innerHTML = editor.options.previewRender(editor.value(), preview);
784 |
785 | // Turn off side by side if needed
786 | var sidebyside = cm.getWrapperElement().nextSibling;
787 | if(/editor-preview-active-side/.test(sidebyside.className))
788 | toggleSideBySide(editor);
789 | }
790 |
791 | function _replaceSelection(cm, active, startEnd, url) {
792 | if(/editor-preview-active/.test(cm.getWrapperElement().lastChild.className))
793 | return;
794 |
795 | var text;
796 | var start = startEnd[0];
797 | var end = startEnd[1];
798 | var startPoint = cm.getCursor("start");
799 | var endPoint = cm.getCursor("end");
800 | if(url) {
801 | end = end.replace("#url#", url);
802 | }
803 | if(active) {
804 | text = cm.getLine(startPoint.line);
805 | start = text.slice(0, startPoint.ch);
806 | end = text.slice(startPoint.ch);
807 | cm.replaceRange(start + end, {
808 | line: startPoint.line,
809 | ch: 0
810 | });
811 | } else {
812 | text = cm.getSelection();
813 | cm.replaceSelection(start + text + end);
814 |
815 | startPoint.ch += start.length;
816 | if(startPoint !== endPoint) {
817 | endPoint.ch += start.length;
818 | }
819 | }
820 | cm.setSelection(startPoint, endPoint);
821 | cm.focus();
822 | }
823 |
824 |
825 | function _toggleHeading(cm, direction, size) {
826 | if(/editor-preview-active/.test(cm.getWrapperElement().lastChild.className))
827 | return;
828 |
829 | var startPoint = cm.getCursor("start");
830 | var endPoint = cm.getCursor("end");
831 | for(var i = startPoint.line; i <= endPoint.line; i++) {
832 | (function(i) {
833 | var text = cm.getLine(i);
834 | var currHeadingLevel = text.search(/[^#]/);
835 |
836 | if(direction !== undefined) {
837 | if(currHeadingLevel <= 0) {
838 | if(direction == "bigger") {
839 | text = "###### " + text;
840 | } else {
841 | text = "# " + text;
842 | }
843 | } else if(currHeadingLevel == 6 && direction == "smaller") {
844 | text = text.substr(7);
845 | } else if(currHeadingLevel == 1 && direction == "bigger") {
846 | text = text.substr(2);
847 | } else {
848 | if(direction == "bigger") {
849 | text = text.substr(1);
850 | } else {
851 | text = "#" + text;
852 | }
853 | }
854 | } else {
855 | if(size == 1) {
856 | if(currHeadingLevel <= 0) {
857 | text = "# " + text;
858 | } else if(currHeadingLevel == size) {
859 | text = text.substr(currHeadingLevel + 1);
860 | } else {
861 | text = "# " + text.substr(currHeadingLevel + 1);
862 | }
863 | } else if(size == 2) {
864 | if(currHeadingLevel <= 0) {
865 | text = "## " + text;
866 | } else if(currHeadingLevel == size) {
867 | text = text.substr(currHeadingLevel + 1);
868 | } else {
869 | text = "## " + text.substr(currHeadingLevel + 1);
870 | }
871 | } else {
872 | if(currHeadingLevel <= 0) {
873 | text = "### " + text;
874 | } else if(currHeadingLevel == size) {
875 | text = text.substr(currHeadingLevel + 1);
876 | } else {
877 | text = "### " + text.substr(currHeadingLevel + 1);
878 | }
879 | }
880 | }
881 |
882 | cm.replaceRange(text, {
883 | line: i,
884 | ch: 0
885 | }, {
886 | line: i,
887 | ch: 99999999999999
888 | });
889 | })(i);
890 | }
891 | cm.focus();
892 | }
893 |
894 |
895 | function _toggleLine(cm, name) {
896 | if(/editor-preview-active/.test(cm.getWrapperElement().lastChild.className))
897 | return;
898 |
899 | var stat = getState(cm);
900 | var startPoint = cm.getCursor("start");
901 | var endPoint = cm.getCursor("end");
902 | var repl = {
903 | "quote": /^(\s*)\>\s+/,
904 | "unordered-list": /^(\s*)(\*|\-|\+)\s+/,
905 | "ordered-list": /^(\s*)\d+\.\s+/
906 | };
907 | var map = {
908 | "quote": "> ",
909 | "unordered-list": "* ",
910 | "ordered-list": "1. "
911 | };
912 | for(var i = startPoint.line; i <= endPoint.line; i++) {
913 | (function(i) {
914 | var text = cm.getLine(i);
915 | if(stat[name]) {
916 | text = text.replace(repl[name], "$1");
917 | } else {
918 | text = map[name] + text;
919 | }
920 | cm.replaceRange(text, {
921 | line: i,
922 | ch: 0
923 | }, {
924 | line: i,
925 | ch: 99999999999999
926 | });
927 | })(i);
928 | }
929 | cm.focus();
930 | }
931 |
932 | function _toggleBlock(editor, type, start_chars, end_chars) {
933 | if(/editor-preview-active/.test(editor.codemirror.getWrapperElement().lastChild.className))
934 | return;
935 |
936 | end_chars = (typeof end_chars === "undefined") ? start_chars : end_chars;
937 | var cm = editor.codemirror;
938 | var stat = getState(cm);
939 |
940 | var text;
941 | var start = start_chars;
942 | var end = end_chars;
943 |
944 | var startPoint = cm.getCursor("start");
945 | var endPoint = cm.getCursor("end");
946 |
947 | if(stat[type]) {
948 | text = cm.getLine(startPoint.line);
949 | start = text.slice(0, startPoint.ch);
950 | end = text.slice(startPoint.ch);
951 | if(type == "bold") {
952 | start = start.replace(/(\*\*|__)(?![\s\S]*(\*\*|__))/, "");
953 | end = end.replace(/(\*\*|__)/, "");
954 | } else if(type == "italic") {
955 | start = start.replace(/(\*|_)(?![\s\S]*(\*|_))/, "");
956 | end = end.replace(/(\*|_)/, "");
957 | } else if(type == "strikethrough") {
958 | start = start.replace(/(\*\*|~~)(?![\s\S]*(\*\*|~~))/, "");
959 | end = end.replace(/(\*\*|~~)/, "");
960 | }
961 | cm.replaceRange(start + end, {
962 | line: startPoint.line,
963 | ch: 0
964 | }, {
965 | line: startPoint.line,
966 | ch: 99999999999999
967 | });
968 |
969 | if(type == "bold" || type == "strikethrough") {
970 | startPoint.ch -= 2;
971 | if(startPoint !== endPoint) {
972 | endPoint.ch -= 2;
973 | }
974 | } else if(type == "italic") {
975 | startPoint.ch -= 1;
976 | if(startPoint !== endPoint) {
977 | endPoint.ch -= 1;
978 | }
979 | }
980 | } else {
981 | text = cm.getSelection();
982 | if(type == "bold") {
983 | text = text.split("**").join("");
984 | text = text.split("__").join("");
985 | } else if(type == "italic") {
986 | text = text.split("*").join("");
987 | text = text.split("_").join("");
988 | } else if(type == "strikethrough") {
989 | text = text.split("~~").join("");
990 | }
991 | cm.replaceSelection(start + text + end);
992 |
993 | startPoint.ch += start_chars.length;
994 | endPoint.ch = startPoint.ch + text.length;
995 | }
996 |
997 | cm.setSelection(startPoint, endPoint);
998 | cm.focus();
999 | }
1000 |
1001 | function _cleanBlock(cm) {
1002 | if(/editor-preview-active/.test(cm.getWrapperElement().lastChild.className))
1003 | return;
1004 |
1005 | var startPoint = cm.getCursor("start");
1006 | var endPoint = cm.getCursor("end");
1007 | var text;
1008 |
1009 | for(var line = startPoint.line; line <= endPoint.line; line++) {
1010 | text = cm.getLine(line);
1011 | text = text.replace(/^[ ]*([# ]+|\*|\-|[> ]+|[0-9]+(.|\)))[ ]*/, "");
1012 |
1013 | cm.replaceRange(text, {
1014 | line: line,
1015 | ch: 0
1016 | }, {
1017 | line: line,
1018 | ch: 99999999999999
1019 | });
1020 | }
1021 | }
1022 |
1023 | // Merge the properties of one object into another.
1024 | function _mergeProperties(target, source) {
1025 | for(var property in source) {
1026 | if(source.hasOwnProperty(property)) {
1027 | if(source[property] instanceof Array) {
1028 | target[property] = source[property].concat(target[property] instanceof Array ? target[property] : []);
1029 | } else if(
1030 | source[property] !== null &&
1031 | typeof source[property] === "object" &&
1032 | source[property].constructor === Object
1033 | ) {
1034 | target[property] = _mergeProperties(target[property] || {}, source[property]);
1035 | } else {
1036 | target[property] = source[property];
1037 | }
1038 | }
1039 | }
1040 |
1041 | return target;
1042 | }
1043 |
1044 | // Merge an arbitrary number of objects into one.
1045 | function extend(target) {
1046 | for(var i = 1; i < arguments.length; i++) {
1047 | target = _mergeProperties(target, arguments[i]);
1048 | }
1049 |
1050 | return target;
1051 | }
1052 |
1053 | /* The right word count in respect for CJK. */
1054 | function wordCount(data) {
1055 | var pattern = /[a-zA-Z0-9_\u0392-\u03c9\u0410-\u04F9]+|[\u4E00-\u9FFF\u3400-\u4dbf\uf900-\ufaff\u3040-\u309f\uac00-\ud7af]+/g;
1056 | var m = data.match(pattern);
1057 | var count = 0;
1058 | if(m === null) return count;
1059 | for(var i = 0; i < m.length; i++) {
1060 | if(m[i].charCodeAt(0) >= 0x4E00) {
1061 | count += m[i].length;
1062 | } else {
1063 | count += 1;
1064 | }
1065 | }
1066 | return count;
1067 | }
1068 |
1069 | var toolbarBuiltInButtons = {
1070 | "bold": {
1071 | name: "bold",
1072 | action: toggleBold,
1073 | className: "fa fa-bold",
1074 | title: "Bold",
1075 | default: true
1076 | },
1077 | "italic": {
1078 | name: "italic",
1079 | action: toggleItalic,
1080 | className: "fa fa-italic",
1081 | title: "Italic",
1082 | default: true
1083 | },
1084 | "strikethrough": {
1085 | name: "strikethrough",
1086 | action: toggleStrikethrough,
1087 | className: "fa fa-strikethrough",
1088 | title: "Strikethrough"
1089 | },
1090 | "heading": {
1091 | name: "heading",
1092 | action: toggleHeadingSmaller,
1093 | className: "fa fa-header",
1094 | title: "Heading",
1095 | default: true
1096 | },
1097 | "heading-smaller": {
1098 | name: "heading-smaller",
1099 | action: toggleHeadingSmaller,
1100 | className: "fa fa-header fa-header-x fa-header-smaller",
1101 | title: "Smaller Heading"
1102 | },
1103 | "heading-bigger": {
1104 | name: "heading-bigger",
1105 | action: toggleHeadingBigger,
1106 | className: "fa fa-header fa-header-x fa-header-bigger",
1107 | title: "Bigger Heading"
1108 | },
1109 | "heading-1": {
1110 | name: "heading-1",
1111 | action: toggleHeading1,
1112 | className: "fa fa-header fa-header-x fa-header-1",
1113 | title: "Big Heading"
1114 | },
1115 | "heading-2": {
1116 | name: "heading-2",
1117 | action: toggleHeading2,
1118 | className: "fa fa-header fa-header-x fa-header-2",
1119 | title: "Medium Heading"
1120 | },
1121 | "heading-3": {
1122 | name: "heading-3",
1123 | action: toggleHeading3,
1124 | className: "fa fa-header fa-header-x fa-header-3",
1125 | title: "Small Heading"
1126 | },
1127 | "separator-1": {
1128 | name: "separator-1"
1129 | },
1130 | "code": {
1131 | name: "code",
1132 | action: toggleCodeBlock,
1133 | className: "fa fa-code",
1134 | title: "Code"
1135 | },
1136 | "quote": {
1137 | name: "quote",
1138 | action: toggleBlockquote,
1139 | className: "fa fa-quote-left",
1140 | title: "Quote",
1141 | default: true
1142 | },
1143 | "unordered-list": {
1144 | name: "unordered-list",
1145 | action: toggleUnorderedList,
1146 | className: "fa fa-list-ul",
1147 | title: "Generic List",
1148 | default: true
1149 | },
1150 | "ordered-list": {
1151 | name: "ordered-list",
1152 | action: toggleOrderedList,
1153 | className: "fa fa-list-ol",
1154 | title: "Numbered List",
1155 | default: true
1156 | },
1157 | "clean-block": {
1158 | name: "clean-block",
1159 | action: cleanBlock,
1160 | className: "fa fa-eraser fa-clean-block",
1161 | title: "Clean block"
1162 | },
1163 | "separator-2": {
1164 | name: "separator-2"
1165 | },
1166 | "link": {
1167 | name: "link",
1168 | action: drawLink,
1169 | className: "fa fa-link",
1170 | title: "Create Link",
1171 | default: true
1172 | },
1173 | "image": {
1174 | name: "image",
1175 | action: drawImage,
1176 | className: "fa fa-picture-o",
1177 | title: "Insert Image",
1178 | default: true
1179 | },
1180 | "table": {
1181 | name: "table",
1182 | action: drawTable,
1183 | className: "fa fa-table",
1184 | title: "Insert Table"
1185 | },
1186 | "horizontal-rule": {
1187 | name: "horizontal-rule",
1188 | action: drawHorizontalRule,
1189 | className: "fa fa-minus",
1190 | title: "Insert Horizontal Line"
1191 | },
1192 | "separator-3": {
1193 | name: "separator-3"
1194 | },
1195 | "preview": {
1196 | name: "preview",
1197 | action: togglePreview,
1198 | className: "fa fa-eye no-disable",
1199 | title: "Toggle Preview",
1200 | default: true
1201 | },
1202 | "side-by-side": {
1203 | name: "side-by-side",
1204 | action: toggleSideBySide,
1205 | className: "fa fa-columns no-disable no-mobile",
1206 | title: "Toggle Side by Side",
1207 | default: true
1208 | },
1209 | "fullscreen": {
1210 | name: "fullscreen",
1211 | action: toggleFullScreen,
1212 | className: "fa fa-arrows-alt no-disable no-mobile",
1213 | title: "Toggle Fullscreen",
1214 | default: true
1215 | },
1216 | "separator-4": {
1217 | name: "separator-4"
1218 | },
1219 | "guide": {
1220 | name: "guide",
1221 | action: "https://simplemde.com/markdown-guide",
1222 | className: "fa fa-question-circle",
1223 | title: "Markdown Guide",
1224 | default: true
1225 | },
1226 | "separator-5": {
1227 | name: "separator-5"
1228 | },
1229 | "undo": {
1230 | name: "undo",
1231 | action: undo,
1232 | className: "fa fa-undo no-disable",
1233 | title: "Undo"
1234 | },
1235 | "redo": {
1236 | name: "redo",
1237 | action: redo,
1238 | className: "fa fa-repeat no-disable",
1239 | title: "Redo"
1240 | }
1241 | };
1242 |
1243 | var insertTexts = {
1244 | link: ["[", "](#url#)"],
1245 | image: [""],
1246 | table: ["", "\n\n| Column 1 | Column 2 | Column 3 |\n| -------- | -------- | -------- |\n| Text | Text | Text |\n\n"],
1247 | horizontalRule: ["", "\n\n-----\n\n"]
1248 | };
1249 |
1250 | var promptTexts = {
1251 | link: "URL for the link:",
1252 | image: "URL of the image:"
1253 | };
1254 |
1255 | var blockStyles = {
1256 | "bold": "**",
1257 | "code": "```",
1258 | "italic": "*"
1259 | };
1260 |
1261 | /**
1262 | * Interface of SimpleMDE.
1263 | */
1264 | function SimpleMDE(options) {
1265 | // Handle options parameter
1266 | options = options || {};
1267 |
1268 |
1269 | // Used later to refer to it"s parent
1270 | options.parent = this;
1271 |
1272 |
1273 | // Check if Font Awesome needs to be auto downloaded
1274 | var autoDownloadFA = true;
1275 |
1276 | if(options.autoDownloadFontAwesome === false) {
1277 | autoDownloadFA = false;
1278 | }
1279 |
1280 | if(options.autoDownloadFontAwesome !== true) {
1281 | var styleSheets = document.styleSheets;
1282 | for(var i = 0; i < styleSheets.length; i++) {
1283 | if(!styleSheets[i].href)
1284 | continue;
1285 |
1286 | if(styleSheets[i].href.indexOf("//maxcdn.bootstrapcdn.com/font-awesome/") > -1) {
1287 | autoDownloadFA = false;
1288 | }
1289 | }
1290 | }
1291 |
1292 | if(autoDownloadFA) {
1293 | var link = document.createElement("link");
1294 | link.rel = "stylesheet";
1295 | link.href = "https://maxcdn.bootstrapcdn.com/font-awesome/latest/css/font-awesome.min.css";
1296 | document.getElementsByTagName("head")[0].appendChild(link);
1297 | }
1298 |
1299 |
1300 | // Find the textarea to use
1301 | if(options.element) {
1302 | this.element = options.element;
1303 | } else if(options.element === null) {
1304 | // This means that the element option was specified, but no element was found
1305 | console.log("SimpleMDE: Error. No element was found.");
1306 | return;
1307 | }
1308 |
1309 |
1310 | // Handle toolbar
1311 | if(options.toolbar === undefined) {
1312 | // Initialize
1313 | options.toolbar = [];
1314 |
1315 |
1316 | // Loop over the built in buttons, to get the preferred order
1317 | for(var key in toolbarBuiltInButtons) {
1318 | if(toolbarBuiltInButtons.hasOwnProperty(key)) {
1319 | if(key.indexOf("separator-") != -1) {
1320 | options.toolbar.push("|");
1321 | }
1322 |
1323 | if(toolbarBuiltInButtons[key].default === true || (options.showIcons && options.showIcons.constructor === Array && options.showIcons.indexOf(key) != -1)) {
1324 | options.toolbar.push(key);
1325 | }
1326 | }
1327 | }
1328 | }
1329 |
1330 |
1331 | // Handle status bar
1332 | if(!options.hasOwnProperty("status")) {
1333 | options.status = ["autosave", "lines", "words", "cursor"];
1334 | }
1335 |
1336 |
1337 | // Add default preview rendering function
1338 | if(!options.previewRender) {
1339 | options.previewRender = function(plainText) {
1340 | // Note: "this" refers to the options object
1341 | return this.parent.markdown(plainText);
1342 | };
1343 | }
1344 |
1345 |
1346 | // Set default options for parsing config
1347 | options.parsingConfig = extend({
1348 | highlightFormatting: true // needed for toggleCodeBlock to detect types of code
1349 | }, options.parsingConfig || {});
1350 |
1351 |
1352 | // Merging the insertTexts, with the given options
1353 | options.insertTexts = extend({}, insertTexts, options.insertTexts || {});
1354 |
1355 |
1356 | // Merging the promptTexts, with the given options
1357 | options.promptTexts = promptTexts;
1358 |
1359 |
1360 | // Merging the blockStyles, with the given options
1361 | options.blockStyles = extend({}, blockStyles, options.blockStyles || {});
1362 |
1363 |
1364 | // Merging the shortcuts, with the given options
1365 | options.shortcuts = extend({}, shortcuts, options.shortcuts || {});
1366 |
1367 |
1368 | // Change unique_id to uniqueId for backwards compatibility
1369 | if(options.autosave != undefined && options.autosave.unique_id != undefined && options.autosave.unique_id != "")
1370 | options.autosave.uniqueId = options.autosave.unique_id;
1371 |
1372 |
1373 | // Update this options
1374 | this.options = options;
1375 |
1376 |
1377 | // Auto render
1378 | this.render();
1379 |
1380 |
1381 | // The codemirror component is only available after rendering
1382 | // so, the setter for the initialValue can only run after
1383 | // the element has been rendered
1384 | if(options.initialValue && (!this.options.autosave || this.options.autosave.foundSavedValue !== true)) {
1385 | this.value(options.initialValue);
1386 | }
1387 | }
1388 |
1389 | /**
1390 | * Default markdown render.
1391 | */
1392 | SimpleMDE.prototype.markdown = function(text) {
1393 | if(marked) {
1394 | // Initialize
1395 | var markedOptions = {};
1396 |
1397 |
1398 | // Update options
1399 | if(this.options && this.options.renderingConfig && this.options.renderingConfig.singleLineBreaks === false) {
1400 | markedOptions.breaks = false;
1401 | } else {
1402 | markedOptions.breaks = true;
1403 | }
1404 |
1405 | if(this.options && this.options.renderingConfig && this.options.renderingConfig.codeSyntaxHighlighting === true && window.hljs) {
1406 | markedOptions.highlight = function(code) {
1407 | return window.hljs.highlightAuto(code).value;
1408 | };
1409 | }
1410 |
1411 |
1412 | // Set options
1413 | marked.setOptions(markedOptions);
1414 |
1415 |
1416 | // Return
1417 | return marked(text);
1418 | }
1419 | };
1420 |
1421 | /**
1422 | * Render editor to the given element.
1423 | */
1424 | SimpleMDE.prototype.render = function(el) {
1425 | if(!el) {
1426 | el = this.element || document.getElementsByTagName("textarea")[0];
1427 | }
1428 |
1429 | if(this._rendered && this._rendered === el) {
1430 | // Already rendered.
1431 | return;
1432 | }
1433 |
1434 | this.element = el;
1435 | var options = this.options;
1436 |
1437 | var self = this;
1438 | var keyMaps = {};
1439 |
1440 | for(var key in options.shortcuts) {
1441 | // null stands for "do not bind this command"
1442 | if(options.shortcuts[key] !== null && bindings[key] !== null) {
1443 | (function(key) {
1444 | keyMaps[fixShortcut(options.shortcuts[key])] = function() {
1445 | bindings[key](self);
1446 | };
1447 | })(key);
1448 | }
1449 | }
1450 |
1451 | keyMaps["Enter"] = "newlineAndIndentContinueMarkdownList";
1452 | keyMaps["Tab"] = "tabAndIndentMarkdownList";
1453 | keyMaps["Shift-Tab"] = "shiftTabAndUnindentMarkdownList";
1454 | keyMaps["Esc"] = function(cm) {
1455 | if(cm.getOption("fullScreen")) toggleFullScreen(self);
1456 | };
1457 |
1458 | document.addEventListener("keydown", function(e) {
1459 | e = e || window.event;
1460 |
1461 | if(e.keyCode == 27) {
1462 | if(self.codemirror.getOption("fullScreen")) toggleFullScreen(self);
1463 | }
1464 | }, false);
1465 |
1466 | var mode, backdrop;
1467 | if(options.spellChecker !== false) {
1468 | mode = "spell-checker";
1469 | backdrop = options.parsingConfig;
1470 | backdrop.name = "gfm";
1471 | backdrop.gitHubSpice = false;
1472 |
1473 | CodeMirrorSpellChecker({
1474 | codeMirrorInstance: CodeMirror
1475 | });
1476 | } else {
1477 | mode = options.parsingConfig;
1478 | mode.name = "gfm";
1479 | mode.gitHubSpice = false;
1480 | }
1481 |
1482 | this.codemirror = CodeMirror.fromTextArea(el, {
1483 | mode: mode,
1484 | backdrop: backdrop,
1485 | theme: "paper",
1486 | tabSize: (options.tabSize != undefined) ? options.tabSize : 2,
1487 | indentUnit: (options.tabSize != undefined) ? options.tabSize : 2,
1488 | indentWithTabs: (options.indentWithTabs === false) ? false : true,
1489 | lineNumbers: false,
1490 | autofocus: (options.autofocus === true) ? true : false,
1491 | extraKeys: keyMaps,
1492 | lineWrapping: (options.lineWrapping === false) ? false : true,
1493 | allowDropFileTypes: ["text/plain"],
1494 | placeholder: options.placeholder || el.getAttribute("placeholder") || "",
1495 | styleSelectedText: (options.styleSelectedText != undefined) ? options.styleSelectedText : true
1496 | });
1497 |
1498 | if(options.forceSync === true) {
1499 | var cm = this.codemirror;
1500 | cm.on("change", function() {
1501 | cm.save();
1502 | });
1503 | }
1504 |
1505 | this.gui = {};
1506 |
1507 | if(options.toolbar !== false) {
1508 | this.gui.toolbar = this.createToolbar();
1509 | }
1510 | if(options.status !== false) {
1511 | this.gui.statusbar = this.createStatusbar();
1512 | }
1513 | if(options.autosave != undefined && options.autosave.enabled === true) {
1514 | this.autosave();
1515 | }
1516 |
1517 | this.gui.sideBySide = this.createSideBySide();
1518 |
1519 | this._rendered = this.element;
1520 |
1521 |
1522 | // Fixes CodeMirror bug (#344)
1523 | var temp_cm = this.codemirror;
1524 | setTimeout(function() {
1525 | temp_cm.refresh();
1526 | }.bind(temp_cm), 0);
1527 | };
1528 |
1529 | // Safari, in Private Browsing Mode, looks like it supports localStorage but all calls to setItem throw QuotaExceededError. We're going to detect this and set a variable accordingly.
1530 | function isLocalStorageAvailable() {
1531 | if(typeof localStorage === "object") {
1532 | try {
1533 | localStorage.setItem("smde_localStorage", 1);
1534 | localStorage.removeItem("smde_localStorage");
1535 | } catch(e) {
1536 | return false;
1537 | }
1538 | } else {
1539 | return false;
1540 | }
1541 |
1542 | return true;
1543 | }
1544 |
1545 | SimpleMDE.prototype.autosave = function() {
1546 | if(isLocalStorageAvailable()) {
1547 | var simplemde = this;
1548 |
1549 | if(this.options.autosave.uniqueId == undefined || this.options.autosave.uniqueId == "") {
1550 | console.log("SimpleMDE: You must set a uniqueId to use the autosave feature");
1551 | return;
1552 | }
1553 |
1554 | if(simplemde.element.form != null && simplemde.element.form != undefined) {
1555 | simplemde.element.form.addEventListener("submit", function() {
1556 | localStorage.removeItem("smde_" + simplemde.options.autosave.uniqueId);
1557 | });
1558 | }
1559 |
1560 | if(this.options.autosave.loaded !== true) {
1561 | if(typeof localStorage.getItem("smde_" + this.options.autosave.uniqueId) == "string" && localStorage.getItem("smde_" + this.options.autosave.uniqueId) != "") {
1562 | this.codemirror.setValue(localStorage.getItem("smde_" + this.options.autosave.uniqueId));
1563 | this.options.autosave.foundSavedValue = true;
1564 | }
1565 |
1566 | this.options.autosave.loaded = true;
1567 | }
1568 |
1569 | localStorage.setItem("smde_" + this.options.autosave.uniqueId, simplemde.value());
1570 |
1571 | var el = document.getElementById("autosaved");
1572 | if(el != null && el != undefined && el != "") {
1573 | var d = new Date();
1574 | var hh = d.getHours();
1575 | var m = d.getMinutes();
1576 | var dd = "am";
1577 | var h = hh;
1578 | if(h >= 12) {
1579 | h = hh - 12;
1580 | dd = "pm";
1581 | }
1582 | if(h == 0) {
1583 | h = 12;
1584 | }
1585 | m = m < 10 ? "0" + m : m;
1586 |
1587 | el.innerHTML = "Autosaved: " + h + ":" + m + " " + dd;
1588 | }
1589 |
1590 | this.autosaveTimeoutId = setTimeout(function() {
1591 | simplemde.autosave();
1592 | }, this.options.autosave.delay || 10000);
1593 | } else {
1594 | console.log("SimpleMDE: localStorage not available, cannot autosave");
1595 | }
1596 | };
1597 |
1598 | SimpleMDE.prototype.clearAutosavedValue = function() {
1599 | if(isLocalStorageAvailable()) {
1600 | if(this.options.autosave == undefined || this.options.autosave.uniqueId == undefined || this.options.autosave.uniqueId == "") {
1601 | console.log("SimpleMDE: You must set a uniqueId to clear the autosave value");
1602 | return;
1603 | }
1604 |
1605 | localStorage.removeItem("smde_" + this.options.autosave.uniqueId);
1606 | } else {
1607 | console.log("SimpleMDE: localStorage not available, cannot autosave");
1608 | }
1609 | };
1610 |
1611 | SimpleMDE.prototype.createSideBySide = function() {
1612 | var cm = this.codemirror;
1613 | var wrapper = cm.getWrapperElement();
1614 | var preview = wrapper.nextSibling;
1615 |
1616 | if(!preview || !/editor-preview-side/.test(preview.className)) {
1617 | preview = document.createElement("div");
1618 | preview.className = "editor-preview-side";
1619 | wrapper.parentNode.insertBefore(preview, wrapper.nextSibling);
1620 | }
1621 |
1622 | // Syncs scroll editor -> preview
1623 | var cScroll = false;
1624 | var pScroll = false;
1625 | cm.on("scroll", function(v) {
1626 | if(cScroll) {
1627 | cScroll = false;
1628 | return;
1629 | }
1630 | pScroll = true;
1631 | var height = v.getScrollInfo().height - v.getScrollInfo().clientHeight;
1632 | var ratio = parseFloat(v.getScrollInfo().top) / height;
1633 | var move = (preview.scrollHeight - preview.clientHeight) * ratio;
1634 | preview.scrollTop = move;
1635 | });
1636 |
1637 | // Syncs scroll preview -> editor
1638 | preview.onscroll = function() {
1639 | if(pScroll) {
1640 | pScroll = false;
1641 | return;
1642 | }
1643 | cScroll = true;
1644 | var height = preview.scrollHeight - preview.clientHeight;
1645 | var ratio = parseFloat(preview.scrollTop) / height;
1646 | var move = (cm.getScrollInfo().height - cm.getScrollInfo().clientHeight) * ratio;
1647 | cm.scrollTo(0, move);
1648 | };
1649 | return preview;
1650 | };
1651 |
1652 | SimpleMDE.prototype.createToolbar = function(items) {
1653 | items = items || this.options.toolbar;
1654 |
1655 | if(!items || items.length === 0) {
1656 | return;
1657 | }
1658 | var i;
1659 | for(i = 0; i < items.length; i++) {
1660 | if(toolbarBuiltInButtons[items[i]] != undefined) {
1661 | items[i] = toolbarBuiltInButtons[items[i]];
1662 | }
1663 | }
1664 |
1665 | var bar = document.createElement("div");
1666 | bar.className = "editor-toolbar";
1667 |
1668 | var self = this;
1669 |
1670 | var toolbarData = {};
1671 | self.toolbar = items;
1672 |
1673 | for(i = 0; i < items.length; i++) {
1674 | if(items[i].name == "guide" && self.options.toolbarGuideIcon === false)
1675 | continue;
1676 |
1677 | if(self.options.hideIcons && self.options.hideIcons.indexOf(items[i].name) != -1)
1678 | continue;
1679 |
1680 | // Fullscreen does not work well on mobile devices (even tablets)
1681 | // In the future, hopefully this can be resolved
1682 | if((items[i].name == "fullscreen" || items[i].name == "side-by-side") && isMobile())
1683 | continue;
1684 |
1685 |
1686 | // Don't include trailing separators
1687 | if(items[i] === "|") {
1688 | var nonSeparatorIconsFollow = false;
1689 |
1690 | for(var x = (i + 1); x < items.length; x++) {
1691 | if(items[x] !== "|" && (!self.options.hideIcons || self.options.hideIcons.indexOf(items[x].name) == -1)) {
1692 | nonSeparatorIconsFollow = true;
1693 | }
1694 | }
1695 |
1696 | if(!nonSeparatorIconsFollow)
1697 | continue;
1698 | }
1699 |
1700 |
1701 | // Create the icon and append to the toolbar
1702 | (function(item) {
1703 | var el;
1704 | if(item === "|") {
1705 | el = createSep();
1706 | } else {
1707 | el = createIcon(item, self.options.toolbarTips, self.options.shortcuts);
1708 | }
1709 |
1710 | // bind events, special for info
1711 | if(item.action) {
1712 | if(typeof item.action === "function") {
1713 | el.onclick = function(e) {
1714 | e.preventDefault();
1715 | item.action(self);
1716 | };
1717 | } else if(typeof item.action === "string") {
1718 | el.href = item.action;
1719 | el.target = "_blank";
1720 | }
1721 | }
1722 |
1723 | toolbarData[item.name || item] = el;
1724 | bar.appendChild(el);
1725 | })(items[i]);
1726 | }
1727 |
1728 | self.toolbarElements = toolbarData;
1729 |
1730 | var cm = this.codemirror;
1731 | cm.on("cursorActivity", function() {
1732 | var stat = getState(cm);
1733 |
1734 | for(var key in toolbarData) {
1735 | (function(key) {
1736 | var el = toolbarData[key];
1737 | if(stat[key]) {
1738 | el.className += " active";
1739 | } else if(key != "fullscreen" && key != "side-by-side") {
1740 | el.className = el.className.replace(/\s*active\s*/g, "");
1741 | }
1742 | })(key);
1743 | }
1744 | });
1745 |
1746 | var cmWrapper = cm.getWrapperElement();
1747 | cmWrapper.parentNode.insertBefore(bar, cmWrapper);
1748 | return bar;
1749 | };
1750 |
1751 | SimpleMDE.prototype.createStatusbar = function(status) {
1752 | // Initialize
1753 | status = status || this.options.status;
1754 | var options = this.options;
1755 | var cm = this.codemirror;
1756 |
1757 |
1758 | // Make sure the status variable is valid
1759 | if(!status || status.length === 0)
1760 | return;
1761 |
1762 |
1763 | // Set up the built-in items
1764 | var items = [];
1765 | var i, onUpdate, defaultValue;
1766 |
1767 | for(i = 0; i < status.length; i++) {
1768 | // Reset some values
1769 | onUpdate = undefined;
1770 | defaultValue = undefined;
1771 |
1772 |
1773 | // Handle if custom or not
1774 | if(typeof status[i] === "object") {
1775 | items.push({
1776 | className: status[i].className,
1777 | defaultValue: status[i].defaultValue,
1778 | onUpdate: status[i].onUpdate
1779 | });
1780 | } else {
1781 | var name = status[i];
1782 |
1783 | if(name === "words") {
1784 | defaultValue = function(el) {
1785 | el.innerHTML = wordCount(cm.getValue());
1786 | };
1787 | onUpdate = function(el) {
1788 | el.innerHTML = wordCount(cm.getValue());
1789 | };
1790 | } else if(name === "lines") {
1791 | defaultValue = function(el) {
1792 | el.innerHTML = cm.lineCount();
1793 | };
1794 | onUpdate = function(el) {
1795 | el.innerHTML = cm.lineCount();
1796 | };
1797 | } else if(name === "cursor") {
1798 | defaultValue = function(el) {
1799 | el.innerHTML = "0:0";
1800 | };
1801 | onUpdate = function(el) {
1802 | var pos = cm.getCursor();
1803 | el.innerHTML = pos.line + ":" + pos.ch;
1804 | };
1805 | } else if(name === "autosave") {
1806 | defaultValue = function(el) {
1807 | if(options.autosave != undefined && options.autosave.enabled === true) {
1808 | el.setAttribute("id", "autosaved");
1809 | }
1810 | };
1811 | }
1812 |
1813 | items.push({
1814 | className: name,
1815 | defaultValue: defaultValue,
1816 | onUpdate: onUpdate
1817 | });
1818 | }
1819 | }
1820 |
1821 |
1822 | // Create element for the status bar
1823 | var bar = document.createElement("div");
1824 | bar.className = "editor-statusbar";
1825 |
1826 |
1827 | // Create a new span for each item
1828 | for(i = 0; i < items.length; i++) {
1829 | // Store in temporary variable
1830 | var item = items[i];
1831 |
1832 |
1833 | // Create span element
1834 | var el = document.createElement("span");
1835 | el.className = item.className;
1836 |
1837 |
1838 | // Ensure the defaultValue is a function
1839 | if(typeof item.defaultValue === "function") {
1840 | item.defaultValue(el);
1841 | }
1842 |
1843 |
1844 | // Ensure the onUpdate is a function
1845 | if(typeof item.onUpdate === "function") {
1846 | // Create a closure around the span of the current action, then execute the onUpdate handler
1847 | this.codemirror.on("update", (function(el, item) {
1848 | return function() {
1849 | item.onUpdate(el);
1850 | };
1851 | }(el, item)));
1852 | }
1853 |
1854 |
1855 | // Append the item to the status bar
1856 | bar.appendChild(el);
1857 | }
1858 |
1859 |
1860 | // Insert the status bar into the DOM
1861 | var cmWrapper = this.codemirror.getWrapperElement();
1862 | cmWrapper.parentNode.insertBefore(bar, cmWrapper.nextSibling);
1863 | return bar;
1864 | };
1865 |
1866 | /**
1867 | * Get or set the text content.
1868 | */
1869 | SimpleMDE.prototype.value = function(val) {
1870 | if(val === undefined) {
1871 | return this.codemirror.getValue();
1872 | } else {
1873 | this.codemirror.getDoc().setValue(val);
1874 | return this;
1875 | }
1876 | };
1877 |
1878 |
1879 | /**
1880 | * Bind static methods for exports.
1881 | */
1882 | SimpleMDE.toggleBold = toggleBold;
1883 | SimpleMDE.toggleItalic = toggleItalic;
1884 | SimpleMDE.toggleStrikethrough = toggleStrikethrough;
1885 | SimpleMDE.toggleBlockquote = toggleBlockquote;
1886 | SimpleMDE.toggleHeadingSmaller = toggleHeadingSmaller;
1887 | SimpleMDE.toggleHeadingBigger = toggleHeadingBigger;
1888 | SimpleMDE.toggleHeading1 = toggleHeading1;
1889 | SimpleMDE.toggleHeading2 = toggleHeading2;
1890 | SimpleMDE.toggleHeading3 = toggleHeading3;
1891 | SimpleMDE.toggleCodeBlock = toggleCodeBlock;
1892 | SimpleMDE.toggleUnorderedList = toggleUnorderedList;
1893 | SimpleMDE.toggleOrderedList = toggleOrderedList;
1894 | SimpleMDE.cleanBlock = cleanBlock;
1895 | SimpleMDE.drawLink = drawLink;
1896 | SimpleMDE.drawImage = drawImage;
1897 | SimpleMDE.drawTable = drawTable;
1898 | SimpleMDE.drawHorizontalRule = drawHorizontalRule;
1899 | SimpleMDE.undo = undo;
1900 | SimpleMDE.redo = redo;
1901 | SimpleMDE.togglePreview = togglePreview;
1902 | SimpleMDE.toggleSideBySide = toggleSideBySide;
1903 | SimpleMDE.toggleFullScreen = toggleFullScreen;
1904 |
1905 | /**
1906 | * Bind instance methods for exports.
1907 | */
1908 | SimpleMDE.prototype.toggleBold = function() {
1909 | toggleBold(this);
1910 | };
1911 | SimpleMDE.prototype.toggleItalic = function() {
1912 | toggleItalic(this);
1913 | };
1914 | SimpleMDE.prototype.toggleStrikethrough = function() {
1915 | toggleStrikethrough(this);
1916 | };
1917 | SimpleMDE.prototype.toggleBlockquote = function() {
1918 | toggleBlockquote(this);
1919 | };
1920 | SimpleMDE.prototype.toggleHeadingSmaller = function() {
1921 | toggleHeadingSmaller(this);
1922 | };
1923 | SimpleMDE.prototype.toggleHeadingBigger = function() {
1924 | toggleHeadingBigger(this);
1925 | };
1926 | SimpleMDE.prototype.toggleHeading1 = function() {
1927 | toggleHeading1(this);
1928 | };
1929 | SimpleMDE.prototype.toggleHeading2 = function() {
1930 | toggleHeading2(this);
1931 | };
1932 | SimpleMDE.prototype.toggleHeading3 = function() {
1933 | toggleHeading3(this);
1934 | };
1935 | SimpleMDE.prototype.toggleCodeBlock = function() {
1936 | toggleCodeBlock(this);
1937 | };
1938 | SimpleMDE.prototype.toggleUnorderedList = function() {
1939 | toggleUnorderedList(this);
1940 | };
1941 | SimpleMDE.prototype.toggleOrderedList = function() {
1942 | toggleOrderedList(this);
1943 | };
1944 | SimpleMDE.prototype.cleanBlock = function() {
1945 | cleanBlock(this);
1946 | };
1947 | SimpleMDE.prototype.drawLink = function() {
1948 | drawLink(this);
1949 | };
1950 | SimpleMDE.prototype.drawImage = function() {
1951 | drawImage(this);
1952 | };
1953 | SimpleMDE.prototype.drawTable = function() {
1954 | drawTable(this);
1955 | };
1956 | SimpleMDE.prototype.drawHorizontalRule = function() {
1957 | drawHorizontalRule(this);
1958 | };
1959 | SimpleMDE.prototype.undo = function() {
1960 | undo(this);
1961 | };
1962 | SimpleMDE.prototype.redo = function() {
1963 | redo(this);
1964 | };
1965 | SimpleMDE.prototype.togglePreview = function() {
1966 | togglePreview(this);
1967 | };
1968 | SimpleMDE.prototype.toggleSideBySide = function() {
1969 | toggleSideBySide(this);
1970 | };
1971 | SimpleMDE.prototype.toggleFullScreen = function() {
1972 | toggleFullScreen(this);
1973 | };
1974 |
1975 | SimpleMDE.prototype.isPreviewActive = function() {
1976 | var cm = this.codemirror;
1977 | var wrapper = cm.getWrapperElement();
1978 | var preview = wrapper.lastChild;
1979 |
1980 | return /editor-preview-active/.test(preview.className);
1981 | };
1982 |
1983 | SimpleMDE.prototype.isSideBySideActive = function() {
1984 | var cm = this.codemirror;
1985 | var wrapper = cm.getWrapperElement();
1986 | var preview = wrapper.nextSibling;
1987 |
1988 | return /editor-preview-active-side/.test(preview.className);
1989 | };
1990 |
1991 | SimpleMDE.prototype.isFullscreenActive = function() {
1992 | var cm = this.codemirror;
1993 |
1994 | return cm.getOption("fullScreen");
1995 | };
1996 |
1997 | SimpleMDE.prototype.getState = function() {
1998 | var cm = this.codemirror;
1999 |
2000 | return getState(cm);
2001 | };
2002 |
2003 | SimpleMDE.prototype.toTextArea = function() {
2004 | var cm = this.codemirror;
2005 | var wrapper = cm.getWrapperElement();
2006 |
2007 | if(wrapper.parentNode) {
2008 | if(this.gui.toolbar) {
2009 | wrapper.parentNode.removeChild(this.gui.toolbar);
2010 | }
2011 | if(this.gui.statusbar) {
2012 | wrapper.parentNode.removeChild(this.gui.statusbar);
2013 | }
2014 | if(this.gui.sideBySide) {
2015 | wrapper.parentNode.removeChild(this.gui.sideBySide);
2016 | }
2017 | }
2018 |
2019 | cm.toTextArea();
2020 |
2021 | if(this.autosaveTimeoutId) {
2022 | clearTimeout(this.autosaveTimeoutId);
2023 | this.autosaveTimeoutId = undefined;
2024 | this.clearAutosavedValue();
2025 | }
2026 | };
2027 |
2028 | module.exports = SimpleMDE;
--------------------------------------------------------------------------------
@Model.Post.Comments.Count() Comments
18 | @foreach (var comment in Model.Post.Comments) 19 | { 20 | 36 | } 37 |