Move source code to src directory
55
src/Website/Controllers/AccountController.cs
Normal file
|
@ -0,0 +1,55 @@
|
|||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Website.Data;
|
||||
using Website.Models.Auth;
|
||||
using Website.ViewModels;
|
||||
|
||||
namespace Website.Controllers {
|
||||
public class AccountController:Controller {
|
||||
private readonly IAuthenticationProvider _authenticationProvider;
|
||||
|
||||
public AccountController(IAuthenticationProvider authenticationProvider) => _authenticationProvider = authenticationProvider;
|
||||
|
||||
[Authorize]
|
||||
public IActionResult Index() => View();
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult Login(string returnUrl, bool failedAttempt = false) {
|
||||
var model = new LoginViewModel {
|
||||
ReturnUrl = returnUrl,
|
||||
FailedAttempt = failedAttempt
|
||||
};
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Login(LoginRequest request) {
|
||||
try {
|
||||
var user = await _authenticationProvider.Authenticate(request);
|
||||
return user != null
|
||||
? await SetIdentityAndRedirect(request.ReturnUrl, user)
|
||||
: Login(request.ReturnUrl, true);
|
||||
}
|
||||
catch {
|
||||
return Login(request.ReturnUrl, true);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IActionResult> SetIdentityAndRedirect(string returnUrl, User user) {
|
||||
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
identity.AddClaim(new Claim(ClaimTypes.Name, user.Username));
|
||||
identity.AddClaim(new Claim(ClaimTypes.Email, user.Username));
|
||||
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
|
||||
|
||||
return string.IsNullOrEmpty(returnUrl)
|
||||
? (IActionResult) RedirectToAction(nameof(Index))
|
||||
: Redirect(returnUrl);
|
||||
}
|
||||
}
|
||||
}
|
86
src/Website/Controllers/BlogController.cs
Normal file
|
@ -0,0 +1,86 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Website.Data;
|
||||
using Website.Models.Blog;
|
||||
using Website.ViewModels.Blog;
|
||||
|
||||
namespace Website.Controllers {
|
||||
public class BlogController:Controller {
|
||||
private const int MaxPostsPerPage = 10;
|
||||
private readonly IBlogApi _api;
|
||||
|
||||
public BlogController(IBlogApi api) => _api = api;
|
||||
|
||||
public async Task<IActionResult> Page(int page) {
|
||||
var offset = (page - 1) * MaxPostsPerPage;
|
||||
var posts = (await _api.GetLatestPostsAsync(MaxPostsPerPage, offset)).Select(p => new BlogPostSnippetViewModel(p));
|
||||
var maxPages = (await _api.GetCountAsync()) / MaxPostsPerPage;
|
||||
var model = new BlogViewModel(posts, page, maxPages);
|
||||
return View(model);
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Entry(string url, bool preview = false) {
|
||||
try {
|
||||
var post = await _api.GetPostByUrlAsync(url);
|
||||
|
||||
if (!preview && string.IsNullOrEmpty(post.Content))
|
||||
return NotFound();
|
||||
|
||||
var model = new BlogPostViewModel(post, preview);
|
||||
return View(model);
|
||||
} catch (InvalidOperationException) {
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Edit(int? id) {
|
||||
if (!id.HasValue)
|
||||
return View();
|
||||
|
||||
try {
|
||||
var post = await _api.GetPostByIdAsync(id.Value);
|
||||
var model = new BlogPostSubmission {
|
||||
Id = post.Id,
|
||||
Title = post.Title,
|
||||
Content = post.Draft
|
||||
};
|
||||
return View(model);
|
||||
} catch (InvalidOperationException) {
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Save(BlogPostSubmission submission) {
|
||||
var savedPost = await _api.SavePost(submission);
|
||||
|
||||
return RedirectToAction(nameof(Edit), new { savedPost.Id });
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Manage() {
|
||||
var posts = await _api.GetAllPostsAsync();
|
||||
var models = posts.OrderByDescending(post => post.Timestamp).Select(post => new BlogPostViewModel(post, false));
|
||||
return View(models);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Publish(int id) {
|
||||
await _api.PublishPostAsync(id);
|
||||
|
||||
return RedirectToAction(nameof(Manage));
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Delete(int id) {
|
||||
await _api.DeletePostAsync(id);
|
||||
|
||||
return RedirectToAction(nameof(Manage));
|
||||
}
|
||||
}
|
||||
}
|
27
src/Website/Controllers/ErrorController.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Website.ViewModels;
|
||||
|
||||
namespace Website.Controllers
|
||||
{
|
||||
public class ErrorController : Controller
|
||||
{
|
||||
|
||||
public IActionResult BlowUp() {
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public IActionResult ServerError()
|
||||
{
|
||||
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||
}
|
||||
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public IActionResult PageNotFound()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
}
|
||||
}
|
32
src/Website/Controllers/HomeController.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Website.Data;
|
||||
using Website.ViewModels;
|
||||
using System.Linq;
|
||||
using Website.ViewModels.Blog;
|
||||
|
||||
namespace Website.Controllers {
|
||||
public class HomeController : Controller {
|
||||
private readonly IBlogApi _blogApi;
|
||||
private readonly IGitApi _api;
|
||||
|
||||
public HomeController(IBlogApi blogApi, IGitApi api) {
|
||||
_api = api;
|
||||
_blogApi = blogApi;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Index() {
|
||||
var post = await _blogApi.GetLatestPostAsync();
|
||||
var repo = (await _api.GetRepositories("rob")).First();
|
||||
var branch = (await _api.GetBranches("rob", repo.Name)).First();
|
||||
var commit = await _api.GetCommit("rob", repo.Name, branch.Commit.Id);
|
||||
|
||||
var model = new HomeViewModel {
|
||||
BlogPost = new BlogPostViewModel(post, false),
|
||||
GitCommit = new GitCommitViewModel(repo, branch, commit)
|
||||
};
|
||||
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
}
|
12
src/Website/Data/ApiCallException.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Website.Data {
|
||||
public class ApiCallException : Exception {
|
||||
private readonly HttpResponseMessage Response;
|
||||
|
||||
public ApiCallException(HttpResponseMessage response):base($"Error calling API {response.RequestMessage.RequestUri}: {(int)response.StatusCode}, {response.ReasonPhrase}") {
|
||||
Response = response;
|
||||
}
|
||||
}
|
||||
}
|
48
src/Website/Data/ApiClient.cs
Normal file
|
@ -0,0 +1,48 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Website.Data {
|
||||
public abstract class ApiClient {
|
||||
private readonly HttpClient _client;
|
||||
|
||||
protected ApiClient(HttpClient client) {
|
||||
_client = client;
|
||||
}
|
||||
|
||||
private IDictionary<string, string> ParseQueryParameters(object query) {
|
||||
var type = query.GetType();
|
||||
var props = type.GetProperties();
|
||||
return props.ToDictionary(info => info.Name, info => info.GetValue(query, null).ToString());
|
||||
}
|
||||
|
||||
private async Task<T> Send<T>(HttpMethod method, string url, object query, HttpContent content = null) {
|
||||
if (query != null)
|
||||
url = QueryHelpers.AddQueryString(url, ParseQueryParameters(query));
|
||||
|
||||
using var httpRequest = new HttpRequestMessage(method, url) { Content = content };
|
||||
var response = await _client.SendAsync(httpRequest);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
throw new ApiCallException(response);
|
||||
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
return JsonConvert.DeserializeObject<T>(json);
|
||||
}
|
||||
|
||||
protected async Task<T> Post<T>(string url, object value, object query = null) {
|
||||
var json = JsonConvert.SerializeObject(value);
|
||||
using var requestBody = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
return await Send<T>(HttpMethod.Post, url, query, requestBody);
|
||||
}
|
||||
|
||||
protected async Task<T> Get<T>(string url, object query = null) {
|
||||
return await Send<T>(HttpMethod.Get, url, query);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
19
src/Website/Data/AuthenticationProvider.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Website.Models.Auth;
|
||||
|
||||
namespace Website.Data {
|
||||
public class AuthenticationProvider:ApiClient, IAuthenticationProvider {
|
||||
public AuthenticationProvider(HttpClient client) : base(client) {
|
||||
}
|
||||
|
||||
public async Task<User> Authenticate(LoginRequest request) {
|
||||
try {
|
||||
return await Post<User>("authenticate", request);
|
||||
}
|
||||
catch (ApiCallException) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
22
src/Website/Data/BlogApi.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using Website.Models.Blog;
|
||||
|
||||
namespace Website.Data
|
||||
{
|
||||
public class BlogApi : ApiClient, IBlogApi {
|
||||
public BlogApi(HttpClient client) : base(client) {
|
||||
}
|
||||
|
||||
public async Task<BlogPost> GetPostByUrlAsync(string url) => await Get<BlogPost>("get/" + url);
|
||||
public async Task<IEnumerable<BlogPost>> GetLatestPostsAsync(int count = 0, int offset = 0) => await Get<IEnumerable<BlogPost>>("getlatestposts", new{count, offset});
|
||||
public async Task<BlogPost> GetLatestPostAsync() => await Get<BlogPost>("getlatestpost");
|
||||
public async Task<int> GetCountAsync() => await Get<int>("getcount");
|
||||
public async Task<BlogPost> GetPostByIdAsync(int id) => await Get<BlogPost>("get/" + id);
|
||||
public async Task<BlogPost> SavePost(BlogPostSubmission post) => await Post<BlogPost>("savepost", post);
|
||||
public async Task<IEnumerable<BlogPost>> GetAllPostsAsync() => await Get<IEnumerable<BlogPost>>("getallposts");
|
||||
public async Task DeletePostAsync(int id) => await Post<object>("deletepost", id);
|
||||
public async Task PublishPostAsync(int id) => await Post<object>("publishpost", id);
|
||||
}
|
||||
}
|
16
src/Website/Data/GitApi.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Website.Models.Git;
|
||||
|
||||
namespace Website.Data {
|
||||
public class GitApi : ApiClient, IGitApi {
|
||||
|
||||
public GitApi(HttpClient client) : base(client) {
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Repository>> GetRepositories(string user) => await Get<IEnumerable<Repository>>("repositories", new { user });
|
||||
public async Task<IEnumerable<Branch>> GetBranches(string user, string repository) => await Get<IEnumerable<Branch>>("branches", new {user, repository});
|
||||
public async Task<Commit> GetCommit(string user, string repository, string hash) => await Get<Commit>("commit", new {user, repository, hash});
|
||||
}
|
||||
}
|
8
src/Website/Data/IAuthenticationProvider.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
using System.Threading.Tasks;
|
||||
using Website.Models.Auth;
|
||||
|
||||
namespace Website.Data {
|
||||
public interface IAuthenticationProvider {
|
||||
Task<User> Authenticate(LoginRequest request);
|
||||
}
|
||||
}
|
17
src/Website/Data/IBlogApi.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Website.Models.Blog;
|
||||
|
||||
namespace Website.Data {
|
||||
public interface IBlogApi {
|
||||
Task<BlogPost> GetPostByUrlAsync(string url);
|
||||
Task<IEnumerable<BlogPost>> GetLatestPostsAsync(int count = 0, int offset = 0);
|
||||
Task<BlogPost> GetLatestPostAsync();
|
||||
Task<int> GetCountAsync();
|
||||
Task<BlogPost> GetPostByIdAsync(int id);
|
||||
Task<BlogPost> SavePost(BlogPostSubmission post);
|
||||
Task<IEnumerable<BlogPost>> GetAllPostsAsync();
|
||||
Task DeletePostAsync(int id);
|
||||
Task PublishPostAsync(int id);
|
||||
}
|
||||
}
|
11
src/Website/Data/IGitApi.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Website.Models.Git;
|
||||
|
||||
namespace Website.Data {
|
||||
public interface IGitApi {
|
||||
Task<IEnumerable<Repository>> GetRepositories(string user);
|
||||
Task<IEnumerable<Branch>> GetBranches(string user, string repository);
|
||||
Task<Commit> GetCommit(string user, string repository, string hash);
|
||||
}
|
||||
}
|
25
src/Website/Extensions/DateTimeExtensions.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
|
||||
namespace Website.Extensions {
|
||||
public static class DateTimeExtensions {
|
||||
private static string GetDaySuffix(int day) {
|
||||
switch (day) {
|
||||
case 1:
|
||||
case 21:
|
||||
case 31:
|
||||
return @"\s\t";
|
||||
case 2:
|
||||
case 22:
|
||||
return @"\n\d";
|
||||
case 3:
|
||||
case 23:
|
||||
return @"\r\d";
|
||||
default:
|
||||
return @"\t\h";
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetDaySuffix(this DateTime dateTime) => GetDaySuffix(dateTime.Day);
|
||||
public static string GetDaySuffix(this DateTimeOffset dateTimeOffset) => GetDaySuffix(dateTimeOffset.Day);
|
||||
}
|
||||
}
|
7
src/Website/Models/Auth/LoginRequest.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Website.Models.Auth {
|
||||
public class LoginRequest {
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string ReturnUrl { get; set; }
|
||||
}
|
||||
}
|
6
src/Website/Models/Auth/User.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace Website.Models.Auth {
|
||||
public class User {
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
}
|
||||
}
|
15
src/Website/Models/Blog/BlogPost.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
|
||||
namespace Website.Models.Blog
|
||||
{
|
||||
public class BlogPost
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Content { get; set; }
|
||||
public DateTime Timestamp { get; set; }
|
||||
public string Draft { get; set; }
|
||||
public string Url { get; set; }
|
||||
public int UserId { get; set; }
|
||||
}
|
||||
}
|
7
src/Website/Models/Blog/BlogPostSubmission.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Website.Models.Blog {
|
||||
public class BlogPostSubmission {
|
||||
public int? Id { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Content { get; set; }
|
||||
}
|
||||
}
|
6
src/Website/Models/Git/Branch.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace Website.Models.Git {
|
||||
public class Branch {
|
||||
public string Name { get; set; }
|
||||
public Commit Commit { get; set; }
|
||||
}
|
||||
}
|
10
src/Website/Models/Git/Commit.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System;
|
||||
|
||||
namespace Website.Models.Git {
|
||||
public class Commit {
|
||||
public string Id { get; set; }
|
||||
public string Message { get; set; }
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
public string Url { get; set; }
|
||||
}
|
||||
}
|
6
src/Website/Models/Git/Repository.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace Website.Models.Git {
|
||||
public class Repository {
|
||||
public string Name { get; set; }
|
||||
public string Url { get; set; }
|
||||
}
|
||||
}
|
17
src/Website/Program.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
||||
namespace Website
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateWebHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
|
||||
WebHost.CreateDefaultBuilder(args)
|
||||
.UseStartup<Startup>();
|
||||
}
|
||||
}
|
27
src/Website/Properties/launchSettings.json
Normal file
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:12894",
|
||||
"sslPort": 44394
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"Website": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
86
src/Website/Startup.cs
Normal file
|
@ -0,0 +1,86 @@
|
|||
using System;
|
||||
using System.Net.Http;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Website.Data;
|
||||
|
||||
namespace Website
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.Configure<CookiePolicyOptions>(options =>
|
||||
{
|
||||
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
|
||||
options.CheckConsentNeeded = context => true;
|
||||
options.MinimumSameSitePolicy = SameSiteMode.None;
|
||||
});
|
||||
|
||||
services.AddSingleton(Configuration);
|
||||
|
||||
services.AddHttpClient<IGitApi, GitApi>(client => client.BaseAddress = new Uri(Configuration["gitApiEndpoint"]))
|
||||
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler {ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true});
|
||||
|
||||
services.AddHttpClient<IBlogApi, BlogApi>(client => client.BaseAddress = new Uri(Configuration["blogApiEndpoint"]))
|
||||
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler {ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true});
|
||||
|
||||
services.AddHttpClient<IAuthenticationProvider, AuthenticationProvider>(client => client.BaseAddress = new Uri(Configuration["authApiEndpoint"]))
|
||||
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler {ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true});
|
||||
|
||||
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
|
||||
|
||||
services.AddMvc(options => options.EnableEndpointRouting = false)
|
||||
#if DEBUG
|
||||
.AddRazorRuntimeCompilation()
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
else
|
||||
{
|
||||
app.UseExceptionHandler("/Error/ServerError");
|
||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
app.UseStatusCodePagesWithReExecute("/Error/PageNotFound")
|
||||
.UseHttpsRedirection()
|
||||
.UseStaticFiles()
|
||||
.UseAuthentication()
|
||||
.UseMvc(routes => {
|
||||
routes.MapRoute(
|
||||
name: "blogPages",
|
||||
template: "blog/{action}/{page:int}",
|
||||
defaults: new {controller = "Blog", action = "Page", page = 1});
|
||||
routes.MapRoute(
|
||||
name: "blogView",
|
||||
template: "blog/view/{url}",
|
||||
defaults: new {controller = "Blog", action = "Entry"});
|
||||
routes.MapRoute(
|
||||
name: "default",
|
||||
template: "{controller=Home}/{action=Index}/{id?}");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
18
src/Website/ViewComponents/BlogNavigationViewComponent.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Website.Data;
|
||||
|
||||
namespace Website.ViewComponents {
|
||||
public class BlogNavigationViewComponent:ViewComponent {
|
||||
private readonly IBlogApi _repo;
|
||||
|
||||
public BlogNavigationViewComponent(IBlogApi repo) {
|
||||
_repo = repo;
|
||||
}
|
||||
|
||||
public async Task<IViewComponentResult> InvokeAsync() {
|
||||
var posts = await _repo.GetLatestPostsAsync(5);
|
||||
return View(posts);
|
||||
}
|
||||
}
|
||||
}
|
19
src/Website/ViewComponents/ProjectNavigationViewComponent.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Website.Data;
|
||||
|
||||
namespace Website.ViewComponents {
|
||||
public class ProjectNavigationViewComponent :ViewComponent {
|
||||
private readonly IGitApi _api;
|
||||
|
||||
public ProjectNavigationViewComponent(IGitApi api) {
|
||||
_api = api;
|
||||
}
|
||||
|
||||
public async Task<IViewComponentResult> InvokeAsync() {
|
||||
var repositories = await _api.GetRepositories("rob");
|
||||
return View(repositories.Take(5));
|
||||
}
|
||||
}
|
||||
}
|
9
src/Website/ViewModels/Blog/BlogPostSnippetViewModel.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using Website.Models.Blog;
|
||||
|
||||
namespace Website.ViewModels.Blog
|
||||
{
|
||||
public class BlogPostSnippetViewModel:BlogPostViewModel
|
||||
{
|
||||
public BlogPostSnippetViewModel(BlogPost blogPost) : base(blogPost, false) => Content = Content.Length < 1000 ? Content : Content.Substring(0, 1000);
|
||||
}
|
||||
}
|
37
src/Website/ViewModels/Blog/BlogPostViewModel.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using Markdig;
|
||||
using Pek.Markdig.HighlightJs;
|
||||
using Website.Extensions;
|
||||
using Website.Markdig.Extensions;
|
||||
using Website.Models.Blog;
|
||||
|
||||
namespace Website.ViewModels.Blog
|
||||
{
|
||||
public class BlogPostViewModel
|
||||
{
|
||||
public BlogPostViewModel(BlogPost blogPost, bool preview) {
|
||||
Id = blogPost.Id;
|
||||
Title = blogPost.Title;
|
||||
Timestamp = FormatTimestamp(blogPost.Timestamp);
|
||||
Url = blogPost.Url;
|
||||
Content = preview ? blogPost.Draft : blogPost.Content;
|
||||
}
|
||||
|
||||
private static string FormatTimestamp(DateTime timestamp) => timestamp.ToString($@"dddd \t\h\e d{timestamp.GetDaySuffix()} \o\f MMMM yyyy");
|
||||
|
||||
private static MarkdownPipeline GetPipeline() => new MarkdownPipelineBuilder()
|
||||
//.UseAdvancedExtensions()
|
||||
.UseAutoLinks()
|
||||
.UseSoftlineBreakAsHardlineBreak()
|
||||
.UseHighlightJs()
|
||||
.UseBlogRenderer()
|
||||
.Build();
|
||||
|
||||
public int Id { get; }
|
||||
public string Title { get; }
|
||||
public string Content { get; protected set; }
|
||||
public string Timestamp { get; }
|
||||
public string Url { get; }
|
||||
public string ContentHtml => Markdown.ToHtml(Content, GetPipeline()).Trim();
|
||||
}
|
||||
}
|
18
src/Website/ViewModels/Blog/BlogViewModel.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Website.ViewModels.Blog
|
||||
{
|
||||
public class BlogViewModel
|
||||
{
|
||||
public BlogViewModel(IEnumerable<BlogPostViewModel> posts, int page, int maxPages)
|
||||
{
|
||||
Posts = posts;
|
||||
Page = page;
|
||||
MaxPages = maxPages;
|
||||
}
|
||||
|
||||
public IEnumerable<BlogPostViewModel> Posts { get; }
|
||||
public int Page { get; }
|
||||
public int MaxPages { get; }
|
||||
}
|
||||
}
|
9
src/Website/ViewModels/ErrorViewModel.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Website.ViewModels
|
||||
{
|
||||
public class ErrorViewModel
|
||||
{
|
||||
public string RequestId { get; set; }
|
||||
|
||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
}
|
||||
}
|
27
src/Website/ViewModels/GitCommitViewModel.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using Website.Extensions;
|
||||
using Website.Models.Git;
|
||||
|
||||
namespace Website.ViewModels {
|
||||
public class GitCommitViewModel {
|
||||
public GitCommitViewModel(Repository repo, Branch branch, Commit commit) {
|
||||
RepositoryName = repo.Name;
|
||||
RepositoryUrl = repo.Url;
|
||||
BranchName = branch.Name;
|
||||
Hash = commit.Id;
|
||||
CommitUrl = commit.Url;
|
||||
CommitMessage = commit.Message;
|
||||
Timestamp = FormatTimestamp(commit.Timestamp);
|
||||
}
|
||||
|
||||
private static string FormatTimestamp(DateTimeOffset timestamp) => timestamp.ToString($@"HH:mm:ss on dddd \t\h\e d{timestamp.GetDaySuffix()} \o\f MMMM yyyy");
|
||||
|
||||
public string RepositoryName { get; }
|
||||
public string RepositoryUrl { get; }
|
||||
public string BranchName { get; }
|
||||
public string Hash { get; }
|
||||
public string CommitUrl { get; }
|
||||
public string CommitMessage { get; }
|
||||
public string Timestamp { get; }
|
||||
}
|
||||
}
|
8
src/Website/ViewModels/HomeViewModel.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
using Website.ViewModels.Blog;
|
||||
|
||||
namespace Website.ViewModels {
|
||||
public class HomeViewModel {
|
||||
public BlogPostViewModel BlogPost { get; set; }
|
||||
public GitCommitViewModel GitCommit { get; internal set; }
|
||||
}
|
||||
}
|
6
src/Website/ViewModels/LoginViewModel.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace Website.ViewModels {
|
||||
public class LoginViewModel {
|
||||
public string ReturnUrl { get; set; }
|
||||
public bool FailedAttempt { get; set; }
|
||||
}
|
||||
}
|
8
src/Website/Views/Account/Index.cshtml
Normal file
|
@ -0,0 +1,8 @@
|
|||
@model object
|
||||
|
||||
@{
|
||||
ViewBag.Title = "title";
|
||||
Layout = "_Layout";
|
||||
}
|
||||
|
||||
<h2>title</h2>
|
40
src/Website/Views/Account/Login.cshtml
Normal file
|
@ -0,0 +1,40 @@
|
|||
@model LoginViewModel
|
||||
|
||||
@{
|
||||
ViewBag.Title = "Login";
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(Model.ReturnUrl)) {
|
||||
<div>Please log in to perform that action</div>
|
||||
}
|
||||
|
||||
@if (Model.FailedAttempt) {
|
||||
<div>Could not log in with those credentials</div>
|
||||
}
|
||||
|
||||
<form method="post" action="login">
|
||||
<input type="hidden" name="returnUrl" value="@Model.ReturnUrl" />
|
||||
<table>
|
||||
<tr class="form-row">
|
||||
<td>
|
||||
<label for="username">Username: </label>
|
||||
</td>
|
||||
<td>
|
||||
<input name="username" id="username" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="form-row">
|
||||
<td>
|
||||
<label for="password">Password: </label>
|
||||
</td>
|
||||
<td>
|
||||
<input name="password" type="password" id="password" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="form-row">
|
||||
<td colspan="2">
|
||||
<button type="submit">Login</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
21
src/Website/Views/Blog/Edit.cshtml
Normal file
|
@ -0,0 +1,21 @@
|
|||
@model Website.Models.Blog.BlogPostSubmission
|
||||
|
||||
@{
|
||||
ViewBag.Title = Model != null ? "Edit Post" : "New Post";
|
||||
}
|
||||
|
||||
<form method="post" action="/blog/save" class="edit-form">
|
||||
<input type="hidden" name="id" value="@Model?.Id"/>
|
||||
<div class="edit-form__title form-row">
|
||||
<label for="postTitle" class="edit-form__title__label">Title: </label>
|
||||
<input name="title" id="postTitle" value="@Model?.Title" class="edit-form__title__input"/>
|
||||
</div>
|
||||
<textarea id="postContent" name="content">@Model?.Content</textarea>
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js"></script>
|
||||
<script>
|
||||
var simpleMde = new SimpleMDE({ element: document.getElementById("postContent"), tabSize: 4 });
|
||||
</script>
|
11
src/Website/Views/Blog/Entry.cshtml
Normal file
|
@ -0,0 +1,11 @@
|
|||
@model Website.ViewModels.Blog.BlogPostViewModel
|
||||
|
||||
@{
|
||||
ViewData["Title"] = Model.Title;
|
||||
}
|
||||
|
||||
<article class="blog-post">
|
||||
@Html.Raw(Model.ContentHtml)
|
||||
</article>
|
||||
|
||||
<small>Posted on @Model.Timestamp</small>
|
19
src/Website/Views/Blog/Manage.cshtml
Normal file
|
@ -0,0 +1,19 @@
|
|||
@model IEnumerable<Website.ViewModels.Blog.BlogPostViewModel>
|
||||
|
||||
@{
|
||||
ViewBag.Title = "Manage posts";
|
||||
}
|
||||
|
||||
<a href="/blog/edit">New Post</a>
|
||||
|
||||
<table class="post-list">
|
||||
@foreach (var post in Model) {
|
||||
<tr class="post-list__post">
|
||||
<td><a href="/blog/view/@post.Url">@post.Title</a></td>
|
||||
<td><a href="edit?id=@post.Id"><img src="/images/edit.svg" alt="Edit" title="Edit"/></a></td>
|
||||
<td><a href="/blog/view/@post.Url?preview=true"><img src="/images/blog.svg" alt="Preview" title="Preview"/></a></td>
|
||||
<td><a href="publish?id=@post.Id"><img src="/images/send.svg" alt="Publish" title="Publish" onclick="return confirm('Are you sure?')"/></a></td>
|
||||
<td><a href="delete?id=@post.Id"><img src="/images/delete.svg" alt="Delete" title="Delete" onclick="return confirm('Are you sure?')"/></a></td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
23
src/Website/Views/Blog/Page.cshtml
Normal file
|
@ -0,0 +1,23 @@
|
|||
@model Website.ViewModels.Blog.BlogViewModel
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Blog";
|
||||
}
|
||||
|
||||
@foreach (var post in Model.Posts) {
|
||||
<article class="blog-post">
|
||||
<h2><a href="/blog/view/@post.Url">@post.Title</a></h2>
|
||||
@Html.Raw(post.ContentHtml)
|
||||
<small>Posted on @post.Timestamp</small>
|
||||
<p><a href="/blog/view/@post.Url">Read more...</a></p>
|
||||
</article>
|
||||
}
|
||||
|
||||
<div class="blog-navigation">
|
||||
@if (Model.Page > 1) {
|
||||
<a href="/blog/page/@(Model.Page - 1)" class="round-button blog-navigation__previous"><img src="/images/previous.svg" alt="Previous"/></a>
|
||||
}
|
||||
@if (Model.Page < Model.MaxPages) {
|
||||
<a href="/blog/page/@(Model.Page + 1)" class="round-button blog-navigation__next"><img src="/images/next.svg" alt="Next"/></a>
|
||||
}
|
||||
</div>
|
7
src/Website/Views/Error/PageNotFound.cshtml
Normal file
|
@ -0,0 +1,7 @@
|
|||
@model ErrorViewModel
|
||||
@{
|
||||
ViewData["Title"] = "404 Not Found";
|
||||
}
|
||||
|
||||
<h1>404 Not Found - page unavailable.</h1>
|
||||
This page cannot be found.
|
25
src/Website/Views/Error/ServerError.cshtml
Normal file
|
@ -0,0 +1,25 @@
|
|||
@model ErrorViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Error";
|
||||
}
|
||||
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
@if (Model.ShowRequestId)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||
It can result in displaying sensitive information from exceptions to end users.
|
||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||
and restarting the app.
|
||||
</p>
|
23
src/Website/Views/Home/Index.cshtml
Normal file
|
@ -0,0 +1,23 @@
|
|||
@model HomeViewModel
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Welcome";
|
||||
}
|
||||
|
||||
<p>Hello, I'm Rob. I'm a senior software engineer at <a href="https://www.codecomputerlove.com/" target="_blank">Code Computerlove</a> where I focus on back end development primarily using C#. In my spare time I spend my time riding bikes or making stuff, typically involving an Arduino.</p>
|
||||
<p>This website is primarily an outlet for me to write about things which have been technically challenging, either in a professional or personal capacity, though not limited to that.</p>
|
||||
<p>If you wish to get in contact, then get in touch via my <a href="http://uk.linkedin.com/in/robertmarshall/" target="_blank"><img alt="LinkedIn profile" src="https://static.licdn.com/scds/common/u/img/webpromo/btn_liprofile_blue_80x15.png" title=""></a>.</p>
|
||||
|
||||
<h2>Latest code commit</h2>
|
||||
<table class="commit-table">
|
||||
<tr><td>Repository</td><td><a href="@Model.GitCommit.RepositoryUrl">@Model.GitCommit.RepositoryName</a></td></tr>
|
||||
<tr><td>Branch</td><td>@Model.GitCommit.BranchName</td></tr>
|
||||
<tr><td>SHA</td><td><a href="@Model.GitCommit.CommitUrl">@Model.GitCommit.Hash</a></td></tr>
|
||||
<tr><td>Message</td><td>@Model.GitCommit.CommitMessage</td></tr>
|
||||
<tr><td>Timestamp</td><td>@Model.GitCommit.Timestamp</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>Latest Blog Post</h2>
|
||||
<h3><a href="/blog/view/@Model.BlogPost.Url">@Model.BlogPost.Title</a></h3>
|
||||
<partial name="~/Views/Blog/Entry.cshtml" , model="Model.BlogPost" />
|
||||
<p><a href="/blog">View more</a></p>
|
|
@ -0,0 +1,13 @@
|
|||
@model System.Collections.Generic.IEnumerable<Website.Models.Blog.BlogPost>
|
||||
|
||||
<h5>Blog:</h5>
|
||||
<ul class="nav-list">
|
||||
@foreach (var post in Model) {
|
||||
<li class="nav-list__link"><a href="/blog/view/@post.Url">@post.Title</a></li>
|
||||
}
|
||||
<li class="nav-list__link"><a href="/blog">View All</a></li>
|
||||
|
||||
@if (User.Identity.IsAuthenticated) {
|
||||
<li class="nav-list__link"><a href="/blog/manage">Manage</a></li>
|
||||
}
|
||||
</ul>
|
|
@ -0,0 +1,9 @@
|
|||
@model IEnumerable<Website.Models.Git.Repository>
|
||||
|
||||
<h5>Projects:</h5>
|
||||
<ul class="nav-list">
|
||||
@foreach (var repository in Model) {
|
||||
<li class="nav-list__link"><a href="@repository.Url" target="_blank">@repository.Name</a></li>
|
||||
}
|
||||
<li class="nav-list__link"><a href="//git.robware.uk" target="_blank">View All</a></li>
|
||||
</ul>
|
40
src/Website/Views/Shared/_Layout.cshtml
Normal file
|
@ -0,0 +1,40 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@ViewData["Title"] | Robware</title>
|
||||
<link href="/css/style.css" rel="stylesheet" defer/>
|
||||
<script type="text/javascript" src="/js/javascript.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="header">
|
||||
<a class="header__blurb" href="/">
|
||||
<span class="header__blurb__title">Robware</span>
|
||||
<small class="header__blurb__tagline">Software by Rob</small>
|
||||
</a>
|
||||
<div class="header__headshot"></div>
|
||||
</header>
|
||||
<div id="content">
|
||||
<main>
|
||||
<h1>@ViewData["Title"]</h1>
|
||||
@RenderBody()
|
||||
</main>
|
||||
<nav>
|
||||
<label for="menu-toggle"><img class="nav-open" src="/images/menu.svg" /></label>
|
||||
<input type="checkbox" id="menu-toggle" />
|
||||
<div class="nav-container">
|
||||
@await Component.InvokeAsync("BlogNavigation")
|
||||
@await Component.InvokeAsync("ProjectNavigation")
|
||||
</div>
|
||||
<div class="strava">
|
||||
<iframe height='160' width='250' frameborder='0' allowtransparency='true' scrolling='no' src='https://www.strava.com/athletes/8892667/activity-summary/df1a6e21a9947dbe7a9dd36f86b1c17185002d22'></iframe>
|
||||
<iframe height='454' width='250' frameborder='0' allowtransparency='true' scrolling='no' src='https://www.strava.com/athletes/8892667/latest-rides/df1a6e21a9947dbe7a9dd36f86b1c17185002d22'></iframe>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
<footer>
|
||||
<div class="logo" onclick="window.location='/account'"></div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
18
src/Website/Views/Shared/_ValidationScriptsPartial.cshtml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<environment include="Development">
|
||||
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
|
||||
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
|
||||
</environment>
|
||||
<environment exclude="Development">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.17.0/jquery.validate.min.js"
|
||||
asp-fallback-src="~/lib/jquery-validation/dist/jquery.validate.min.js"
|
||||
asp-fallback-test="window.jQuery && window.jQuery.validator"
|
||||
crossorigin="anonymous"
|
||||
integrity="sha256-F6h55Qw6sweK+t7SiOJX+2bpSAa3b/fnlrVCJvmEj1A=">
|
||||
</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.11/jquery.validate.unobtrusive.min.js"
|
||||
asp-fallback-src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
|
||||
asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive"
|
||||
crossorigin="anonymous"
|
||||
integrity="sha256-9GycpJnliUjJDVDqP0UEu/bsm9U+3dnQUH8+3W10vkY=">
|
||||
</script>
|
||||
</environment>
|
3
src/Website/Views/_ViewImports.cshtml
Normal file
|
@ -0,0 +1,3 @@
|
|||
@using Website
|
||||
@using Website.ViewModels
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
3
src/Website/Views/_ViewStart.cshtml
Normal file
|
@ -0,0 +1,3 @@
|
|||
@{
|
||||
Layout = "_Layout";
|
||||
}
|
26
src/Website/Website.csproj
Normal file
|
@ -0,0 +1,26 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BuildBundlerMinifier" Version="3.2.435" />
|
||||
<PackageReference Include="markdig" Version="0.18.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="Pek.Markdig.HighlightJs" Version="0.5.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Website.Markdig.Extensions\Website.Markdig.Extensions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="BuildCss" BeforeTargets="Build">
|
||||
<Exec WorkingDirectory="$(ProjectDir)" Command="npm install" />
|
||||
<Exec WorkingDirectory="$(ProjectDir)" Command="npx gulp compile_scss" />
|
||||
</Target>
|
||||
</Project>
|
12
src/Website/appsettings.Development.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
},
|
||||
"blogApiEndpoint": "",
|
||||
"gitApiEndpoint": "",
|
||||
"authApiEndpoint": ""
|
||||
}
|
18
src/Website/appsettings.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
},
|
||||
"blogApiEndpoint": "<BlogEndpoint>",
|
||||
"gitApiEndpoint": "<GitEndpoint>",
|
||||
"authApiEndpoint": "<AuthEndpoint>",
|
||||
"AllowedHosts": "*",
|
||||
"Kestrel": {
|
||||
"EndPoints": {
|
||||
"Http": {
|
||||
"Url": "http://0.0.0.0:5000"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
13
src/Website/bundleconfig.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
[
|
||||
{
|
||||
"outputFileName": "wwwroot/js/javascript.min.js",
|
||||
"inputFiles": [
|
||||
"wwwroot/js/javascript.js"
|
||||
],
|
||||
"minify": {
|
||||
"enabled": true,
|
||||
"renameLocals": true
|
||||
},
|
||||
"sourceMap": false
|
||||
}
|
||||
]
|
14
src/Website/gulpfile.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
var gulp = require("gulp"),
|
||||
sass = require("gulp-sass"),
|
||||
cleanCss = require("gulp-clean-css");
|
||||
|
||||
gulp.task("compile_scss", function () {
|
||||
return gulp.src('wwwroot/css/**/*.scss')
|
||||
.pipe(sass())
|
||||
.pipe(cleanCss())
|
||||
.pipe(gulp.dest('wwwroot/css'));
|
||||
});
|
||||
|
||||
gulp.task("watch", function () {
|
||||
gulp.watch("wwwroot/css/**/*.scss", gulp.series("compile_scss"));
|
||||
});
|
4886
src/Website/package-lock.json
generated
Normal file
21
src/Website/package.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "website",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"devDependencies": {
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-clean-css": "^4.0.0",
|
||||
"gulp-less": "^4.0.1",
|
||||
"gulp-sass": "^4.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.12.1"
|
||||
},
|
||||
"scripts": {
|
||||
"gulp": "gulp"
|
||||
},
|
||||
"author": "Robert Marshall",
|
||||
"license": "ISC",
|
||||
"private": true
|
||||
}
|
157
src/Website/wwwroot/css/blog.scss
Normal file
|
@ -0,0 +1,157 @@
|
|||
@import "colours.scss";
|
||||
|
||||
.blog-post {
|
||||
&__image-container {
|
||||
max-width: 100%;
|
||||
padding: 10px;
|
||||
background: $Grey-200;
|
||||
display: inline-block;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
|
||||
&--container {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&--image {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
&--tagline {
|
||||
margin-top: 10px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
:not(pre)>code {
|
||||
padding: 2px;
|
||||
background: $Grey-200;
|
||||
border-radius: 2px;
|
||||
border: 1px solid $Grey-400;
|
||||
}
|
||||
|
||||
pre>code {
|
||||
border-left: 4px solid $primary;
|
||||
margin-bottom: 10px;
|
||||
overflow: auto;
|
||||
padding: 6px;
|
||||
padding-left: 10px;
|
||||
tab-size: 4;
|
||||
-moz-tab-size: 4;
|
||||
|
||||
/**
|
||||
* Obsidian style
|
||||
* ported by Alexander Marenin (http://github.com/ioncreature)
|
||||
*/
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
background: #282b2e;
|
||||
color: #e0e2e4;
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-literal,
|
||||
.hljs-selector-id {
|
||||
color: #93c763;
|
||||
}
|
||||
|
||||
.hljs-number {
|
||||
color: #ffcd22;
|
||||
}
|
||||
|
||||
.hljs-attribute {
|
||||
color: #668bb0;
|
||||
}
|
||||
|
||||
.hljs-code,
|
||||
.hljs-class .hljs-title,
|
||||
.hljs-section {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.hljs-regexp,
|
||||
.hljs-link {
|
||||
color: #d39745;
|
||||
}
|
||||
|
||||
.hljs-meta {
|
||||
color: #557182;
|
||||
}
|
||||
|
||||
.hljs-tag,
|
||||
.hljs-name,
|
||||
.hljs-bullet,
|
||||
.hljs-subst,
|
||||
.hljs-emphasis,
|
||||
.hljs-type,
|
||||
.hljs-built_in,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo,
|
||||
.hljs-addition,
|
||||
.hljs-variable,
|
||||
.hljs-template-tag,
|
||||
.hljs-template-variable {
|
||||
color: #8cbbad;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-symbol {
|
||||
color: #ec7600;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-quote,
|
||||
.hljs-deletion {
|
||||
color: #818e96;
|
||||
}
|
||||
|
||||
.hljs-selector-class {
|
||||
color: #A082BD
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-literal,
|
||||
.hljs-doctag,
|
||||
.hljs-title,
|
||||
.hljs-section,
|
||||
.hljs-type,
|
||||
.hljs-name,
|
||||
.hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.blog-navigation {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
&__next {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-form {
|
||||
&__title {
|
||||
display: flex;
|
||||
|
||||
&__label {
|
||||
flex: 0 0 auto;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
&__input {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.post-list {
|
||||
border-collapse: collapse;
|
||||
|
||||
&__post:hover {
|
||||
background: $Grey-300;
|
||||
}
|
||||
}
|
23
src/Website/wwwroot/css/colours.scss
Normal file
|
@ -0,0 +1,23 @@
|
|||
@import "material_colours.scss";
|
||||
|
||||
$background:$Grey-50;
|
||||
|
||||
$primary-100:$Red-100;
|
||||
$primary-200:$Red-200;
|
||||
$primary-300:$Red-300;
|
||||
$primary-400:$Red-400;
|
||||
$primary-500:$Red-500;
|
||||
$primary-600:$Red-600;
|
||||
$primary-700:$Red-700;
|
||||
$primary-800:$Red-800;
|
||||
$primary-900:$Red-900;
|
||||
|
||||
$accent-100:$Teal-100;
|
||||
$accent-200:$Teal-200;
|
||||
$accent-400:$Teal-400;
|
||||
$accent-700:$Teal-700;
|
||||
|
||||
$primary: #e32c28; //$primary-600;
|
||||
$accent:$accent-400;
|
||||
$control:$Grey-200;
|
||||
$control2:$Grey-300;
|
5
src/Website/wwwroot/css/home.scss
Normal file
|
@ -0,0 +1,5 @@
|
|||
.commit-table {
|
||||
td:first-child(){
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
272
src/Website/wwwroot/css/material_colours.scss
Normal file
|
@ -0,0 +1,272 @@
|
|||
$Red-50:#ffebee;
|
||||
$Red-100:#ffcdd2;
|
||||
$Red-200:#ef9a9a;
|
||||
$Red-300:#e57373;
|
||||
$Red-400:#ef5350;
|
||||
$Red-500:#f44336;
|
||||
$Red-600:#e53935;
|
||||
$Red-700:#d32f2f;
|
||||
$Red-800:#c62828;
|
||||
$Red-900:#b71c1c;
|
||||
$Red-A100:#ff8a80;
|
||||
$Red-A200:#ff5252;
|
||||
$Red-A400:#ff1744;
|
||||
$Red-A700:#d50000;
|
||||
|
||||
$Pink-50:#fce4ec;
|
||||
$Pink-100:#f8bbd0;
|
||||
$Pink-200:#f48fb1;
|
||||
$Pink-300:#f06292;
|
||||
$Pink-400:#ec407a;
|
||||
$Pink-500:#e91e63;
|
||||
$Pink-600:#d81b60;
|
||||
$Pink-700:#c2185b;
|
||||
$Pink-800:#ad1457;
|
||||
$Pink-900:#880e4f;
|
||||
$Pink-A100:#ff80ab;
|
||||
$Pink-A200:#ff4081;
|
||||
$Pink-A400:#f50057;
|
||||
$Pink-A700:#c51162;
|
||||
|
||||
$Purple-50:#f3e5f5;
|
||||
$Purple-100:#e1bee7;
|
||||
$Purple-200:#ce93d8;
|
||||
$Purple-300:#ba68c8;
|
||||
$Purple-400:#ab47bc;
|
||||
$Purple-500:#9c27b0;
|
||||
$Purple-600:#8e24aa;
|
||||
$Purple-700:#7b1fa2;
|
||||
$Purple-800:#6a1b9a;
|
||||
$Purple-900:#4a148c;
|
||||
$Purple-A100:#ea80fc;
|
||||
$Purple-A200:#e040fb;
|
||||
$Purple-A400:#d500f9;
|
||||
$Purple-A700:#aa00ff;
|
||||
|
||||
$DeepPurple-50:#ede7f6;
|
||||
$DeepPurple-50100:#d1c4e9;
|
||||
$DeepPurple-50200:#b39ddb;
|
||||
$DeepPurple-50300:#9575cd;
|
||||
$DeepPurple-50400:#7e57c2;
|
||||
$DeepPurple-50500:#673ab7;
|
||||
$DeepPurple-50600:#5e35b1;
|
||||
$DeepPurple-50700:#512da8;
|
||||
$DeepPurple-50800:#4527a0;
|
||||
$DeepPurple-50900:#311b92;
|
||||
$DeepPurple-50A100:#b388ff;
|
||||
$DeepPurple-50A200:#7c4dff;
|
||||
$DeepPurple-50A400:#651fff;
|
||||
$DeepPurple-50A700:#6200ea;
|
||||
|
||||
$Indigo-50:#e8eaf6;
|
||||
$Indigo-100:#c5cae9;
|
||||
$Indigo-200:#9fa8da;
|
||||
$Indigo-300:#7986cb;
|
||||
$Indigo-400:#5c6bc0;
|
||||
$Indigo-500:#3f51b5;
|
||||
$Indigo-600:#3949ab;
|
||||
$Indigo-700:#303f9f;
|
||||
$Indigo-800:#283593;
|
||||
$Indigo-900:#1a237e;
|
||||
$Indigo-A100:#8c9eff;
|
||||
$Indigo-A200:#536dfe;
|
||||
$Indigo-A400:#3d5afe;
|
||||
$Indigo-A700:#304ffe;
|
||||
|
||||
$Blue-50:#e3f2fd;
|
||||
$Blue-100:#bbdefb;
|
||||
$Blue-200:#90caf9;
|
||||
$Blue-300:#64b5f6;
|
||||
$Blue-400:#42a5f5;
|
||||
$Blue-500:#2196f3;
|
||||
$Blue-600:#1e88e5;
|
||||
$Blue-700:#1976d2;
|
||||
$Blue-800:#1565c0;
|
||||
$Blue-900:#0d47a1;
|
||||
$Blue-A100:#82b1ff;
|
||||
$Blue-A200:#448aff;
|
||||
$Blue-A400:#2979ff;
|
||||
$Blue-A700:#2962ff;
|
||||
|
||||
$LightBlue-50:#e1f5fe;
|
||||
$LightBlue-100:#b3e5fc;
|
||||
$LightBlue-200:#81d4fa;
|
||||
$LightBlue-300:#4fc3f7;
|
||||
$LightBlue-400:#29b6f6;
|
||||
$LightBlue-500:#03a9f4;
|
||||
$LightBlue-600:#039be5;
|
||||
$LightBlue-700:#0288d1;
|
||||
$LightBlue-800:#0277bd;
|
||||
$LightBlue-900:#01579b;
|
||||
$LightBlue-A100:#80d8ff;
|
||||
$LightBlue-A200:#40c4ff;
|
||||
$LightBlue-A400:#00b0ff;
|
||||
$LightBlue-A700:#0091ea;
|
||||
|
||||
$Cyan-50:#e0f7fa;
|
||||
$Cyan-100:#b2ebf2;
|
||||
$Cyan-200:#80deea;
|
||||
$Cyan-300:#4dd0e1;
|
||||
$Cyan-400:#26c6da;
|
||||
$Cyan-500:#00bcd4;
|
||||
$Cyan-600:#00acc1;
|
||||
$Cyan-700:#0097a7;
|
||||
$Cyan-800:#00838f;
|
||||
$Cyan-900:#006064;
|
||||
$Cyan-A100:#84ffff;
|
||||
$Cyan-A200:#18ffff;
|
||||
$Cyan-A400:#00e5ff;
|
||||
$Cyan-A700:#00b8d4;
|
||||
|
||||
$Teal-50:#e0f2f1;
|
||||
$Teal-100:#b2dfdb;
|
||||
$Teal-200:#80cbc4;
|
||||
$Teal-300:#4db6ac;
|
||||
$Teal-400:#26a69a;
|
||||
$Teal-500:#009688;
|
||||
$Teal-600:#00897b;
|
||||
$Teal-700:#00796b;
|
||||
$Teal-800:#00695c;
|
||||
$Teal-900:#004d40;
|
||||
$Teal-A100:#a7ffeb;
|
||||
$Teal-A200:#64ffda;
|
||||
$Teal-A400:#1de9b6;
|
||||
$Teal-A700:#00bfa5;
|
||||
|
||||
$Green-50:#e8f5e9;
|
||||
$Green-100:#c8e6c9;
|
||||
$Green-200:#a5d6a7;
|
||||
$Green-300:#81c784;
|
||||
$Green-400:#66bb6a;
|
||||
$Green-500:#4caf50;
|
||||
$Green-600:#43a047;
|
||||
$Green-700:#388e3c;
|
||||
$Green-800:#2e7d32;
|
||||
$Green-900:#1b5e20;
|
||||
$Green-A100:#b9f6ca;
|
||||
$Green-A200:#69f0ae;
|
||||
$Green-A400:#00e676;
|
||||
$Green-A700:#00c853;
|
||||
|
||||
$LightGreen-50:#f1f8e9;
|
||||
$LightGreen-100:#dcedc8;
|
||||
$LightGreen-200:#c5e1a5;
|
||||
$LightGreen-300:#aed581;
|
||||
$LightGreen-400:#9ccc65;
|
||||
$LightGreen-500:#8bc34a;
|
||||
$LightGreen-600:#7cb342;
|
||||
$LightGreen-700:#689f38;
|
||||
$LightGreen-800:#558b2f;
|
||||
$LightGreen-900:#33691e;
|
||||
$LightGreen-A100:#ccff90;
|
||||
$LightGreen-A200:#b2ff59;
|
||||
$LightGreen-A400:#76ff03;
|
||||
$LightGreen-A700:#64dd17;
|
||||
|
||||
$Lime-50:#f9fbe7;
|
||||
$Lime-100:#f0f4c3;
|
||||
$Lime-200:#e6ee9c;
|
||||
$Lime-300:#dce775;
|
||||
$Lime-400:#d4e157;
|
||||
$Lime-500:#cddc39;
|
||||
$Lime-600:#c0ca33;
|
||||
$Lime-700:#afb42b;
|
||||
$Lime-800:#9e9d24;
|
||||
$Lime-900:#827717;
|
||||
$Lime-A100:#f4ff81;
|
||||
$Lime-A200:#eeff41;
|
||||
$Lime-A400:#c6ff00;
|
||||
$Lime-A700:#aeea00;
|
||||
|
||||
$Yellow-50:#fffde7;
|
||||
$Yellow-100:#fff9c4;
|
||||
$Yellow-200:#fff59d;
|
||||
$Yellow-300:#fff176;
|
||||
$Yellow-400:#ffee58;
|
||||
$Yellow-500:#ffeb3b;
|
||||
$Yellow-600:#fdd835;
|
||||
$Yellow-700:#fbc02d;
|
||||
$Yellow-800:#f9a825;
|
||||
$Yellow-900:#f57f17;
|
||||
$Yellow-A100:#ffff8d;
|
||||
$Yellow-A200:#ffff00;
|
||||
$Yellow-A400:#ffea00;
|
||||
$Yellow-A700:#ffd600;
|
||||
|
||||
$Amber-50:#fff8e1;
|
||||
$Amber-100:#ffecb3;
|
||||
$Amber-200:#ffe082;
|
||||
$Amber-300:#ffd54f;
|
||||
$Amber-400:#ffca28;
|
||||
$Amber-500:#ffc107;
|
||||
$Amber-600:#ffb300;
|
||||
$Amber-700:#ffa000;
|
||||
$Amber-800:#ff8f00;
|
||||
$Amber-900:#ff6f00;
|
||||
$Amber-A100:#ffe57f;
|
||||
$Amber-A200:#ffd740;
|
||||
$Amber-A400:#ffc400;
|
||||
$Amber-A700:#ffab00;
|
||||
|
||||
$Orange-50:#fff3e0;
|
||||
$Orange-100:#ffe0b2;
|
||||
$Orange-200:#ffcc80;
|
||||
$Orange-300:#ffb74d;
|
||||
$Orange-400:#ffa726;
|
||||
$Orange-500:#ff9800;
|
||||
$Orange-600:#fb8c00;
|
||||
$Orange-700:#f57c00;
|
||||
$Orange-800:#ef6c00;
|
||||
$Orange-900:#e65100;
|
||||
$Orange-A100:#ffd180;
|
||||
$Orange-A200:#ffab40;
|
||||
$Orange-A400:#ff9100;
|
||||
$Orange-A700:#ff6d00;
|
||||
|
||||
$DeepOrange-50:#fbe9e7;
|
||||
$DeepOrange-100:#ffccbc;
|
||||
$DeepOrange-200:#ffab91;
|
||||
$DeepOrange-300:#ff8a65;
|
||||
$DeepOrange-400:#ff7043;
|
||||
$DeepOrange-500:#ff5722;
|
||||
$DeepOrange-600:#f4511e;
|
||||
$DeepOrange-700:#e64a19;
|
||||
$DeepOrange-800:#d84315;
|
||||
$DeepOrange-900:#bf360c;
|
||||
$DeepOrange-A100:#ff9e80;
|
||||
$DeepOrange-A200:#ff6e40;
|
||||
$DeepOrange-A400:#ff3d00;
|
||||
$DeepOrange-A700:#dd2c00;
|
||||
|
||||
$Brown-50:#efebe9;
|
||||
$Brown-100:#d7ccc8;
|
||||
$Brown-200:#bcaaa4;
|
||||
$Brown-300:#a1887f;
|
||||
$Brown-400:#8d6e63;
|
||||
$Brown-500:#795548;
|
||||
$Brown-600:#6d4c41;
|
||||
$Brown-700:#5d4037;
|
||||
$Brown-800:#4e342e;
|
||||
$Brown-900:#3e2723;
|
||||
|
||||
$Grey-50:#fafafa;
|
||||
$Grey-100:#f5f5f5;
|
||||
$Grey-200:#eeeeee;
|
||||
$Grey-300:#e0e0e0;
|
||||
$Grey-400:#bdbdbd;
|
||||
$Grey-500:#9e9e9e;
|
||||
$Grey-600:#757575;
|
||||
$Grey-700:#616161;
|
||||
$Grey-800:#424242;
|
||||
$Grey-900:#212121;
|
||||
|
||||
$BlueGrey-50:#eceff1;
|
||||
$BlueGrey-100:#cfd8dc;
|
||||
$BlueGrey-200:#b0bec5;
|
||||
$BlueGrey-300:#90a4ae;
|
||||
$BlueGrey-400:#78909c;
|
||||
$BlueGrey-500:#607d8b;
|
||||
$BlueGrey-600:#546e7a;
|
||||
$BlueGrey-700:#455a64;
|
||||
$BlueGrey-800:#37474f;
|
||||
$BlueGrey-900:#263238;
|
277
src/Website/wwwroot/css/style.scss
Normal file
|
@ -0,0 +1,277 @@
|
|||
@import url('https://fonts.googleapis.com/css?family=Public+Sans&display=swap');
|
||||
@import "colours.scss";
|
||||
$fa-font-path: "../webfonts";
|
||||
@import "../../node_modules/@fortawesome/fontawesome-free/scss/fontawesome.scss";
|
||||
@import "../../node_modules/@fortawesome/fontawesome-free/scss/solid.scss";
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
html {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: url(/images/background.png);
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: 'Public Sans', sans-serif;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: $primary;
|
||||
color: white
|
||||
}
|
||||
|
||||
body {
|
||||
max-width: 1280px;
|
||||
min-height: 100%;
|
||||
margin: auto;
|
||||
background: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $primary;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
|
||||
background: linear-gradient(to bottom, $primary 0%, $primary 0%);
|
||||
background-position: 0 100%;
|
||||
background-size: 0px 0px;
|
||||
background-repeat: repeat-y;
|
||||
transition: all 0.3s ease-in-out 0s;
|
||||
|
||||
&:hover {
|
||||
color: white;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
&[href^="http"] {
|
||||
&:after {
|
||||
@extend %fa-icon;
|
||||
@extend .fas;
|
||||
content: fa-content($fa-var-external-link-alt);
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 40px 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
background-color: black;
|
||||
overflow: hidden;
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
background: url(/images/header.jpg) no-repeat;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
top: -2px;
|
||||
bottom: -2px;
|
||||
left: -2px;
|
||||
right: -2px;
|
||||
filter: blur(2px);
|
||||
}
|
||||
|
||||
&__blurb {
|
||||
background-color: rgba(white, 0.8);
|
||||
color: black;
|
||||
padding: 30px;
|
||||
width: 50%;
|
||||
z-index: 0;
|
||||
display: block;
|
||||
|
||||
&__title {
|
||||
font-size: 32px;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&__tagline {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&__headshot {
|
||||
flex: 0 0 auto;
|
||||
background: url(/images/headshot.jpg) center no-repeat;
|
||||
background-size: cover;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
border: 5px solid white;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 1);
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#content {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
main {
|
||||
flex: 1 1 auto;
|
||||
padding: 30px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
nav {
|
||||
flex: 0 0 250px;
|
||||
border-left: 1px solid $Grey-400;
|
||||
margin: 20px 0;
|
||||
padding: 0 10px;
|
||||
|
||||
#menu-toggle,
|
||||
[for="menu-toggle"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-list {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
&__link {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 20px;
|
||||
margin-top: auto;
|
||||
|
||||
.logo {
|
||||
background: url(/images/logo.png) no-repeat center;
|
||||
background-size: contain;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
button,
|
||||
.button {
|
||||
background: $accent;
|
||||
border: none;
|
||||
box-shadow: 0px 2px 3px 0px rgba(0, 0, 0, 0.25);
|
||||
padding: 10px 20px;
|
||||
border-radius: 50px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.round-button {
|
||||
border-radius: 50%;
|
||||
background: $accent;
|
||||
padding: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
box-shadow: 0px 2px 3px 0px rgba(0, 0, 0, 0.25);
|
||||
margin: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
form {
|
||||
.form-row {
|
||||
margin: 5px 0;
|
||||
}
|
||||
}
|
||||
|
||||
@import "blog.scss";
|
||||
@import "home.scss";
|
||||
|
||||
|
||||
@media screen and (max-width: 992px) {
|
||||
#content {
|
||||
flex-direction: column-reverse;
|
||||
|
||||
main {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
flex: 1 1 auto;
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 30px;
|
||||
|
||||
.nav-open {
|
||||
filter: invert(100%);
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
#menu-toggle:checked+.nav-container {
|
||||
max-height: 1000px;
|
||||
margin-top: 20px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[for="menu-toggle"] {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: all 0.5s ease-out;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.strava {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20px 10px;
|
||||
|
||||
&__blurb {
|
||||
padding: 20px 10px;
|
||||
}
|
||||
|
||||
&__headshot {
|
||||
width: 40vw;
|
||||
height: 40vw;
|
||||
max-width: 200px;
|
||||
max-height: 200px;
|
||||
}
|
||||
}
|
||||
}
|
12
src/Website/wwwroot/images/add.svg
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<path fill="#CFD8DC" d="M19.967,7.972v12c0,1.1-0.9,2-2,2H5.958c-1.1,0-1.99-0.9-1.99-2l0.01-16c0-1.1,0.89-2,1.99-2h8L19.967,7.972
|
||||
z"/>
|
||||
<path fill="none" d="M0,0h24v24H0V0z"/>
|
||||
<polygon fill="#455A64" points="16,16 13,16 13,19 11,19 11,16 8,16 8,14 11,14 11,11 13,11 13,14 16,14 "/>
|
||||
<polygon opacity="0.5" fill="#90A4AE" enable-background="new " points="19.967,8.067 19.967,9.067 13.967,9.067 13.967,2.067
|
||||
"/>
|
||||
</svg>
|
After Width: | Height: | Size: 895 B |
11
src/Website/wwwroot/images/add_album.svg
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<path fill="none" d="M0,0h24v24H0V0z"/>
|
||||
<path fill="#4CAF50" enable-background="new " d="M4,6H2v14c0,1.1,0.9,2,2,2h14v-2H4V6z"/>
|
||||
<path fill="#66BB6A" d="M22,4v12c0,1.1-0.9,2-2,2H8c-1.1,0-2-0.9-2-2V4c0-1.1,0.9-2,2-2h12C21.1,2,22,2.9,22,4z"/>
|
||||
<path opacity="0.7" fill="#4CAF50" enable-background="new " d="M22,4v12c0,1.1-0.9,2-2,2h-6.04V2H20C21.1,2,22,2.9,22,4z"/>
|
||||
<polygon fill="#FFFFFF" points="19,11 15,11 15,15 13,15 13,11 9,11 9,9 13,9 13,5 15,5 15,9 19,9 "/>
|
||||
</svg>
|
After Width: | Height: | Size: 950 B |
BIN
src/Website/wwwroot/images/background.png
Normal file
After Width: | Height: | Size: 817 B |
9
src/Website/wwwroot/images/blog.svg
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<path fill="#CFD8DC" d="M20,2H4C2.9,2,2.01,2.9,2.01,4L2,22l4-4h14c1.1,0,2-0.9,2-2V4C22,2.9,21.1,2,20,2z"/>
|
||||
<path fill="#607D8B" d="M6,9h12v2H6V9z M14,14H6v-2h8V14z M18,8H6V6h12V8z"/>
|
||||
<path fill="none" d="M0,0h24v24H0V0z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 703 B |
BIN
src/Website/wwwroot/images/chevron.png
Normal file
After Width: | Height: | Size: 48 KiB |
12
src/Website/wwwroot/images/delete.svg
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<path fill="#9E9E9E" d="M6,19c0,1.1,0.9,2,2,2h8c1.1,0,2-0.9,2-2V6H6V19z"/>
|
||||
<polygon fill="#616161" points="19,4 15.5,4 14.5,3 9.5,3 8.5,4 5,4 5,6 19,6 "/>
|
||||
<path fill="none" d="M0,0h24v24H0V0z"/>
|
||||
<rect x="8" y="8" fill="#757575" width="2" height="11"/>
|
||||
<rect x="12" y="8" fill="#757575" width="2" height="11"/>
|
||||
<rect x="16" y="8" fill="#757575" width="2" height="11"/>
|
||||
</svg>
|
After Width: | Height: | Size: 851 B |
11
src/Website/wwwroot/images/desktop.svg
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<path fill="none" d="M0,0h24v24H0V0z"/>
|
||||
<path fill="#546E7A" d="M23,4v12c0,1.1-0.9,2-2,2h-7v2h2v2H8v-2h2v-2H3c-1.1,0-2-0.9-2-2V4c0-1.1,0.9-2,2-2h18C22.1,2,23,2.9,23,4z"
|
||||
/>
|
||||
<rect x="3" y="4" fill="#B3E5FC" width="18" height="12"/>
|
||||
<rect x="10" y="17.98" fill="#455A64" width="4" height="2.02"/>
|
||||
</svg>
|
After Width: | Height: | Size: 776 B |
9
src/Website/wwwroot/images/done.svg
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<path fill="none" d="M0,0h24v24H0V0z"/>
|
||||
<path fill="#81C784" d="M9,19L21,7l-1.41-1.41L9,16.17V19z"/>
|
||||
<path fill="#4CAF50" d="M9,16.17L4.83,12l-1.42,1.41L9,19V16.17z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 648 B |
12
src/Website/wwwroot/images/edit.svg
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<polygon fill="#CFD8DC" points="17.266,10.453 19.094,8.656 15.344,4.891 13.703,6.547 "/>
|
||||
<polygon fill="#78909C" points="17.811,9.94 6.75,21 3,21 3,17.25 14.061,6.19 "/>
|
||||
<path fill="#B0BEC5" d="M20.71,7.04c0.39-0.39,0.39-1.02,0-1.41l-2.34-2.34c-0.39-0.39-1.021-0.39-1.41,0l-1.83,1.83l3.75,3.75
|
||||
L20.71,7.04z"/>
|
||||
<path fill="none" d="M0,0h24v24H0V0z"/>
|
||||
<polygon fill="#B0BEC5" points="6.75,21 3,21 3,17.25 "/>
|
||||
</svg>
|
After Width: | Height: | Size: 890 B |
10
src/Website/wwwroot/images/error.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<path fill="none" d="M0,0h24v24H0V0z"/>
|
||||
<path fill="#E57373" d="M12,2C6.48,2,2,6.48,2,12c0,5.52,4.48,10,10,10c5.52,0,10-4.48,10-10C22,6.48,17.52,2,12,2z"/>
|
||||
<path fill="#F44336" d="M12,4c-4.417,0-8,3.583-8,8c0,4.414,3.583,8,8,8c4.414,0,8-3.586,8-8C20,7.583,16.414,4,12,4z"/>
|
||||
<path fill="#CFD8DC" d="M13,17h-2v-2h2V17z M13,13h-2V7h2V13z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 819 B |
11
src/Website/wwwroot/images/gallery.svg
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<path fill="none" d="M0,0h24v24H0V0z"/>
|
||||
<path fill="#B3E5FC" d="M22,4v12c0,1.1-0.9,2-2,2H8c-1.1,0-2-0.9-2-2V4c0-1.1,0.9-2,2-2h12C21.1,2,22,2.9,22,4z"/>
|
||||
<path fill="#81D4FA" d="M22,4v12c0,1.1-0.9,2-2,2h-6.03V2H20C21.1,2,22,2.9,22,4z"/>
|
||||
<polygon fill="#66BB6A" points="11,12 13.029,14.71 16,11 20,16 8,16 "/>
|
||||
<path fill="#81D4FA" d="M2,6v14c0,1.1,0.9,2,2,2h14v-2H4V6H2z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 852 B |
BIN
src/Website/wwwroot/images/header.jpg
Normal file
After Width: | Height: | Size: 272 KiB |
BIN
src/Website/wwwroot/images/headshot.jpg
Normal file
After Width: | Height: | Size: 52 KiB |
10
src/Website/wwwroot/images/home.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<path fill="none" d="M0,0h24v24H0V0z"/>
|
||||
<rect x="9.375" y="13.292" fill="#795548" width="5.333" height="6.708"/>
|
||||
<polygon fill="#D7CCC8" points="5,12 5,20 10,20 10,14 14,14 14,20 19,20 19,12 19.021,12 12.01,5.691 "/>
|
||||
<polygon fill="#F44336" points="12,3 2,12 5,12 12.01,5.691 19.021,12 22,12 "/>
|
||||
</svg>
|
After Width: | Height: | Size: 777 B |
13
src/Website/wwwroot/images/library.svg
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<path fill="none" d="M0,0h24v24H0V0z"/>
|
||||
<path fill="#6D4C41" d="M4,6H2v14c0,1.1,0.9,2,2,2h14v-2H4V6z"/>
|
||||
<path fill="#8D6E63" d="M22,4v12c0,1.1-0.9,2-2,2H8c-1.1,0-2-0.9-2-2V4c0-1.1,0.9-2,2-2h12C21.1,2,22,2.9,22,4z"/>
|
||||
<path fill="#6D4C41" d="M22,4v12c0,1.1-0.9,2-2,2h-6.04V2H20C21.1,2,22,2.9,22,4z"/>
|
||||
<rect x="9" y="9" fill="#FFFFFF" width="10" height="2"/>
|
||||
<rect x="9" y="13" fill="#FFFFFF" width="6" height="2"/>
|
||||
<rect x="9" y="5" fill="#FFFFFF" width="10" height="2"/>
|
||||
</svg>
|
After Width: | Height: | Size: 953 B |
BIN
src/Website/wwwroot/images/logo.png
Normal file
After Width: | Height: | Size: 184 KiB |
BIN
src/Website/wwwroot/images/logo_email.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
10
src/Website/wwwroot/images/member.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<circle fill="#1976D2" cx="12" cy="8" r="4"/>
|
||||
<path fill="#1565C0" d="M12,14c-2.67,0-8,1.34-8,4v2h16v-2C20,15.34,14.67,14,12,14z"/>
|
||||
<path fill="none" d="M0,0h24v24H0V0z"/>
|
||||
<polygon fill="none" points="62,-15 62,9 52.88,9 52.88,-12.92 38,-12.92 38,-15 "/>
|
||||
</svg>
|
After Width: | Height: | Size: 735 B |
10
src/Website/wwwroot/images/menu.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<path fill="none" d="M0,0h24v24H0V0z"/>
|
||||
<rect x="3" y="16" fill="#FFFFFF" width="18" height="2"/>
|
||||
<rect x="3" y="11" fill="#FFFFFF" width="18" height="2"/>
|
||||
<rect x="3" y="6" fill="#FFFFFF" width="18" height="2"/>
|
||||
</svg>
|
After Width: | Height: | Size: 693 B |
8
src/Website/wwwroot/images/next.svg
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<path fill="#FFFFFF" d="M10,6L8.59,7.41L13.17,12l-4.58,4.59L10,18l6-6L10,6z"/>
|
||||
<path fill="none" d="M0,0h24v24H0V0z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 597 B |
13
src/Website/wwwroot/images/photo.svg
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<path fill="none" d="M0,0h24v24H0V0z"/>
|
||||
<path fill="#BBDEFB" d="M21,19V5c0-1.1-0.9-2-2-2H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14C20.1,21,21,20.1,21,19z M8.5,13.5
|
||||
l2.5,3.01L14.5,12l4.5,6H5L8.5,13.5z"/>
|
||||
<g>
|
||||
<polygon fill="#43A047" points="19,17.979 5,17.979 8.5,13.479 11,16.489 14.5,11.979 "/>
|
||||
<polygon fill="#66BB6A" points="5,17.979 8.5,13.479 12.234,17.979 "/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 856 B |
10
src/Website/wwwroot/images/power.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<path fill="none" d="M0,0h24v24H0V0z"/>
|
||||
<rect x="11" y="2" fill="#64B5F6" width="2" height="10"/>
|
||||
<path fill="#42A5F5" d="M16.561,4.44l-1.451,1.45C16.84,6.94,18,8.83,18,11c0,3.311-2.689,6-6,6c-3.31,0-6-2.689-6-6
|
||||
c0-2.17,1.16-4.06,2.88-5.12L7.44,4.44C5.36,5.88,4,8.28,4,11c0,4.42,3.58,8,8,8s8-3.58,8-8C20,8.28,18.641,5.88,16.561,4.44z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 818 B |
8
src/Website/wwwroot/images/previous.svg
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<path fill="#FFFFFF" d="M15.41,7.41L14,6l-6,6l6,6l1.41-1.41L10.83,12L15.41,7.41z"/>
|
||||
<path fill="none" d="M0,0h24v24H0V0z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 602 B |
19
src/Website/wwwroot/images/projects.svg
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="#4E342E" d="M19.001,3.016H14.82c-0.42-1.16-1.521-2-2.819-2s-2.4,0.84-2.82,2h-4.18c-1.1,0-2,0.9-2,2v14
|
||||
c0,1.101,0.9,2,2,2h14c1.101,0,2-0.899,2-2v-14C21.001,3.916,20.102,3.016,19.001,3.016z"/>
|
||||
<path fill="#D7CCC8" d="M19.71,6.553c0-1.354-1.103-2.454-2.455-2.454H6.79c-1.356,0-2.455,1.099-2.455,2.454V17.23
|
||||
c0,1.354,1.099,2.453,2.455,2.453h10.465c1.354,0,2.455-1.101,2.455-2.453V6.553z"/>
|
||||
<circle fill="#F1F8E9" cx="12.001" cy="4.016" r="1"/>
|
||||
</g>
|
||||
<path fill="none" d="M0,0h24v24H0V0z"/>
|
||||
<rect x="7" y="16" fill="#455A64" width="7" height="2"/>
|
||||
<rect x="7" y="12" fill="#455A64" width="10" height="2"/>
|
||||
<rect x="7" y="8" fill="#455A64" width="10" height="2"/>
|
||||
<path fill="#FFA000" d="M7.011,6.213l9.984,0.031l0.002-1.236v-2h-2.18c-0.42-1.16-1.521-2-2.82-2s-2.4,0.84-2.82,2h-2.18v2
|
||||
L7.011,6.213z M11.997,3.008c0.55,0,1,0.45,1,1s-0.45,1-1,1s-1-0.45-1-1S11.447,3.008,11.997,3.008z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
13
src/Website/wwwroot/images/register.svg
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<path fill="none" d="M0,0h24v24H0V0z"/>
|
||||
<circle fill="#1976D2" cx="15" cy="8.13" r="4"/>
|
||||
<polygon fill="#8BC34A" points="6,10 6,7 4,7 4,10 1,10 1,12 4,12 4,15 6,15 6,12 9,12 9,10 "/>
|
||||
<path fill="#1565C0" d="M23,18v2H7v-2c0-2.66,5.33-4,8-4S23,15.34,23,18z"/>
|
||||
<path fill="none" d="M15,12.13v-8c2.21,0,4,1.79,4,4S17.21,12.13,15,12.13z"/>
|
||||
<path fill="none" d="M23,18v2h-8v-6C17.67,14,23,15.34,23,18z"/>
|
||||
<polygon fill="none" points="39,-2 39,22 29.88,22 29.88,0.08 15,0.08 15,-2 "/>
|
||||
</svg>
|
After Width: | Height: | Size: 961 B |
15
src/Website/wwwroot/images/save.svg
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<g id="Capa_1_1_">
|
||||
<path fill="none" d="M0,0h24v24H0V0z"/>
|
||||
<path fill="#37474F" d="M21,7v12c0,1.1-0.9,2-2,2H5c-1.11,0-2-0.9-2-2V5c0-1.1,0.89-2,2-2h12L21,7z"/>
|
||||
<path opacity="0.5" fill="#455A64" d="M11.96,3v18H5c-1.11,0-2-0.9-2-2V5c0-1.1,0.89-2,2-2H11.96z"/>
|
||||
<path fill="#B0BEC5" d="M15,16c0,1.66-1.34,3-3,3h-0.05C10.31,18.97,9,17.641,9,16s1.31-2.97,2.95-3H12C13.66,13,15,14.34,15,16z"
|
||||
/>
|
||||
<rect x="5" y="5" fill="#B0BEC5" width="10" height="4"/>
|
||||
<path fill="#90A4AE" d="M15,16c0,1.66-1.34,3-3,3h-0.05v-6H12C13.66,13,15,14.34,15,16z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1 KiB |
8
src/Website/wwwroot/images/send.svg
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<polygon fill="#1E88E5" points="23,12 2.01,21 2,14 17,12 2,10 2.01,3 "/>
|
||||
<path fill="none" d="M0,0h24v24H0V0z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 591 B |
1
src/Website/wwwroot/images/spinner.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><svg width='200px' height='200px' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="uil-ring-alt"><rect x="0" y="0" width="100" height="100" fill="none" class="bk"></rect><circle cx="50" cy="50" r="40" stroke="none" fill="none" stroke-width="10" stroke-linecap="round"></circle><circle cx="50" cy="50" r="40" stroke="#e53935" fill="none" stroke-width="6" stroke-linecap="round"><animate attributeName="stroke-dashoffset" dur="2s" repeatCount="indefinite" from="0" to="502"></animate><animate attributeName="stroke-dasharray" dur="2s" repeatCount="indefinite" values="150.6 100.4;1 250;150.6 100.4"></animate></circle></svg>
|
After Width: | Height: | Size: 706 B |
16
src/Website/wwwroot/images/status.svg
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<rect x="4" y="17" fill="#BA68C8" width="10" height="2"/>
|
||||
<rect x="4" y="9" fill="#4DB6AC" width="16" height="2"/>
|
||||
<rect x="4" y="13" fill="#DCE775" width="16" height="2"/>
|
||||
<rect x="4" y="5" fill="#4FC3F7" width="16" height="2"/>
|
||||
<path fill="none" d="M0,0v24h24V0H0z M22.49,19.42H13.97V19H14v-2h-0.03v-2H20v-2h-6.03v-2H20V9h-6.03V7H20V5h-6.03V3.28h8.521
|
||||
V19.42z"/>
|
||||
<rect x="13.97" y="17" opacity="0.2" fill="#546E7A" width="0.03" height="2"/>
|
||||
<rect x="13.97" y="5" opacity="0.2" fill="#546E7A" width="6.03" height="2"/>
|
||||
<rect x="13.97" y="9" opacity="0.2" fill="#546E7A" width="6.03" height="2"/>
|
||||
<rect x="13.97" y="13" opacity="0.2" fill="#546E7A" width="6.03" height="2"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
10
src/Website/wwwroot/images/temperature.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<path fill="none" d="M0,0h24v24H0V0z"/>
|
||||
<rect x="9.375" y="13.292" fill="#795548" width="5.333" height="6.708"/>
|
||||
<polygon fill="#D7CCC8" points="5,12 5,20 10,20 10,14 14,14 14,20 19,20 19,12 19.021,12 12.01,5.691 "/>
|
||||
<polygon fill="#F44336" points="12,3 2,12 5,12 12.01,5.691 19.021,12 22,12 "/>
|
||||
</svg>
|
After Width: | Height: | Size: 777 B |
9
src/Website/wwwroot/images/upload.svg
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<path fill="none" d="M0,0h24v24H0V0z"/>
|
||||
<polygon fill="#03A9F4" points="9,16 15,16 15,10 19,10 12,3 5,10 9,10 "/>
|
||||
<rect x="5" y="18" fill="#0288D1" width="14" height="2"/>
|
||||
</svg>
|
After Width: | Height: | Size: 652 B |
11
src/Website/wwwroot/images/weight.svg
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<g id="Capa_1_1_">
|
||||
<polygon fill="#E53935" points="22,12 22,18 16,18 18.29,15.71 13.41,10.83 9.41,14.83 2,7.41 3.41,6 9.41,12 13.41,8 19.71,14.29
|
||||
"/>
|
||||
<path fill="none" d="M0,0h24v24H0V0z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 682 B |
67
src/Website/wwwroot/js/javascript.js
Normal file
|
@ -0,0 +1,67 @@
|
|||
function Navigate(url) {
|
||||
window.location = url;
|
||||
}
|
||||
|
||||
function CreateCookie(name, value, days) {
|
||||
var expires;
|
||||
if (days) {
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
|
||||
expires = "; expires=" + date.toGMTString();
|
||||
}
|
||||
else {
|
||||
expires = "";
|
||||
}
|
||||
document.cookie = name + "=" + value + expires + "; path=/";
|
||||
}
|
||||
|
||||
function round(value, decimals) {
|
||||
return Number(Math.round(value + 'e' + decimals) + 'e-' + decimals);
|
||||
}
|
||||
|
||||
function isNumber(value){
|
||||
return isFinite(String(value));
|
||||
}
|
||||
|
||||
String.prototype.upperCaseFirst = function() {
|
||||
var lower = this.toLowerCase();
|
||||
return lower.charAt(0).toUpperCase() + lower.slice(1);
|
||||
};
|
||||
|
||||
function twoDigitZeroPad(input){
|
||||
input=input.toString();
|
||||
return [!input[1] && '0' || "", input].join('');
|
||||
}
|
||||
|
||||
Date.prototype.oldToString = Date.prototype.toString;
|
||||
Date.prototype.toString = function(format) {
|
||||
if (!format)
|
||||
return this.oldToString();
|
||||
var me=this;
|
||||
return format.replace(/./g, function(match) {
|
||||
switch(match){
|
||||
case 'd': return twoDigitZeroPad(me.getDate());
|
||||
case 'm': return twoDigitZeroPad(me.getMonth()+1);
|
||||
case 'y': return me.getFullYear();
|
||||
case "H": return twoDigitZeroPad(me.getHours());
|
||||
case "i": return twoDigitZeroPad(me.getMinutes());
|
||||
case "s": return twoDigitZeroPad(me.getSeconds());
|
||||
default: return match;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Number.prototype.oldToString=Number.prototype.toString;
|
||||
Number.prototype.toString=function(input, shouldRound){
|
||||
if (!shouldRound)
|
||||
return this.oldToString(input);
|
||||
|
||||
return round(this, input);
|
||||
};
|
||||
|
||||
function documentReady(callback) {
|
||||
if (document.readyState === "complete" || document.readyState === "interactive")
|
||||
setTimeout(callback, 1);
|
||||
else
|
||||
document.addEventListener("DOMContentLoaded", callback);
|
||||
}
|
1
src/Website/wwwroot/webfonts
Symbolic link
|
@ -0,0 +1 @@
|
|||
../node_modules/@fortawesome/fontawesome-free/webfonts
|