diff --git a/Authentication/MongoAuth/Base32Encoding.cs b/Authentication/MongoAuth/Base32Encoding.cs new file mode 100644 index 0000000..46f2816 --- /dev/null +++ b/Authentication/MongoAuth/Base32Encoding.cs @@ -0,0 +1,133 @@ +using System; + +namespace MongoAuth +{ + /* Based off http://stackoverflow.com/a/7135008 */ + internal static class Base32Encoding + { + public static byte[] ToBytes(string input) + { + if (string.IsNullOrEmpty(input)) + { + throw new ArgumentNullException(nameof(input)); + } + + input = input.TrimEnd('='); //remove padding characters + int byteCount = input.Length * 5 / 8; //this must be TRUNCATED + var returnArray = new byte[byteCount]; + + byte curByte = 0, + bitsRemaining = 8; + + int arrayIndex = 0; + + foreach (char c in input) + { + int cValue = CharToValue(c); + + int mask; + if (bitsRemaining > 5) + { + mask = cValue << (bitsRemaining - 5); + curByte = (byte)(curByte | mask); + bitsRemaining -= 5; + } + else + { + mask = cValue >> (5 - bitsRemaining); + curByte = (byte)(curByte | mask); + returnArray[arrayIndex++] = curByte; + curByte = (byte)(cValue << (3 + bitsRemaining)); + bitsRemaining += 3; + } + } + + //if we didn't end with a full byte + if (arrayIndex != byteCount) + { + returnArray[arrayIndex] = curByte; + } + + return returnArray; + } + + public static string ToString(byte[] input) + { + if (input == null || input.Length == 0) + { + throw new ArgumentNullException(nameof(input)); + } + + int charCount = (int)Math.Ceiling(input.Length / 5d) * 8; + var returnArray = new char[charCount]; + + byte nextChar = 0, + bitsRemaining = 5; + int arrayIndex = 0; + + foreach (byte b in input) + { + nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining))); + returnArray[arrayIndex++] = ValueToChar(nextChar); + + if (bitsRemaining < 4) + { + nextChar = (byte)((b >> (3 - bitsRemaining)) & 31); + returnArray[arrayIndex++] = ValueToChar(nextChar); + bitsRemaining += 5; + } + + bitsRemaining -= 3; + nextChar = (byte)((b << bitsRemaining) & 31); + } + + //if we didn't end with a full char + if (arrayIndex != charCount) + { + returnArray[arrayIndex++] = ValueToChar(nextChar); + while (arrayIndex != charCount) + returnArray[arrayIndex++] = '='; //padding + } + + return new string(returnArray); + } + + private static int CharToValue(char c) + { + int value = c; + + //65-90 == uppercase letters + if (value < 91 && value > 64) + { + return value - 65; + } + //50-55 == numbers 2-7 + if (value < 56 && value > 49) + { + return value - 24; + } + //97-122 == lowercase letters + if (value < 123 && value > 96) + { + return value - 97; + } + + throw new ArgumentException("Character is not a Base32 character.", nameof(c)); + } + + private static char ValueToChar(byte b) + { + if (b < 26) + { + return (char)(b + 65); + } + + if (b < 32) + { + return (char)(b + 24); + } + + throw new ArgumentException("Byte is not a value Base32 value.", nameof(b)); + } + } +} \ No newline at end of file diff --git a/Authentication/MongoAuth/DatabaseConfig.cs b/Authentication/MongoAuth/DatabaseConfig.cs index 344f9ba..f3af9bf 100644 --- a/Authentication/MongoAuth/DatabaseConfig.cs +++ b/Authentication/MongoAuth/DatabaseConfig.cs @@ -2,34 +2,34 @@ namespace MongoAuth { - internal static class DatabaseConfig - { - public static string Host { get; } - public static int Port { get; } - public static string Database { get; } - public static string Username { get; } - public static string Password { get; } + internal static class DatabaseConfig + { + public static string Host { get; } + public static int Port { get; } + public static string Database { get; } + public static string Username { get; } + public static string Password { get; } - static DatabaseConfig() - { - Host = !string.IsNullOrEmpty(ConfigurationManager.AppSettings["data:MongoHost"]) - ? ConfigurationManager.AppSettings["data:MongoHost"] - : "localhost"; + static DatabaseConfig() + { + Host = !string.IsNullOrEmpty(ConfigurationManager.AppSettings["data:MongoHost"]) + ? ConfigurationManager.AppSettings["data:MongoHost"] + : "localhost"; - int _port; - bool success = int.TryParse(ConfigurationManager.AppSettings["data:MongoPort"], out _port); - if (!success) - { - _port = 27017; // mongo default port - } - Port = _port; + int port; + bool success = int.TryParse(ConfigurationManager.AppSettings["data:MongoPort"], out port); + if (!success) + { + port = 27017; // mongo default port + } + Port = port; - Database = !string.IsNullOrEmpty(ConfigurationManager.AppSettings["data:MongoDB"]) - ? ConfigurationManager.AppSettings["data:MongoDB"] - : "MongoAuth"; + Database = !string.IsNullOrEmpty(ConfigurationManager.AppSettings["data:MongoDB"]) + ? ConfigurationManager.AppSettings["data:MongoDB"] + : "MongoAuth"; - Username = ConfigurationManager.AppSettings["data:MongoUser"] ?? ""; - Password = ConfigurationManager.AppSettings["data:MongoPass"] ?? ""; - } - } + Username = ConfigurationManager.AppSettings["data:MongoUser"] ?? ""; + Password = ConfigurationManager.AppSettings["data:MongoPass"] ?? ""; + } + } } \ No newline at end of file diff --git a/Authentication/MongoAuth/MongoAuth.csproj b/Authentication/MongoAuth/MongoAuth.csproj index aa9f206..5dd7bac 100644 --- a/Authentication/MongoAuth/MongoAuth.csproj +++ b/Authentication/MongoAuth/MongoAuth.csproj @@ -56,6 +56,7 @@ + diff --git a/Authentication/MongoAuth/MongoMembershipProvider.cs b/Authentication/MongoAuth/MongoMembershipProvider.cs index 1b09476..da199bc 100644 --- a/Authentication/MongoAuth/MongoMembershipProvider.cs +++ b/Authentication/MongoAuth/MongoMembershipProvider.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using System.Configuration; using System.Linq; using System.Security.Cryptography; using System.Text; @@ -12,419 +13,468 @@ namespace MongoAuth { - public class MongoMembershipProvider : MembershipProvider - { - private const string MEMBER_COLLECTION_NAME = "members"; + public class MongoMembershipProvider : MembershipProvider + { + private const string MEMBER_COLLECTION_NAME = "members"; - private bool _enablePasswordReset = true; - private int _maxInvalidPasswordAttempts = 5; + private bool _enablePasswordReset = true; + private int _maxInvalidPasswordAttempts = 5; - private IMongoCollection _memberCollection; - private int _minRequiredNonAlphanumericCharacters = 1; - private int _minRequriedPasswordLength = 8; - private int _passwordAttemptWindow = 60; - private bool _requiresUniqueEmail = true; + private IMongoCollection _memberCollection; + private int _minRequiredNonAlphanumericCharacters = 1; + private int _minRequriedPasswordLength = 8; + private int _passwordAttemptWindow = 60; + private bool _requiresUniqueEmail = true; - public override string ApplicationName { get; set; } + public override string ApplicationName { get; set; } - public override bool EnablePasswordReset => _enablePasswordReset; + public override bool EnablePasswordReset => _enablePasswordReset; - public override bool EnablePasswordRetrieval => false; + public override bool EnablePasswordRetrieval => false; - public override int MaxInvalidPasswordAttempts => _maxInvalidPasswordAttempts; + public override int MaxInvalidPasswordAttempts => _maxInvalidPasswordAttempts; - public override int MinRequiredNonAlphanumericCharacters => _minRequiredNonAlphanumericCharacters; + public override int MinRequiredNonAlphanumericCharacters => _minRequiredNonAlphanumericCharacters; - public override int MinRequiredPasswordLength => _minRequriedPasswordLength; + public override int MinRequiredPasswordLength => _minRequriedPasswordLength; - public override int PasswordAttemptWindow => _passwordAttemptWindow; + public override int PasswordAttemptWindow => _passwordAttemptWindow; - public override MembershipPasswordFormat PasswordFormat => MembershipPasswordFormat.Hashed; + public override MembershipPasswordFormat PasswordFormat => MembershipPasswordFormat.Hashed; - public override string PasswordStrengthRegularExpression => ""; + public override string PasswordStrengthRegularExpression => ""; - public override bool RequiresQuestionAndAnswer => false; + public override bool RequiresQuestionAndAnswer => false; - public override bool RequiresUniqueEmail => _requiresUniqueEmail; + public override bool RequiresUniqueEmail => _requiresUniqueEmail; - public override void Initialize(string name, NameValueCollection config) - { - if (config == null) - { - throw new ArgumentNullException(nameof(config)); - } - - base.Initialize(name, config); - - _enablePasswordReset = TryReadBool(config["enablePasswordReset"], _enablePasswordReset); - _maxInvalidPasswordAttempts = TryReadInt(config["maxInvalidPasswordAttempts"], _maxInvalidPasswordAttempts); - _minRequiredNonAlphanumericCharacters = TryReadInt(config["minRequiredNonAlphanumericCharacters"], _minRequiredNonAlphanumericCharacters); - _minRequriedPasswordLength = TryReadInt(config["minRequriedPasswordLength"], _minRequriedPasswordLength); - _passwordAttemptWindow = TryReadInt(config["passwordAttemptWindow"], _passwordAttemptWindow); - _requiresUniqueEmail = TryReadBool(config["requiresUniqueEmail"], _requiresUniqueEmail); - - - MongoClientSettings settings = new MongoClientSettings - { - Server = new MongoServerAddress(DatabaseConfig.Host, DatabaseConfig.Port) - }; - - if (!string.IsNullOrEmpty(DatabaseConfig.Username) && !string.IsNullOrEmpty(DatabaseConfig.Password)) - { - settings.Credentials = new List + public override void Initialize(string name, NameValueCollection config) + { + if (config == null) { - MongoCredential.CreateCredential(DatabaseConfig.Database, DatabaseConfig.Username, DatabaseConfig.Password) + throw new ArgumentNullException(nameof(config)); + } + + base.Initialize(name, config); + + _enablePasswordReset = TryReadBool(config["enablePasswordReset"], _enablePasswordReset); + _maxInvalidPasswordAttempts = TryReadInt(config["maxInvalidPasswordAttempts"], _maxInvalidPasswordAttempts); + _minRequiredNonAlphanumericCharacters = TryReadInt(config["minRequiredNonAlphanumericCharacters"], _minRequiredNonAlphanumericCharacters); + _minRequriedPasswordLength = TryReadInt(config["minRequriedPasswordLength"], _minRequriedPasswordLength); + _passwordAttemptWindow = TryReadInt(config["passwordAttemptWindow"], _passwordAttemptWindow); + _requiresUniqueEmail = TryReadBool(config["requiresUniqueEmail"], _requiresUniqueEmail); + + + MongoClientSettings settings = new MongoClientSettings + { + Server = new MongoServerAddress(DatabaseConfig.Host, DatabaseConfig.Port) }; - } - MongoClient dbClient = new MongoClient(settings); + if (!string.IsNullOrEmpty(DatabaseConfig.Username) && !string.IsNullOrEmpty(DatabaseConfig.Password)) + { + settings.Credentials = new List + { + MongoCredential.CreateCredential(DatabaseConfig.Database, DatabaseConfig.Username, DatabaseConfig.Password) + }; + } - _memberCollection = dbClient.GetDatabase(DatabaseConfig.Database).GetCollection(MEMBER_COLLECTION_NAME); - } + MongoClient dbClient = new MongoClient(settings); - public override bool ChangePassword(string username, string oldPassword, string newPassword) - { - bool isAuthenticated = ValidateUser(username, oldPassword); + _memberCollection = dbClient.GetDatabase(DatabaseConfig.Database).GetCollection(MEMBER_COLLECTION_NAME); + } - if (isAuthenticated) - { - Task task = _memberCollection.Find(m => m.UserName.ToLower() == username.ToLower()).SingleOrDefaultAsync(); + public override bool ChangePassword(string username, string oldPassword, string newPassword) + { + bool isAuthenticated = ValidateUser(username, oldPassword); + + if (isAuthenticated) + { + Task task = _memberCollection.Find(m => m.UserName.ToLower() == username.ToLower()).SingleOrDefaultAsync(); + task.Wait(); + MongoMember mm = task.Result; + + if (mm == null) + { + return false; + } + + var salt = new byte[24]; + byte[] hash = CalculateHash(newPassword, ref salt); + + mm.PassSalt = salt; + mm.PassHash = hash; + + Task replaceTask = _memberCollection.ReplaceOneAsync(m => m.Id == mm.Id, mm); + replaceTask.Wait(); + + return replaceTask.IsCompleted; + } + + return false; + } + + public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) + { + throw new NotImplementedException(); + } + + public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) + { + if (password.Length < MinRequiredPasswordLength) + { + status = MembershipCreateStatus.InvalidPassword; + return null; + } + + MembershipUser mu = null; + + Task dupeUsers = _memberCollection.Find(m => m.UserName.ToLower() == username.ToLower()).CountAsync(); + Task dupeEmails = _memberCollection.Find(m => m.EmailAddress.ToLower() == email.ToLower()).CountAsync(); + dupeUsers.Wait(); + dupeEmails.Wait(); + + if (dupeUsers.Result > 0) + { + status = MembershipCreateStatus.DuplicateUserName; + } + else if (dupeEmails.Result > 0) + { + status = MembershipCreateStatus.DuplicateEmail; + } + else + { + var salt = new byte[24]; + byte[] hash = CalculateHash(password, ref salt); + + MongoMember mm = new MongoMember + { + Id = Guid.NewGuid(), + UserName = username, + PassHash = hash, + PassSalt = salt, + EmailAddress = email, + IsApproved = isApproved, + IsLockedOut = false, + CreationDate = DateTime.UtcNow, + LastLoginDate = DateTime.MinValue, + LastActivityDate = DateTime.MinValue, + LastLockoutDate = DateTime.MinValue + }; + + Task insertTask = _memberCollection.InsertOneAsync(mm); + insertTask.Wait(); + + if (insertTask.Status == TaskStatus.RanToCompletion) + { + status = MembershipCreateStatus.Success; + mu = new MembershipUser(Name, mm.UserName, mm.Id, mm.EmailAddress, "", "", mm.IsApproved, mm.IsLockedOut, mm.CreationDate, mm.LastLoginDate, mm.LastActivityDate, DateTime.MinValue, mm.LastLockoutDate); + } + else + { + status = MembershipCreateStatus.ProviderError; + } + } + + return mu; + } + + public override bool DeleteUser(string username, bool deleteAllRelatedData) + { + Task task = _memberCollection.DeleteOneAsync(m => m.UserName.ToLower() == username.ToLower()); + task.Wait(); + + return task.Result.IsAcknowledged && task.Result.DeletedCount == 1; + } + + public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) + { + throw new NotImplementedException(); + } + + public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) + { + throw new NotImplementedException(); + } + + public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) + { + MembershipUserCollection muc = new MembershipUserCollection(); + + IFindFluent users = _memberCollection.Find(new BsonDocument()); + + Task totalRecordsTask = users.CountAsync(); + totalRecordsTask.Wait(); + totalRecords = Convert.ToInt32(totalRecordsTask.Result); + + users = users.Skip(pageIndex * pageSize).Limit(pageSize); + Task> pageItemsTask = users.ToListAsync(); + pageItemsTask.Wait(); + + foreach (MongoMember mm in pageItemsTask.Result) + { + muc.Add(new MembershipUser(Name, mm.UserName, mm.Id, mm.EmailAddress, "", "", mm.IsApproved, mm.IsLockedOut, FixupDatesFromMongo(mm.CreationDate), FixupDatesFromMongo(mm.LastLoginDate), FixupDatesFromMongo(mm.LastActivityDate), DateTime.MinValue, FixupDatesFromMongo(mm.LastLockoutDate))); + } + + return muc; + } + + public override int GetNumberOfUsersOnline() + { + throw new NotImplementedException(); + } + + public override string GetPassword(string username, string answer) + { + throw new NotImplementedException(); + } + + public override MembershipUser GetUser(string username, bool userIsOnline) + { + Task task = _memberCollection.Find(f => f.UserName.ToLower() == username.ToLower()).FirstOrDefaultAsync(); + task.Wait(); + + MongoMember mm = task.Result; + + return mm == null + ? null + : new MembershipUser(Name, mm.UserName, mm.Id, mm.EmailAddress, "", "", mm.IsApproved, mm.IsLockedOut, FixupDatesFromMongo(mm.CreationDate), FixupDatesFromMongo(mm.LastLoginDate), FixupDatesFromMongo(mm.LastActivityDate), DateTime.MinValue, FixupDatesFromMongo(mm.LastLockoutDate)); + } + + public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) + { + Task task = _memberCollection.Find(f => f.Id == (Guid)providerUserKey).FirstOrDefaultAsync(); + task.Wait(); + + MongoMember mm = task.Result; + + return mm == null + ? null + : new MembershipUser(Name, mm.UserName, mm.Id, mm.EmailAddress, "", "", mm.IsApproved, mm.IsLockedOut, FixupDatesFromMongo(mm.CreationDate), FixupDatesFromMongo(mm.LastLoginDate), FixupDatesFromMongo(mm.LastActivityDate), DateTime.MinValue, FixupDatesFromMongo(mm.LastLockoutDate)); + } + + public override string GetUserNameByEmail(string email) + { + Task task = _memberCollection.Find(f => f.EmailAddress.ToLower() == email.ToLower()).FirstOrDefaultAsync(); + task.Wait(); + + return task.Result.UserName; + } + + public override string ResetPassword(string username, string answer) + { + throw new NotImplementedException(); + } + + public void ChangeApproval(Guid id, bool newStatus) + { + Task task = _memberCollection.UpdateOneAsync(Builders.Filter.Eq(u => u.Id, id), Builders.Update.Set(u => u.IsApproved, newStatus)); + task.Wait(); + } + + public void ChangeLockStatus(Guid id, bool newStatus) + { + var updateDefinition = new List> + { + Builders.Update.Set(u => u.IsLockedOut, newStatus) + }; + + if (newStatus) + { + updateDefinition.Add(Builders.Update.Set(u => u.LastLockoutDate, DateTime.UtcNow)); + } + else + { + updateDefinition.Add(Builders.Update.Set(u => u.LockoutWindowAttempts, 0)); + updateDefinition.Add(Builders.Update.Set(u => u.LastLockoutDate, DateTime.MinValue)); + } + + Task task = _memberCollection.UpdateOneAsync(Builders.Filter.Eq(u => u.Id, id), Builders.Update.Combine(updateDefinition)); + task.Wait(); + } + + public override bool UnlockUser(string userName) + { + Task task = _memberCollection.UpdateOneAsync(Builders.Filter.Eq(m => m.UserName.ToLower(), userName.ToLower()), Builders.Update.Set(m => m.IsLockedOut, false)); + task.Wait(); + + return task.Result.IsAcknowledged && task.Result.ModifiedCount == 1; + } + + public override void UpdateUser(MembershipUser user) + { + throw new NotImplementedException(); + } + + public override bool ValidateUser(string username, string password) + { + Task task = _memberCollection.Find(f => f.UserName.ToLower() == username.ToLower()).FirstOrDefaultAsync(); task.Wait(); MongoMember mm = task.Result; - if (mm == null) + if (mm == null + || !(mm.IsApproved && !mm.IsLockedOut)) { - return false; + return false; } - var salt = new byte[24]; - byte[] hash = CalculateHash(newPassword, ref salt); - - mm.PassSalt = salt; - mm.PassHash = hash; - - Task replaceTask = _memberCollection.ReplaceOneAsync(m => m.Id == mm.Id, mm); - replaceTask.Wait(); - - return replaceTask.IsCompleted; - } - - return false; - } - - public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) - { - throw new NotImplementedException(); - } - - public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) - { - if (password.Length < MinRequiredPasswordLength) - { - status = MembershipCreateStatus.InvalidPassword; - return null; - } - - MembershipUser mu = null; - - Task dupeUsers = _memberCollection.Find(m => m.UserName.ToLower() == username.ToLower()).CountAsync(); - Task dupeEmails = _memberCollection.Find(m => m.EmailAddress.ToLower() == email.ToLower()).CountAsync(); - dupeUsers.Wait(); - dupeEmails.Wait(); - - if (dupeUsers.Result > 0) - { - status = MembershipCreateStatus.DuplicateUserName; - } - else if (dupeEmails.Result > 0) - { - status = MembershipCreateStatus.DuplicateEmail; - } - else - { - var salt = new byte[24]; + byte[] salt = mm.PassSalt; byte[] hash = CalculateHash(password, ref salt); - MongoMember mm = new MongoMember - { - Id = Guid.NewGuid(), - UserName = username, - PassHash = hash, - PassSalt = salt, - EmailAddress = email, - IsApproved = false, - IsLockedOut = false, - CreationDate = DateTime.Now, - LastLoginDate = DateTime.MinValue, - LastActivityDate = DateTime.MinValue, - LastLockoutDate = DateTime.MinValue - }; + bool isFail = false; - Task insertTask = _memberCollection.InsertOneAsync(mm); - insertTask.Wait(); - - if (insertTask.Status == TaskStatus.RanToCompletion) + for (int i = 0; i > hash.Length; i++) { - status = MembershipCreateStatus.Success; - mu = new MembershipUser(Name, mm.UserName, mm.Id, mm.EmailAddress, "", "", mm.IsApproved, mm.IsLockedOut, mm.CreationDate, mm.LastLoginDate, mm.LastActivityDate, DateTime.MinValue, mm.LastLockoutDate); + isFail |= hash[i] != mm.PassHash[i]; + } + + + if (isFail) + { + if (mm.LockoutWindowStart == DateTime.MinValue) + { + mm.LockoutWindowStart = DateTime.UtcNow; + mm.LockoutWindowAttempts = 1; + } + else + { + if (mm.LockoutWindowStart.AddMinutes(PasswordAttemptWindow) > DateTime.UtcNow) + { + // still within window + mm.LockoutWindowAttempts++; + if (mm.LockoutWindowAttempts >= MaxInvalidPasswordAttempts) + { + mm.IsLockedOut = true; + } + } + else + { + // outside of window, reset + mm.LockoutWindowStart = DateTime.UtcNow; + mm.LockoutWindowAttempts = 1; + } + } } else { - status = MembershipCreateStatus.ProviderError; + mm.LastLoginDate = DateTime.UtcNow; + mm.LockoutWindowStart = DateTime.MinValue; + mm.LockoutWindowAttempts = 0; } - } - return mu; - } + Task updTask = _memberCollection.ReplaceOneAsync(Builders.Filter.Eq(u => u.Id, mm.Id), mm); + updTask.Wait(); - public override bool DeleteUser(string username, bool deleteAllRelatedData) - { - Task task = _memberCollection.DeleteOneAsync(m => m.UserName.ToLower() == username.ToLower()); - task.Wait(); + return !isFail; + } - return task.Result.IsAcknowledged && (task.Result.DeletedCount == 1); - } - - public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) - { - throw new NotImplementedException(); - } - - public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) - { - throw new NotImplementedException(); - } - - public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) - { - MembershipUserCollection muc = new MembershipUserCollection(); - - IFindFluent users = _memberCollection.Find(new BsonDocument()); - - Task totalRecordsTask = users.CountAsync(); - totalRecordsTask.Wait(); - totalRecords = Convert.ToInt32(totalRecordsTask.Result); - - users = users.Skip(pageIndex * pageSize).Limit(pageSize); - Task> pageItemsTask = users.ToListAsync(); - pageItemsTask.Wait(); - - foreach (MongoMember mm in pageItemsTask.Result) - { - muc.Add(new MembershipUser(Name, mm.UserName, mm.Id, mm.EmailAddress, "", "", mm.IsApproved, mm.IsLockedOut, mm.CreationDate, mm.LastLoginDate, mm.LastActivityDate, DateTime.MinValue, mm.LastLockoutDate)); - } - - return muc; - } - - public override int GetNumberOfUsersOnline() - { - throw new NotImplementedException(); - } - - public override string GetPassword(string username, string answer) - { - throw new NotImplementedException(); - } - - public override MembershipUser GetUser(string username, bool userIsOnline) - { - Task task = _memberCollection.Find(f => f.UserName.ToLower() == username.ToLower()).FirstOrDefaultAsync(); - task.Wait(); - - MongoMember mm = task.Result; - - return mm == null - ? null - : new MembershipUser(Name, mm.UserName, mm.Id, mm.EmailAddress, "", "", mm.IsApproved, mm.IsLockedOut, mm.CreationDate, mm.LastLoginDate, mm.LastActivityDate, DateTime.MinValue, mm.LastLockoutDate); - } - - public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) - { - Task task = _memberCollection.Find(f => f.Id == (Guid)providerUserKey).FirstOrDefaultAsync(); - task.Wait(); - - MongoMember mm = task.Result; - - return mm == null - ? null - : new MembershipUser(Name, mm.UserName, mm.Id, mm.EmailAddress, "", "", mm.IsApproved, mm.IsLockedOut, mm.CreationDate, mm.LastLoginDate, mm.LastActivityDate, DateTime.MinValue, mm.LastLockoutDate); - } - - public override string GetUserNameByEmail(string email) - { - Task task = _memberCollection.Find(f => f.EmailAddress.ToLower() == email.ToLower()).FirstOrDefaultAsync(); - task.Wait(); - - return task.Result.UserName; - } - - public override string ResetPassword(string username, string answer) - { - throw new NotImplementedException(); - } - - public void ChangeApproval(Guid id, bool newStatus) - { - Task task = _memberCollection.UpdateOneAsync(Builders.Filter.Eq(u => u.Id, id), Builders.Update.Set(u => u.IsApproved, newStatus)); - task.Wait(); - } - - public void ChangeLockStatus(Guid id, bool newStatus) - { - var updateDefinition = new List> - { - Builders.Update.Set(u => u.IsLockedOut, newStatus) - }; - - if (newStatus) - { - updateDefinition.Add(Builders.Update.Set(u => u.LastLockoutDate, DateTime.Now)); - } - else - { - updateDefinition.Add(Builders.Update.Set(u => u.LockoutWindowAttempts, 0)); - updateDefinition.Add(Builders.Update.Set(u => u.LastLockoutDate, DateTime.MinValue)); - } - - Task task = _memberCollection.UpdateOneAsync(Builders.Filter.Eq(u => u.Id, id), Builders.Update.Combine(updateDefinition)); - task.Wait(); - } - - public override bool UnlockUser(string userName) - { - Task task = _memberCollection.UpdateOneAsync(Builders.Filter.Eq(m => m.UserName.ToLower(), userName.ToLower()), Builders.Update.Set(m => m.IsLockedOut, false)); - task.Wait(); - - return task.Result.IsAcknowledged && (task.Result.ModifiedCount == 1); - } - - public override void UpdateUser(MembershipUser user) - { - throw new NotImplementedException(); - } - - public override bool ValidateUser(string username, string password) - { - Task task = _memberCollection.Find(f => f.UserName.ToLower() == username.ToLower()).FirstOrDefaultAsync(); - task.Wait(); - MongoMember mm = task.Result; - - if ((mm == null) - || !(mm.IsApproved && !mm.IsLockedOut)) - { - return false; - } - - byte[] salt = mm.PassSalt; - byte[] hash = CalculateHash(password, ref salt); - - bool isFail = false; - - for (int i = 0; i > hash.Length; i++) - { - isFail |= hash[i] != mm.PassHash[i]; - } - - - if (isFail) - { - if (mm.LockoutWindowStart == DateTime.MinValue) + public async Task GenerateValidationHash(Guid id) + { + MongoMember mm = await _memberCollection.Find(Builders.Filter.Eq(u => u.Id, id)).FirstOrDefaultAsync(); + if (mm == null) { - mm.LockoutWindowStart = DateTime.Now; - mm.LockoutWindowAttempts = 1; + return null; } - else + + using (SHA256 sha = SHA256.Create()) { - if (mm.LockoutWindowStart.AddMinutes(PasswordAttemptWindow) > DateTime.Now) - { - // still within window - mm.LockoutWindowAttempts++; - if (mm.LockoutWindowAttempts >= MaxInvalidPasswordAttempts) - { - mm.IsLockedOut = true; - } - } - else - { - // outside of window, reset - mm.LockoutWindowStart = DateTime.Now; - mm.LockoutWindowAttempts = 1; - } + string content = $"{mm.Id}.{mm.PassSalt}.{ConfigurationManager.AppSettings["data:SecretKey"]}"; + byte[] hashBytes = sha.ComputeHash(Encoding.UTF8.GetBytes(content)); + + return Base32Encoding.ToString(hashBytes); } - } - else - { - mm.LastLoginDate = DateTime.Now; - mm.LockoutWindowStart = DateTime.MinValue; - mm.LockoutWindowAttempts = 0; - } + } - Task updTask = _memberCollection.ReplaceOneAsync(Builders.Filter.Eq(u => u.Id, mm.Id), mm); - updTask.Wait(); + public async Task ValidateUserFromHash(Guid id, string validate) + { + MongoMember mm = await _memberCollection.Find(Builders.Filter.Eq(u => u.Id, id)).FirstOrDefaultAsync(); + if (mm == null) + { + return false; + } - return !isFail; - } + using (SHA256 sha = SHA256.Create()) + { + string content = $"{mm.Id}.{mm.PassSalt}.{ConfigurationManager.AppSettings["data:SecretKey"]}"; + byte[] hashBytes = sha.ComputeHash(Encoding.UTF8.GetBytes(content)); - private static byte[] CalculateHash(string password, ref byte[] salt) - { - if (!salt.Any(v => v != 0)) - { - RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); - rng.GetBytes(salt); - } + string expected = Base32Encoding.ToString(hashBytes); - byte[] passwordBytes = Encoding.UTF8.GetBytes(password); + bool success = string.Equals(expected, validate, StringComparison.InvariantCultureIgnoreCase); - var hashPlaintext = new byte[salt.Length + passwordBytes.Length]; + if (success) + { + ChangeApproval(id, true); + } - passwordBytes.CopyTo(hashPlaintext, 0); - salt.CopyTo(hashPlaintext, passwordBytes.Length); + return success; + } + } - SHA512CryptoServiceProvider sha = new SHA512CryptoServiceProvider(); - byte[] hash = sha.ComputeHash(hashPlaintext); + private static byte[] CalculateHash(string password, ref byte[] salt) + { + if (!salt.Any(v => v != 0)) + { + RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); + rng.GetBytes(salt); + } - return hash; - } + byte[] passwordBytes = Encoding.UTF8.GetBytes(password); - private static bool TryReadBool(string config, bool defaultValue) - { - bool temp; - bool success = bool.TryParse(config, out temp); - return success - ? temp - : defaultValue; - } + var hashPlaintext = new byte[salt.Length + passwordBytes.Length]; - private static int TryReadInt(string config, int defaultValue) - { - int temp; - bool success = int.TryParse(config, out temp); - return success - ? temp - : defaultValue; - } - } + passwordBytes.CopyTo(hashPlaintext, 0); + salt.CopyTo(hashPlaintext, passwordBytes.Length); - public class MongoMember - { - [BsonId] - public Guid Id { get; set; } + SHA512CryptoServiceProvider sha = new SHA512CryptoServiceProvider(); + byte[] hash = sha.ComputeHash(hashPlaintext); - public string UserName { get; set; } - public byte[] PassHash { get; set; } - public byte[] PassSalt { get; set; } - public string EmailAddress { get; set; } + return hash; + } - public bool IsApproved { get; set; } - public bool IsLockedOut { get; set; } + private static bool TryReadBool(string config, bool defaultValue) + { + bool temp; + bool success = bool.TryParse(config, out temp); + return success + ? temp + : defaultValue; + } - public DateTime CreationDate { get; set; } - public DateTime LastActivityDate { get; set; } - public DateTime LastLockoutDate { get; set; } - public DateTime LastLoginDate { get; set; } + private static int TryReadInt(string config, int defaultValue) + { + int temp; + bool success = int.TryParse(config, out temp); + return success + ? temp + : defaultValue; + } - public DateTime LockoutWindowStart { get; set; } - public int LockoutWindowAttempts { get; set; } - } + private static DateTime FixupDatesFromMongo(DateTime dt) + { + DateTime local = DateTime.SpecifyKind(dt, DateTimeKind.Local); + return local; + } + } + + public class MongoMember + { + [BsonId] + public Guid Id { get; set; } + + public string UserName { get; set; } + public byte[] PassHash { get; set; } + public byte[] PassSalt { get; set; } + public string EmailAddress { get; set; } + + public bool IsApproved { get; set; } + public bool IsLockedOut { get; set; } + + public DateTime CreationDate { get; set; } + public DateTime LastActivityDate { get; set; } + public DateTime LastLockoutDate { get; set; } + public DateTime LastLoginDate { get; set; } + + public DateTime LockoutWindowStart { get; set; } + public int LockoutWindowAttempts { get; set; } + } } \ No newline at end of file diff --git a/Authentication/MongoAuth/MongoRoleProvider.cs b/Authentication/MongoAuth/MongoRoleProvider.cs index 0cd10c1..9e5d246 100644 --- a/Authentication/MongoAuth/MongoRoleProvider.cs +++ b/Authentication/MongoAuth/MongoRoleProvider.cs @@ -13,214 +13,221 @@ namespace MongoAuth { - public class MongoRoleProvider : RoleProvider - { - private const string MEMBER_COLLECTION_NAME = "members"; - private const string ROLE_COLLECTION_NAME = "roles"; - private IMongoCollection _memberCollection; - private IMongoCollection _roleCollection; + public class MongoRoleProvider : RoleProvider + { + private const string MEMBER_COLLECTION_NAME = "members"; + private const string ROLE_COLLECTION_NAME = "roles"; + private IMongoCollection _memberCollection; + private IMongoCollection _roleCollection; - public override string ApplicationName - { - get { return ""; } - set { } - } + public override string ApplicationName + { + get { return ""; } + set { } + } - public override void Initialize(string name, NameValueCollection config) - { - base.Initialize(name, config); + public override void Initialize(string name, NameValueCollection config) + { + base.Initialize(name, config); - MongoClientSettings settings = new MongoClientSettings - { - Server = new MongoServerAddress(DatabaseConfig.Host, DatabaseConfig.Port) - }; - - if (!string.IsNullOrEmpty(DatabaseConfig.Username) && !string.IsNullOrEmpty(DatabaseConfig.Password)) - { - settings.Credentials = new List + MongoClientSettings settings = new MongoClientSettings { - MongoCredential.CreateCredential(DatabaseConfig.Database, DatabaseConfig.Username, DatabaseConfig.Password) + Server = new MongoServerAddress(DatabaseConfig.Host, DatabaseConfig.Port) }; - } - MongoClient dbClient = new MongoClient(settings); - - _roleCollection = dbClient.GetDatabase(DatabaseConfig.Database).GetCollection(ROLE_COLLECTION_NAME); - _memberCollection = dbClient.GetDatabase(DatabaseConfig.Database).GetCollection(MEMBER_COLLECTION_NAME); - } - - public override void AddUsersToRoles(string[] usernames, string[] roleNames) - { - Task> roleTask = _roleCollection.Find(r => roleNames.Contains(r.RoleName)).ToListAsync(); - roleTask.Wait(); - List roles = roleTask.Result; - - Task> userTask = _memberCollection.Find(u => usernames.Contains(u.UserName)).ToListAsync(); - userTask.Wait(); - List users = userTask.Result; - - for (int i = 0; i < roles.Count; i++) - { - var newUsers = new List(); - - if (roles[i].Users != null) + if (!string.IsNullOrEmpty(DatabaseConfig.Username) && !string.IsNullOrEmpty(DatabaseConfig.Password)) { - newUsers.AddRange(roles[i].Users); + settings.Credentials = new List + { + MongoCredential.CreateCredential(DatabaseConfig.Database, DatabaseConfig.Username, DatabaseConfig.Password) + }; } - IEnumerable usersToAdd = from u in users - where newUsers.All(v => v != u.Id) - select u.Id; + MongoClient dbClient = new MongoClient(settings); - newUsers.AddRange(usersToAdd); + _roleCollection = dbClient.GetDatabase(DatabaseConfig.Database).GetCollection(ROLE_COLLECTION_NAME); + _memberCollection = dbClient.GetDatabase(DatabaseConfig.Database).GetCollection(MEMBER_COLLECTION_NAME); + } - roles[i].Users = newUsers.ToArray(); + public override void AddUsersToRoles(string[] usernames, string[] roleNames) + { + Task> roleTask = _roleCollection.Find(r => roleNames.Contains(r.RoleName)).ToListAsync(); + roleTask.Wait(); + List roles = roleTask.Result; - Task update = _roleCollection.ReplaceOneAsync(Builders.Filter.Eq(r => r.Id, roles[i].Id), roles[i]); - update.Wait(); - } - } + Task> userTask = _memberCollection.Find(u => usernames.Contains(u.UserName)).ToListAsync(); + userTask.Wait(); + List users = userTask.Result; - public override void CreateRole(string roleName) - { - MongoRole r = new MongoRole - { - Id = Guid.NewGuid(), - RoleName = roleName - }; + for (int i = 0; i < roles.Count; i++) + { + var newUsers = new List(); - Task task = _roleCollection.InsertOneAsync(r); - task.Wait(); - } + if (roles[i].Users != null) + { + newUsers.AddRange(roles[i].Users); + } - public override bool DeleteRole(string roleName, bool throwOnPopulatedRole) - { - Task role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync(); - role.Wait(); + IEnumerable usersToAdd = from u in users + where newUsers.All(v => v != u.Id) + select u.Id; - if ((role.Result != null) - && (role.Result.Users.Length > 0) - && throwOnPopulatedRole) - { - throw new ProviderException(Resources.RoleNotEmpty); - } + newUsers.AddRange(usersToAdd); - Task task = _roleCollection.DeleteOneAsync(r => r.RoleName == roleName); - task.Wait(); + roles[i].Users = newUsers.ToArray(); - return true; - } + Task update = _roleCollection.ReplaceOneAsync(Builders.Filter.Eq(r => r.Id, roles[i].Id), roles[i]); + update.Wait(); + } + } - public override string[] FindUsersInRole(string roleName, string usernameToMatch) - { - Task role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync(); - role.Wait(); + public override void CreateRole(string roleName) + { + Task role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync(); + role.Wait(); + if (role.Result != null) + { + return; + } - if (role.Result == null) - { - return Array.Empty(); - } + MongoRole mr = new MongoRole + { + Id = Guid.NewGuid(), + RoleName = roleName + }; - Task> users = _memberCollection.Find(u => role.Result.Users.Contains(u.Id) && u.UserName.ToLower().Contains(usernameToMatch.ToLower())).ToListAsync(); - users.Wait(); + Task task = _roleCollection.InsertOneAsync(mr); + task.Wait(); + } - return users.Result.Select(r => r.UserName).ToArray(); - } + public override bool DeleteRole(string roleName, bool throwOnPopulatedRole) + { + Task role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync(); + role.Wait(); - public override string[] GetAllRoles() - { - Task> roles = _roleCollection.Find(new BsonDocument()).ToListAsync(); - roles.Wait(); + if (role.Result != null + && role.Result.Users.Length > 0 + && throwOnPopulatedRole) + { + throw new ProviderException(Resources.RoleNotEmpty); + } - return roles.Result.Select(r => r.RoleName).ToArray(); - } + Task task = _roleCollection.DeleteOneAsync(r => r.RoleName == roleName); + task.Wait(); - public override string[] GetRolesForUser(string username) - { - Task user = _memberCollection.Find(u => u.UserName.ToLower() == username.ToLower()).SingleOrDefaultAsync(); - user.Wait(); + return true; + } - if (user.Result == null) - { - return Array.Empty(); - } + public override string[] FindUsersInRole(string roleName, string usernameToMatch) + { + Task role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync(); + role.Wait(); - Task> role = _roleCollection.Find(r => (r.Users != null) && r.Users.Contains(user.Result.Id)).ToListAsync(); - role.Wait(); + if (role.Result == null) + { + return Array.Empty(); + } - return role.Result.Select(r => r.RoleName).ToArray(); - } + Task> users = _memberCollection.Find(u => role.Result.Users.Contains(u.Id) && u.UserName.ToLower().Contains(usernameToMatch.ToLower())).ToListAsync(); + users.Wait(); - public override string[] GetUsersInRole(string roleName) - { - Task role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync(); - role.Wait(); + return users.Result.Select(r => r.UserName).ToArray(); + } - if (role.Result == null) - { - return Array.Empty(); - } + public override string[] GetAllRoles() + { + Task> roles = _roleCollection.Find(new BsonDocument()).ToListAsync(); + roles.Wait(); - Task> users = _memberCollection.Find(u => role.Result.Users.Contains(u.Id)).ToListAsync(); - users.Wait(); + return roles.Result.Select(r => r.RoleName).ToArray(); + } - return users.Result.Select(u => u.UserName).ToArray(); - } + public override string[] GetRolesForUser(string username) + { + Task user = _memberCollection.Find(u => u.UserName.ToLower() == username.ToLower()).SingleOrDefaultAsync(); + user.Wait(); - public override bool IsUserInRole(string username, string roleName) - { - Task user = _memberCollection.Find(u => u.UserName.ToLower() == username.ToLower()).SingleOrDefaultAsync(); - Task role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync(); - user.Wait(); - role.Wait(); + if (user.Result == null) + { + return Array.Empty(); + } - if ((user.Result == null) - || (role.Result?.Users == null)) - { - return false; - } + Task> role = _roleCollection.Find(r => r.Users != null && r.Users.Contains(user.Result.Id)).ToListAsync(); + role.Wait(); - return role.Result.Users.Contains(user.Result.Id); - } + return role.Result.Select(r => r.RoleName).ToArray(); + } - public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames) - { - Task> roleTask = _roleCollection.Find(r => roleNames.Contains(r.RoleName)).ToListAsync(); - roleTask.Wait(); - List roles = roleTask.Result; + public override string[] GetUsersInRole(string roleName) + { + Task role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync(); + role.Wait(); - Task> userTask = _memberCollection.Find(u => usernames.Contains(u.UserName)).ToListAsync(); - userTask.Wait(); - List users = userTask.Result; + if (role.Result == null) + { + return Array.Empty(); + } - foreach (MongoRole t in roles) - { - t.Users = (from u in t.Users - where users.All(v => v.Id != u) - select u).ToArray(); + Task> users = _memberCollection.Find(u => role.Result.Users.Contains(u.Id)).ToListAsync(); + users.Wait(); - Task update = _roleCollection.ReplaceOneAsync(Builders.Filter.Eq(r => r.Id, t.Id), t); - update.Wait(); - } - } + return users.Result.Select(u => u.UserName).ToArray(); + } - public override bool RoleExists(string roleName) - { - Task role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync(); - role.Wait(); + public override bool IsUserInRole(string username, string roleName) + { + Task user = _memberCollection.Find(u => u.UserName.ToLower() == username.ToLower()).SingleOrDefaultAsync(); + Task role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync(); + user.Wait(); + role.Wait(); - return role.Result != null; - } - } + if (user.Result == null + || role.Result?.Users == null) + { + return false; + } - [DataObject] - public class MongoRole - { - [BsonId] - public Guid Id { get; set; } + return role.Result.Users.Contains(user.Result.Id); + } - public string RoleName { get; set; } + public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames) + { + Task> roleTask = _roleCollection.Find(r => roleNames.Contains(r.RoleName)).ToListAsync(); + roleTask.Wait(); + List roles = roleTask.Result; - public Guid[] Users { get; set; } - } + Task> userTask = _memberCollection.Find(u => usernames.Contains(u.UserName)).ToListAsync(); + userTask.Wait(); + List users = userTask.Result; + + foreach (MongoRole t in roles) + { + t.Users = (from u in t.Users + where users.All(v => v.Id != u) + select u).ToArray(); + + Task update = _roleCollection.ReplaceOneAsync(Builders.Filter.Eq(r => r.Id, t.Id), t); + update.Wait(); + } + } + + public override bool RoleExists(string roleName) + { + Task role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync(); + role.Wait(); + + return role.Result != null; + } + } + + [DataObject] + public class MongoRole + { + [BsonId] + public Guid Id { get; set; } + + public string RoleName { get; set; } + + public Guid[] Users { get; set; } + } } \ No newline at end of file diff --git a/BuildFeed.Local/VariantTerms.Designer.cs b/BuildFeed.Local/VariantTerms.Designer.cs index 000933a..679893d 100644 --- a/BuildFeed.Local/VariantTerms.Designer.cs +++ b/BuildFeed.Local/VariantTerms.Designer.cs @@ -186,6 +186,15 @@ public static string Common_Admin { } } + /// + /// Looks up a localized string similar to Change password. + /// + public static string Common_ChangePassword { + get { + return ResourceManager.GetString("Common_ChangePassword", resourceCulture); + } + } + /// /// Looks up a localized string similar to Contribute on {0}. /// @@ -330,6 +339,32 @@ public static string Common_Twitter { } } + /// + /// Looks up a localized string similar to Thank you for registering to {0}. + /// + ///Please verify your email address by clicking the link below, or by copying and pasting it into your browser. + ///{1} + /// + ///If you did not register to {0}, you can ignore this email. + /// + ///Thanks, + ///The {0} Team.. + /// + public static string Email_Registration_Body { + get { + return ResourceManager.GetString("Email_Registration_Body", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0}: Please verify your email address. + /// + public static string Email_Registration_Subject { + get { + return ResourceManager.GetString("Email_Registration_Subject", resourceCulture); + } + } + /// /// Looks up a localized string similar to About. /// @@ -924,15 +959,6 @@ public static string Search_Year { } } - /// - /// Looks up a localized string similar to Every account is validated by an administrator, so be patient and check again later. - /// - public static string Support_AccountValidation { - get { - return ResourceManager.GetString("Support_AccountValidation", resourceCulture); - } - } - /// /// Looks up a localized string similar to Additions to BuildFeed. /// @@ -987,6 +1013,24 @@ public static string Support_EmailAddress { } } + /// + /// Looks up a localized string similar to An validation link has been sent to your email address. Please click the link in the email to verify the account.. + /// + public static string Support_EmailValidationContent { + get { + return ResourceManager.GetString("Support_EmailValidationContent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Please verify your email address. + /// + public static string Support_EmailValidationTitle { + get { + return ResourceManager.GetString("Support_EmailValidationTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to Enter current password. /// @@ -1014,6 +1058,51 @@ public static string Support_EnterPassword { } } + /// + /// Looks up a localized string similar to An error occurred when attempting to change your password.. + /// + public static string Support_Error_ChangingPasswordFail { + get { + return ResourceManager.GetString("Support_Error_ChangingPasswordFail", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A user account with this email address already exists.. + /// + public static string Support_Error_DuplicateEmail { + get { + return ResourceManager.GetString("Support_Error_DuplicateEmail", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A user account with this user name already exists.. + /// + public static string Support_Error_DuplicateUserName { + get { + return ResourceManager.GetString("Support_Error_DuplicateUserName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The password you have entered is invalid.. + /// + public static string Support_Error_InvalidPassword { + get { + return ResourceManager.GetString("Support_Error_InvalidPassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An unknown error has occurred.. + /// + public static string Support_Error_UnknownError { + get { + return ResourceManager.GetString("Support_Error_UnknownError", resourceCulture); + } + } + /// /// Looks up a localized string similar to Highest version. /// @@ -1095,15 +1184,6 @@ public static string Support_RememberMe { } } - /// - /// Looks up a localized string similar to Thank you for registering. - /// - public static string Support_ThanksRegister { - get { - return ResourceManager.GetString("Support_ThanksRegister", resourceCulture); - } - } - /// /// Looks up a localized string similar to Username. /// @@ -1113,6 +1193,42 @@ public static string Support_UserName { } } + /// + /// Looks up a localized string similar to Please check the validation link in your email and try again.. + /// + public static string Support_ValidationFailureContent { + get { + return ResourceManager.GetString("Support_ValidationFailureContent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to We were unable to validate your account.. + /// + public static string Support_ValidationFailureTitle { + get { + return ResourceManager.GetString("Support_ValidationFailureTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You will now be able to log in using your username and password.. + /// + public static string Support_ValidationSuccessContent { + get { + return ResourceManager.GetString("Support_ValidationSuccessContent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You have successfully validated your account.. + /// + public static string Support_ValidationSuccessTitle { + get { + return ResourceManager.GetString("Support_ValidationSuccessTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to Week. /// diff --git a/BuildFeed.Local/VariantTerms.qps-ploc.resx b/BuildFeed.Local/VariantTerms.qps-ploc.resx index 3e1bc4f..29565c5 100644 --- a/BuildFeed.Local/VariantTerms.qps-ploc.resx +++ b/BuildFeed.Local/VariantTerms.qps-ploc.resx @@ -159,6 +159,9 @@ [!!! Âδ₥ïñ !!!] + + [!!! Çλáñϱè ƥáƨƨωôřδ ℓ !!!] + [!!! Çôñƭřïβúƭè ôñ {0} !!!] @@ -207,6 +210,20 @@ [!!! Tωïƭƭèř !!!] + + [!!! Tλáñƙ ¥ôú ƒôř řèϱïƨƭèřïñϱ ƭô {0}. ℓôřè !!!] +[!!! !!!] +[!!! Þℓèáƨè Ʋèř ¥ôúř è₥áïℓ áδδřèƨƨ β¥ çℓïçƙïñϱ ƭλè ℓïñƙ βèℓôω, ôř β¥ çôƥ¥ïñϱ áñδ ƥáƨƭïñϱ ïƭ ïñƭô ¥ôúř βřôωƨèř. ℓôřè₥ ïƥƨú₥ δôℓ !!!] +[!!! {1} !!!] +[!!! !!!] +[!!! ̃ ¥ôú δïδ ñôƭ řèϱïƨƭèř ƭô {0}, ¥ôú çáñ ïϱñôřè ƭλïƨ è₥áïℓ. ℓôřè₥ ïƥ !!!] +[!!! !!!] +[!!! Tλáñƙƨ, !!!] +[!!! Tλè {0} Tèá₥. ℓ !!!] + + + [!!! {0}: Þℓèáƨè Ʋèř ¥ôúř è₥áïℓ áδδřèƨƨ ℓôřè₥ !!!] + [!!! Âβôúƭ !!!] @@ -405,9 +422,6 @@ [!!! Ýèář !!!] - - [!!! ÉƲèř¥ áççôúñƭ ïƨ Ʋáℓïδáƭèδ β¥ áñ Âδ₥ïñïƨƭřáƭôř, ƨô βè ƥáƭïèñƭ áñδ çλèçƙ áϱáïñ ℓáƭèř. ℓôřè₥ ï !!!] - [!!! Âδδïƭïôñƨ ƭô ßúïℓδFèèδ ℓ !!!] @@ -426,6 +440,12 @@ [!!! É₥áïℓ áδδřèƨƨ !!!] + + [!!! Âñ Ʋáℓïδáƭïôñ ℓïñƙ λáƨ βèèñ ƨèñƭ ƭô ¥ôúř è₥áïℓ áδδřèƨƨ. Þℓèáƨè çℓïçƙ ƭλè ℓïñƙ ïñ ƭλè è₥áïℓ ƭô Ʋèř ƭλè áççôúñƭ. ℓôřè₥ ïƥƨú₥ δôℓô !!!] + + + [!!! Þℓèáƨè Ʋèř ¥ôúř è₥áïℓ áδδřèƨƨ ℓôřè !!!] + [!!! Éñƭèř çúřřèñƭ ƥáƨƨωôřδ ℓ !!!] @@ -435,6 +455,21 @@ [!!! Éñƭèř ƥáƨƨωôřδ !!!] + + [!!! Âñ èřřôř ôççúřřèδ ωλèñ áƭƭè₥ƥƭïñϱ ƭô çλáñϱè ¥ôúř ƥáƨƨωôřδ. ℓôřè₥ ïƥ !!!] + + + [!!!  úƨèř áççôúñƭ ωïƭλ ƭλïƨ è₥áïℓ áδδřèƨƨ áℓřèáδ¥ èжïƨƭƨ. ℓôřè₥ ï !!!] + + + [!!!  úƨèř áççôúñƭ ωïƭλ ƭλïƨ úƨèř ñá₥è áℓřèáδ¥ èжïƨƭƨ. ℓôřè₥ ï !!!] + + + [!!! Tλè ƥáƨƨωôřδ ¥ôú λáƲè èñƭèřèδ ïƨ ïñƲáℓïδ. ℓôřè₥ !!!] + + + [!!! Âñ úñƙñôωñ èřřôř λáƨ ôççúřřèδ. ℓôřè !!!] + [!!! Hïϱλèƨƭ Ʋèřƨïôñ ℓ !!!] @@ -462,12 +497,21 @@ [!!! Rè₥è₥βèř ₥è !!!] - - [!!! Tλáñƙ ¥ôú ƒôř řèϱïƨƭèřïñϱ ℓô !!!] - [!!! Ûƨèřñá₥è !!!] + + [!!! Þℓèáƨè çλèçƙ ƭλè Ʋáℓïδáƭïôñ ℓïñƙ ïñ ¥ôúř è₥áïℓ áñδ ƭř¥ áϱáïñ. ℓôřè₥ ïƥ !!!] + + + [!!! Wè ωèřè úñáβℓè ƭô Ʋáℓïδáƭè ¥ôúř áççôúñƭ. ℓôřè₥ !!!] + + + [!!! Ýôú ωïℓℓ ñôω βè áβℓè ƭô ℓôϱ ïñ úƨïñϱ ¥ôúř úƨèřñá₥è áñδ ƥáƨƨωôřδ. ℓôřè₥ ïƥƨ !!!] + + + [!!! Ýôú λáƲè ƨúççèƨƨƒúℓℓ¥ Ʋáℓïδáƭèδ ¥ôúř áççôúñƭ. ℓôřè₥ !!!] + [!!! Wèèƙ !!!] diff --git a/BuildFeed.Local/VariantTerms.resx b/BuildFeed.Local/VariantTerms.resx index 1108b2f..a859dcf 100644 --- a/BuildFeed.Local/VariantTerms.resx +++ b/BuildFeed.Local/VariantTerms.resx @@ -159,6 +159,9 @@ Admin + + Change password + Contribute on {0} @@ -207,6 +210,20 @@ Twitter + + Thank you for registering to {0}. + +Please verify your email address by clicking the link below, or by copying and pasting it into your browser. +{1} + +If you did not register to {0}, you can ignore this email. + +Thanks, +The {0} Team. + + + {0}: Please verify your email address + About @@ -405,9 +422,6 @@ Year - - Every account is validated by an administrator, so be patient and check again later - Additions to BuildFeed @@ -426,6 +440,12 @@ Email address + + An validation link has been sent to your email address. Please click the link in the email to verify the account. + + + Please verify your email address + Enter current password @@ -435,6 +455,21 @@ Enter password + + An error occurred when attempting to change your password. + + + A user account with this email address already exists. + + + A user account with this user name already exists. + + + The password you have entered is invalid. + + + An unknown error has occurred. + Highest version @@ -462,12 +497,21 @@ Remember me - - Thank you for registering - Username + + Please check the validation link in your email and try again. + + + We were unable to validate your account. + + + You will now be able to log in using your username and password. + + + You have successfully validated your account. + Week diff --git a/BuildFeed/Areas/admin/Views/users/index.cshtml b/BuildFeed/Areas/admin/Views/users/index.cshtml index e3d7e34..c84d790 100644 --- a/BuildFeed/Areas/admin/Views/users/index.cshtml +++ b/BuildFeed/Areas/admin/Views/users/index.cshtml @@ -46,17 +46,15 @@ @mu.Email - @(mu.CreationDate == default(DateTime) - ? "Never" - : mu.CreationDate.Humanize()) + @mu.CreationDate.Humanize() - @(mu.LastLoginDate == default(DateTime) + @(mu.LastLoginDate == DateTime.MinValue ? "Never" : mu.LastLoginDate.Humanize()) - @(mu.LastLockoutDate == default(DateTime) + @(mu.LastLockoutDate == DateTime.MinValue ? "Never" : mu.LastLockoutDate.Humanize()) diff --git a/BuildFeed/BuildFeed.csproj b/BuildFeed/BuildFeed.csproj index 49ac9e7..fd1aba6 100644 --- a/BuildFeed/BuildFeed.csproj +++ b/BuildFeed/BuildFeed.csproj @@ -73,23 +73,23 @@ ..\packages\Microsoft.ApplicationInsights.Agent.Intercept.2.0.7\lib\net45\Microsoft.AI.Agent.Intercept.dll - - ..\packages\Microsoft.ApplicationInsights.DependencyCollector.2.2.0\lib\net45\Microsoft.AI.DependencyCollector.dll + + ..\packages\Microsoft.ApplicationInsights.DependencyCollector.2.3.0\lib\net45\Microsoft.AI.DependencyCollector.dll - - ..\packages\Microsoft.ApplicationInsights.PerfCounterCollector.2.2.0\lib\net45\Microsoft.AI.PerfCounterCollector.dll + + ..\packages\Microsoft.ApplicationInsights.PerfCounterCollector.2.3.0\lib\net45\Microsoft.AI.PerfCounterCollector.dll - - ..\packages\Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.2.2.0\lib\net45\Microsoft.AI.ServerTelemetryChannel.dll + + ..\packages\Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.2.3.0\lib\net45\Microsoft.AI.ServerTelemetryChannel.dll - - ..\packages\Microsoft.ApplicationInsights.Web.2.2.0\lib\net45\Microsoft.AI.Web.dll + + ..\packages\Microsoft.ApplicationInsights.Web.2.3.0\lib\net45\Microsoft.AI.Web.dll - - ..\packages\Microsoft.ApplicationInsights.WindowsServer.2.2.0\lib\net45\Microsoft.AI.WindowsServer.dll + + ..\packages\Microsoft.ApplicationInsights.WindowsServer.2.3.0\lib\net45\Microsoft.AI.WindowsServer.dll - - ..\packages\Microsoft.ApplicationInsights.2.2.0\lib\net46\Microsoft.ApplicationInsights.dll + + ..\packages\Microsoft.ApplicationInsights.2.3.0\lib\net46\Microsoft.ApplicationInsights.dll ..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.3\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll @@ -109,10 +109,10 @@ ..\packages\MongoDB.Driver.Core.2.4.3\lib\net45\MongoDB.Driver.Core.dll - ..\packages\Newtonsoft.Json.10.0.1\lib\net45\Newtonsoft.Json.dll + ..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll - ..\packages\OneSignal.CSharp.SDK.0.9\lib\net45\OneSignal.CSharp.SDK.dll + ..\packages\OneSignal.CSharp.SDK.0.10\lib\net45\OneSignal.CSharp.SDK.dll ..\packages\RestSharp.105.2.3\lib\net46\RestSharp.dll @@ -195,6 +195,7 @@ + @@ -202,11 +203,14 @@ + + + @@ -391,10 +395,10 @@ - - - - + + + + @@ -416,6 +420,8 @@ + + diff --git a/BuildFeed/BuildFeed.csproj.DotSettings b/BuildFeed/BuildFeed.csproj.DotSettings new file mode 100644 index 0000000..9adb28e --- /dev/null +++ b/BuildFeed/BuildFeed.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/BuildFeed/Code/Base32Encoding.cs b/BuildFeed/Code/Base32Encoding.cs new file mode 100644 index 0000000..866249c --- /dev/null +++ b/BuildFeed/Code/Base32Encoding.cs @@ -0,0 +1,133 @@ +using System; + +namespace BuildFeed.Code +{ + /* Based off http://stackoverflow.com/a/7135008 */ + public class Base32Encoding + { + public static byte[] ToBytes(string input) + { + if (string.IsNullOrEmpty(input)) + { + throw new ArgumentNullException(nameof(input)); + } + + input = input.TrimEnd('='); //remove padding characters + int byteCount = input.Length * 5 / 8; //this must be TRUNCATED + var returnArray = new byte[byteCount]; + + byte curByte = 0, + bitsRemaining = 8; + + int arrayIndex = 0; + + foreach (char c in input) + { + int cValue = CharToValue(c); + + int mask; + if (bitsRemaining > 5) + { + mask = cValue << (bitsRemaining - 5); + curByte = (byte)(curByte | mask); + bitsRemaining -= 5; + } + else + { + mask = cValue >> (5 - bitsRemaining); + curByte = (byte)(curByte | mask); + returnArray[arrayIndex++] = curByte; + curByte = (byte)(cValue << (3 + bitsRemaining)); + bitsRemaining += 3; + } + } + + //if we didn't end with a full byte + if (arrayIndex != byteCount) + { + returnArray[arrayIndex] = curByte; + } + + return returnArray; + } + + public static string ToString(byte[] input) + { + if (input == null || input.Length == 0) + { + throw new ArgumentNullException(nameof(input)); + } + + int charCount = (int)Math.Ceiling(input.Length / 5d) * 8; + var returnArray = new char[charCount]; + + byte nextChar = 0, + bitsRemaining = 5; + int arrayIndex = 0; + + foreach (byte b in input) + { + nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining))); + returnArray[arrayIndex++] = ValueToChar(nextChar); + + if (bitsRemaining < 4) + { + nextChar = (byte)((b >> (3 - bitsRemaining)) & 31); + returnArray[arrayIndex++] = ValueToChar(nextChar); + bitsRemaining += 5; + } + + bitsRemaining -= 3; + nextChar = (byte)((b << bitsRemaining) & 31); + } + + //if we didn't end with a full char + if (arrayIndex != charCount) + { + returnArray[arrayIndex++] = ValueToChar(nextChar); + while (arrayIndex != charCount) + returnArray[arrayIndex++] = '='; //padding + } + + return new string(returnArray); + } + + private static int CharToValue(char c) + { + int value = c; + + //65-90 == uppercase letters + if (value < 91 && value > 64) + { + return value - 65; + } + //50-55 == numbers 2-7 + if (value < 56 && value > 49) + { + return value - 24; + } + //97-122 == lowercase letters + if (value < 123 && value > 96) + { + return value - 97; + } + + throw new ArgumentException("Character is not a Base32 character.", nameof(c)); + } + + private static char ValueToChar(byte b) + { + if (b < 26) + { + return (char)(b + 65); + } + + if (b < 32) + { + return (char)(b + 24); + } + + throw new ArgumentException("Byte is not a value Base32 value.", nameof(b)); + } + } +} \ No newline at end of file diff --git a/BuildFeed/Code/EmailManager/EmailManager.cs b/BuildFeed/Code/EmailManager/EmailManager.cs new file mode 100644 index 0000000..d62bd88 --- /dev/null +++ b/BuildFeed/Code/EmailManager/EmailManager.cs @@ -0,0 +1,7 @@ +namespace BuildFeed.Code +{ + public static partial class EmailManager + { + private const string EMAIL_FROM = "thomas@buildfeed.net"; + } +} \ No newline at end of file diff --git a/BuildFeed/Code/EmailManager/RegistrationEmail.cs b/BuildFeed/Code/EmailManager/RegistrationEmail.cs new file mode 100644 index 0000000..32374c5 --- /dev/null +++ b/BuildFeed/Code/EmailManager/RegistrationEmail.cs @@ -0,0 +1,24 @@ +using System.Net.Mail; +using System.Threading.Tasks; +using System.Web.Security; +using BuildFeed.Local; + +namespace BuildFeed.Code +{ + public static partial class EmailManager + { + public static async Task SendRegistrationEmail(MembershipUser mu, string validationLink) + { + using (MailMessage mm = new MailMessage(EMAIL_FROM, mu.Email)) + { + mm.Subject = string.Format(VariantTerms.Email_Registration_Subject, InvariantTerms.SiteName); + mm.Body = string.Format(VariantTerms.Email_Registration_Body, InvariantTerms.SiteName, validationLink); + + using (SmtpClient sc = new SmtpClient()) + { + await sc.SendMailAsync(mm); + } + } + } + } +} \ No newline at end of file diff --git a/BuildFeed/Controllers/AccountController.cs b/BuildFeed/Controllers/AccountController.cs new file mode 100644 index 0000000..614f3ae --- /dev/null +++ b/BuildFeed/Controllers/AccountController.cs @@ -0,0 +1,156 @@ +using System; +using System.Net; +using System.Threading.Tasks; +using System.Web; +using System.Web.Mvc; +using System.Web.Security; +using BuildFeed.Code; +using BuildFeed.Local; +using BuildFeed.Model.View; +using MongoAuth; + +namespace BuildFeed.Controllers +{ + public class AccountController : BaseController + { + [Route("login/")] + public ActionResult Login() => View(); + + [HttpPost] + [ValidateAntiForgeryToken] + [Route("login/")] + public ActionResult Login(LoginUser ru) + { + if (ModelState.IsValid) + { + bool isAuthenticated = Membership.ValidateUser(ru.UserName, ru.Password); + + if (isAuthenticated) + { + int expiryLength = ru.RememberMe + ? 129600 + : 60; + + FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(ru.UserName, true, expiryLength); + string encryptedTicket = FormsAuthentication.Encrypt(ticket); + HttpCookie cookieTicket = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket) + { + Expires = DateTime.Now.AddMinutes(expiryLength), + Path = FormsAuthentication.FormsCookiePath + }; + Response.Cookies.Add(cookieTicket); + + string returnUrl = string.IsNullOrEmpty(Request.QueryString["ReturnUrl"]) + ? "/" + : Request.QueryString["ReturnUrl"]; + + return Redirect(returnUrl); + } + } + + ViewData["ErrorMessage"] = "The username and password are not valid."; + return View(ru); + } + + [Authorize] + [Route("password/")] + public ActionResult Password() => View(); + + [HttpPost] + [ValidateAntiForgeryToken] + [Authorize] + [Route("password/")] + public ActionResult Password(ChangePassword cp) + { + if (ModelState.IsValid) + { + MembershipUser user = Membership.GetUser(); + + if (user != null) + { + bool success = user.ChangePassword(cp.OldPassword, cp.NewPassword); + + if (success) + { + return Redirect("/"); + } + } + } + + ViewData["ErrorMessage"] = VariantTerms.Support_Error_ChangingPasswordFail; + return View(cp); + } + + [Route("logout/")] + public ActionResult Logout() + { + FormsAuthentication.SignOut(); + return Redirect("/"); + } + + [Route("register/")] + public ActionResult Register() => View(); + + [HttpPost] + [ValidateAntiForgeryToken] + [Route("register/")] + public async Task Register(RegistrationUser ru) + { + if (ModelState.IsValid) + { + MembershipCreateStatus status; + MembershipUser mu = Membership.CreateUser(ru.UserName, ru.Password, ru.EmailAddress, "{IGNORE}", "{IGNORE}", false, out status); + + switch (status) + { + case MembershipCreateStatus.Success: + { + Roles.AddUserToRole(mu.UserName, "Users"); + + MongoMembershipProvider provider = (MongoMembershipProvider)Membership.Provider; + Guid id = (Guid)mu.ProviderUserKey; + string hash = (await provider.GenerateValidationHash(id)).ToLower(); + + string fullUrl = Request.Url?.GetLeftPart(UriPartial.Authority) + Url.Action("Validate", + "Account", + new + { + id, + hash + }); + await EmailManager.SendRegistrationEmail(mu, fullUrl); + return RedirectToAction(nameof(RegisterThanks)); + } + case MembershipCreateStatus.InvalidPassword: + ViewData["ErrorMessage"] = VariantTerms.Support_Error_InvalidPassword; + break; + case MembershipCreateStatus.DuplicateEmail: + ViewData["ErrorMessage"] = VariantTerms.Support_Error_DuplicateEmail; + break; + case MembershipCreateStatus.DuplicateUserName: + ViewData["ErrorMessage"] = VariantTerms.Support_Error_DuplicateUserName; + break; + default: + ViewData["ErrorMessage"] = VariantTerms.Support_Error_UnknownError; + break; + } + } + + return View(ru); + } + + [Route("register/thanks/")] + public ActionResult RegisterThanks() => View(); + + [Route("validate/{id:guid}/{hash}/")] + public async Task Validate(Guid id, string hash) + { + MongoMembershipProvider provider = (MongoMembershipProvider)Membership.Provider; + bool success = await provider.ValidateUserFromHash(id, hash); + + return View(success + ? "validate-success" + : "validate-failure"); + } + } +} \ No newline at end of file diff --git a/BuildFeed/Controllers/apiController.cs b/BuildFeed/Controllers/apiController.cs index 6c689a6..c7e8058 100644 --- a/BuildFeed/Controllers/apiController.cs +++ b/BuildFeed/Controllers/apiController.cs @@ -12,6 +12,7 @@ using BuildFeed.Model.View; using OneSignal.CSharp.SDK; +#pragma warning disable SG0016 // Controller method is vulnerable to CSRF - Not relevant for API namespace BuildFeed.Controllers { public class ApiController : System.Web.Http.ApiController @@ -70,7 +71,7 @@ public async Task AddWin10Builds(NewBuildPost apiModel) { return false; } - if (Membership.ValidateUser(apiModel.Username, apiModel.Password)) + if (Membership.ValidateUser(apiModel.Username, apiModel.Password) && (Roles.IsUserInRole(apiModel.Username, "Editors") || Roles.IsUserInRole(apiModel.Username, "Administrators"))) { IEnumerable builds = apiModel.NewBuilds.Select(nb => new Build { diff --git a/BuildFeed/Controllers/frontController.cs b/BuildFeed/Controllers/frontController.cs index c348c3b..7941dec 100644 --- a/BuildFeed/Controllers/frontController.cs +++ b/BuildFeed/Controllers/frontController.cs @@ -378,7 +378,7 @@ public async Task ViewVersionPage(uint major, uint minor, int page } [Route("add/")] - [Authorize] + [Authorize(Roles = "Administrators,Editors")] public ActionResult AddBuild() { Build b = new Build @@ -389,7 +389,8 @@ public ActionResult AddBuild() } [Route("add/")] - [Authorize] + [ValidateAntiForgeryToken] + [Authorize(Roles = "Administrators,Editors")] [HttpPost] public async Task AddBuild(Build build) { @@ -429,14 +430,15 @@ public async Task AddBuild(Build build) } [Route("bulk/")] - [Authorize] + [Authorize(Roles = "Administrators")] public ActionResult AddBulk() { return View(); } [Route("bulk/")] - [Authorize] + [ValidateAntiForgeryToken] + [Authorize(Roles = "Administrators")] [HttpPost] public async Task AddBulk(FormCollection values) { @@ -509,7 +511,7 @@ public async Task AddBulk(FormCollection values) } [Route("edit/{id}/")] - [Authorize] + [Authorize(Roles = "Administrators,Editors")] public async Task EditBuild(Guid id) { Build b = await _bModel.SelectById(id); @@ -517,7 +519,8 @@ public async Task EditBuild(Guid id) } [Route("edit/{id}/")] - [Authorize] + [ValidateAntiForgeryToken] + [Authorize(Roles = "Administrators,Editors")] [HttpPost] public async Task EditBuild(Guid id, Build build) { diff --git a/BuildFeed/Controllers/supportController.cs b/BuildFeed/Controllers/supportController.cs index ee40042..4a9c7bc 100644 --- a/BuildFeed/Controllers/supportController.cs +++ b/BuildFeed/Controllers/supportController.cs @@ -2,10 +2,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using System.Web; using System.Web.Mvc; using System.Web.Routing; -using System.Web.Security; using System.Xml.Linq; using BuildFeed.Code; using BuildFeed.Local; @@ -23,116 +21,6 @@ public SupportController() _bModel = new BuildRepository(); } - [Route("login/")] - public ActionResult Login() => View(); - - [HttpPost] - [Route("login/")] - public ActionResult Login(LoginUser ru) - { - if (ModelState.IsValid) - { - bool isAuthenticated = Membership.ValidateUser(ru.UserName, ru.Password); - - if (isAuthenticated) - { - int expiryLength = ru.RememberMe - ? 129600 - : 60; - - FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(ru.UserName, true, expiryLength); - string encryptedTicket = FormsAuthentication.Encrypt(ticket); - HttpCookie cookieTicket = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket) - { - Expires = DateTime.Now.AddMinutes(expiryLength), - Path = FormsAuthentication.FormsCookiePath - }; - Response.Cookies.Add(cookieTicket); - - string returnUrl = string.IsNullOrEmpty(Request.QueryString["ReturnUrl"]) - ? "/" - : Request.QueryString["ReturnUrl"]; - - return Redirect(returnUrl); - } - } - - ViewData["ErrorMessage"] = "The username and password are not valid."; - return View(ru); - } - - [Authorize] - [Route("password/")] - public ActionResult Password() => View(); - - [HttpPost] - [Authorize] - [Route("password/")] - public ActionResult Password(ChangePassword cp) - { - if (ModelState.IsValid) - { - MembershipUser user = Membership.GetUser(); - - if (user != null) - { - bool success = user.ChangePassword(cp.OldPassword, cp.NewPassword); - - if (success) - { - return Redirect("/"); - } - } - } - - ViewData["ErrorMessage"] = "There was an error changing your password."; - return View(cp); - } - - [Route("logout/")] - public ActionResult Logout() - { - FormsAuthentication.SignOut(); - return Redirect("/"); - } - - [Route("register/")] - public ActionResult Register() => View(); - - [HttpPost] - [Route("register/")] - public ActionResult Register(RegistrationUser ru) - { - if (ModelState.IsValid) - { - MembershipCreateStatus status; - Membership.CreateUser(ru.UserName, ru.Password, ru.EmailAddress, "THIS WILL BE IGNORED", "I WILL BE IGNORED", false, out status); - - switch (status) - { - case MembershipCreateStatus.Success: - return RedirectToAction("thanks_register"); - case MembershipCreateStatus.InvalidPassword: - ViewData["ErrorMessage"] = "The password is invalid."; - break; - case MembershipCreateStatus.DuplicateEmail: - ViewData["ErrorMessage"] = "A user account with this email address already exists."; - break; - case MembershipCreateStatus.DuplicateUserName: - ViewData["ErrorMessage"] = "A user account with this user name already exists."; - break; - default: - ViewData["ErrorMessage"] = "Unspecified error."; - break; - } - } - - return View(ru); - } - - [Route("register/thanks/")] - public ActionResult thanks_register() => View(); - [Route("rss")] public async Task Rss() { diff --git a/BuildFeed/Global.asax.cs b/BuildFeed/Global.asax.cs index ff8ad5b..c72603a 100644 --- a/BuildFeed/Global.asax.cs +++ b/BuildFeed/Global.asax.cs @@ -4,6 +4,7 @@ using System.Web; using System.Web.Mvc; using System.Web.Routing; +using System.Web.Security; using BuildFeed.Code; using BuildFeed.Code.Options; using BuildFeed.Model; @@ -30,6 +31,10 @@ protected void Application_Start() ModelBinders.Binders.Add(typeof(DateTime), db); ModelBinders.Binders.Add(typeof(DateTime?), db); + Roles.CreateRole("Administrators"); + Roles.CreateRole("Editors"); + Roles.CreateRole("Users"); + MongoConfig.SetupIndexes(); } diff --git a/BuildFeed/Views/support/login.cshtml b/BuildFeed/Views/account/login.cshtml similarity index 90% rename from BuildFeed/Views/support/login.cshtml rename to BuildFeed/Views/account/login.cshtml index 08f93ee..4746f83 100644 --- a/BuildFeed/Views/support/login.cshtml +++ b/BuildFeed/Views/account/login.cshtml @@ -1,4 +1,5 @@ -@model BuildFeed.Model.View.LoginUser +@using BuildFeed.Controllers +@model BuildFeed.Model.View.LoginUser @{ ViewBag.Title = $"{VariantTerms.Support_Login} | {InvariantTerms.SiteName}"; @@ -57,9 +58,9 @@
  - @Html.ActionLink(VariantTerms.Support_Register, "register", new + @Html.ActionLink(VariantTerms.Support_Register, nameof(AccountController.Register), new { - controller = "Support" + controller = "Account" }, new { @class = "btn btn-default" diff --git a/BuildFeed/Views/support/password.cshtml b/BuildFeed/Views/account/password.cshtml similarity index 100% rename from BuildFeed/Views/support/password.cshtml rename to BuildFeed/Views/account/password.cshtml diff --git a/BuildFeed/Views/support/register.cshtml b/BuildFeed/Views/account/register.cshtml similarity index 100% rename from BuildFeed/Views/support/register.cshtml rename to BuildFeed/Views/account/register.cshtml diff --git a/BuildFeed/Views/account/registerthanks.cshtml b/BuildFeed/Views/account/registerthanks.cshtml new file mode 100644 index 0000000..fc0048d --- /dev/null +++ b/BuildFeed/Views/account/registerthanks.cshtml @@ -0,0 +1,6 @@ +@{ + ViewBag.Title = $"{VariantTerms.Support_EmailValidationTitle} | {InvariantTerms.SiteName}"; +} + +

@VariantTerms.Support_EmailValidationTitle

+

@VariantTerms.Support_EmailValidationContent

\ No newline at end of file diff --git a/BuildFeed/Views/account/validate-failure.cshtml b/BuildFeed/Views/account/validate-failure.cshtml new file mode 100644 index 0000000..c5ba5f7 --- /dev/null +++ b/BuildFeed/Views/account/validate-failure.cshtml @@ -0,0 +1,6 @@ +@{ + ViewBag.Title = $"{VariantTerms.Support_ValidationFailureTitle} | {InvariantTerms.SiteName}"; +} + +

@VariantTerms.Support_ValidationFailureTitle

+

@VariantTerms.Support_ValidationFailureContent

\ No newline at end of file diff --git a/BuildFeed/Views/account/validate-success.cshtml b/BuildFeed/Views/account/validate-success.cshtml new file mode 100644 index 0000000..074f818 --- /dev/null +++ b/BuildFeed/Views/account/validate-success.cshtml @@ -0,0 +1,6 @@ +@{ + ViewBag.Title = $"{VariantTerms.Support_ValidationSuccessTitle} | {InvariantTerms.SiteName}"; +} + +

@VariantTerms.Support_ValidationSuccessTitle

+

@VariantTerms.Support_ValidationSuccessContent

\ No newline at end of file diff --git a/BuildFeed/Views/front/viewBuild.cshtml b/BuildFeed/Views/front/viewBuild.cshtml index 1715229..d5f622f 100644 --- a/BuildFeed/Views/front/viewBuild.cshtml +++ b/BuildFeed/Views/front/viewBuild.cshtml @@ -143,7 +143,7 @@

-@if (User.Identity.IsAuthenticated) +@if (Roles.IsUserInRole("Editors") || Roles.IsUserInRole("Administrators")) {

@VariantTerms.Front_EditorActions

diff --git a/BuildFeed/Views/front/viewGroup.cshtml b/BuildFeed/Views/front/viewGroup.cshtml index 749eaf0..1518ed2 100644 --- a/BuildFeed/Views/front/viewGroup.cshtml +++ b/BuildFeed/Views/front/viewGroup.cshtml @@ -49,7 +49,7 @@ @VariantTerms.Front_Private

} - @if (User.Identity.IsAuthenticated) + @if (Roles.IsUserInRole("Editors") || Roles.IsUserInRole("Administrators")) {

@VariantTerms.Front_Private

} - @if (User.Identity.IsAuthenticated) + @if (Roles.IsUserInRole("Editors") || Roles.IsUserInRole("Administrators")) {

@VariantTerms.Front_Private

} - @if (User.Identity.IsAuthenticated) + @if (Roles.IsUserInRole("Editors") || Roles.IsUserInRole("Administrators")) {

@VariantTerms.Front_Private

} - @if (User.Identity.IsAuthenticated) + @if (Roles.IsUserInRole("Editors") || Roles.IsUserInRole("Administrators")) {

@VariantTerms.Front_Private

} - @if (User.Identity.IsAuthenticated) + @if (Roles.IsUserInRole("Editors") || Roles.IsUserInRole("Administrators")) {

- - - - - @ViewBag.Title - - - @if (isRtl) - { - - } - - - - - - - - - - - - - @RenderSection("head", false) + + + + + @ViewBag.Title + + + @if (isRtl) + { + + } + + + + + + + + + + + + + @RenderSection("head", false) - - - + + + window.appInsights = appInsights; + appInsights.trackPageView(); + - -

+ -
-
- @RenderBody() -
-
-
+ - + - - -@RenderSection("scripts", false) - - + + + @RenderSection("scripts", false) + + \ No newline at end of file diff --git a/BuildFeed/Views/support/thanks_register.cshtml b/BuildFeed/Views/support/thanks_register.cshtml deleted file mode 100644 index 9a5bd01..0000000 --- a/BuildFeed/Views/support/thanks_register.cshtml +++ /dev/null @@ -1,6 +0,0 @@ -@{ - ViewBag.Title = $"{VariantTerms.Support_ThanksRegister} | {InvariantTerms.SiteName}"; -} - -

@VariantTerms.Support_ThanksRegister

-

@VariantTerms.Support_AccountValidation

\ No newline at end of file diff --git a/BuildFeed/packages.config b/BuildFeed/packages.config index b221354..6034e06 100644 --- a/BuildFeed/packages.config +++ b/BuildFeed/packages.config @@ -47,13 +47,13 @@ - + - - - - - + + + + + @@ -69,8 +69,8 @@ - - + + diff --git a/BuildFeed/res/css/default.css b/BuildFeed/res/css/default.css index ca5d4ca..9444140 100644 --- a/BuildFeed/res/css/default.css +++ b/BuildFeed/res/css/default.css @@ -1,2 +1,2 @@ -body{font-family:Roboto,sans-serif;font-size:10pt;line-height:1.6;margin:0;box-sizing:border-box}a{text-decoration:none}a:active,a:focus,a:hover{text-decoration:underline}h1{font-size:3em;font-weight:500}p{margin:0 0 1em}table{width:100%;border-collapse:collapse}table td,table th{margin:0;border:0;padding:4px 6px}table thead th{border-bottom:1px solid;text-align:left}.at-share-btn-elements{margin-left:-8px}.at-share-btn-elements>.at_flat_counter{font-size:14px!important;vertical-align:top!important}.at-share-btn-elements>.at-share-btn{margin-left:8px!important}.container{width:1240px;max-width:80%;margin:0 auto;position:relative}.no-wrapping{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.eager-wrapping{word-wrap:break-word}.field-validation-error{display:block;margin:.33333em 0;color:#ff2626}a.button,input[type=submit].button{display:inline-block;vertical-align:middle;padding:.33333em 1em;background-color:#0a81cc;color:#fff;text-decoration:none;border-radius:.16667em;text-align:center}a.button.edit-button,input[type=submit].button.edit-button{background-color:#ff9f19}a.button.delete-button,input[type=submit].button.delete-button{background-color:#ff2626}a.button.add-button,input[type=submit].button.add-button{background-color:#12b23f}header#page-header h1{font-weight:300;margin:.33em 0}header#page-header h1 a{text-decoration:none}nav#page-navigation{border-bottom-width:1px;border-bottom-style:solid;position:relative;z-index:10}nav#page-navigation #page-navigation-links{margin:0 -15px;padding:0;text-align:right}nav#page-navigation #page-navigation-links>li{display:inline-block;vertical-align:top}nav#page-navigation #page-navigation-links>li>a{display:block;padding:15px}nav#page-navigation #page-navigation-links>li>a:active,nav#page-navigation #page-navigation-links>li>a:focus,nav#page-navigation #page-navigation-links>li>a:hover{text-decoration:none}nav#page-navigation button{display:none;width:100%;border:0;font-size:1.2em;font-weight:300;padding:.66667em 0;background:0 0}.dropdown-parent .dropdown-menu{box-sizing:border-box;display:none;position:absolute;left:-15px;right:-15px;max-width:1270px;padding:15px 15px 0;text-align:left;z-index:10;border-width:1px;border-style:solid;border-top-width:0;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.dropdown-parent .dropdown-menu h4{margin:0 0 .5em;font-size:1.2em;font-weight:500}.dropdown-parent .dropdown-menu .dropdown-menu-block{display:inline-block;vertical-align:top;margin-bottom:15px;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.dropdown-parent .dropdown-menu .dropdown-menu-block ul{padding:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.dropdown-parent .dropdown-menu .dropdown-menu-block li{list-style-type:none;line-height:2em;width:133px;vertical-align:top;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.dropdown-parent .dropdown-menu .dropdown-menu-block li>a{display:block;padding:0 1em}.dropdown-parent .dropdown-menu .dropdown-menu-block li>a:active,.dropdown-parent .dropdown-menu .dropdown-menu-block li>a:focus,.dropdown-parent .dropdown-menu .dropdown-menu-block li>a:hover{text-decoration:none}.dropdown-parent .dropdown-menu #settings-theme-menu{width:120px;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.dropdown-parent .dropdown-menu #settings-lang-menu{width:320px;-webkit-box-flex:1000;-webkit-flex-grow:1000;-ms-flex-positive:1000;flex-grow:1000}.dropdown-parent.open .dropdown-menu{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}article{padding:2em 0}article h1{font-size:4em;font-weight:300;text-align:center;margin:0 0 .5em}article h1.eager-wrapping{font-size:4em}article h3{font-size:2em;font-weight:700;margin:.33333em 0 .66667em;text-transform:uppercase;letter-spacing:.25em;border-bottom:1px solid}article h4{margin:1em 0 0}article .build-group-listing{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;margin:0 -1.5em}article .build-group-listing .build-group{width:250px;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;margin:0 1.5em 3em;box-sizing:border-box;border:3px solid #0a81cc;padding:.5em 1.5em}article .build-group-listing .build-group h3{background-color:#0a81cc;color:#fff;margin:-.25em -.75em .75em;font-size:2em;line-height:1.25em;font-weight:400;padding:0;letter-spacing:0;border-bottom:0;text-transform:lowercase;text-align:center}article .build-group-listing .build-group h3 a{color:#fff;text-decoration:none!important;display:block;padding:.25em 0}article .build-group-listing .build-group p{font-size:1.1em;font-weight:300;margin:0 0 .66667em}article .build-group-listing .build-group-empty{width:270px;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;margin:0 .75em;box-sizing:border-box}.latest-flex{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;margin:2em -2em 0 0}.latest-flex .latest-flex-item{width:240px;box-sizing:border-box;background:#fff;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;margin:0 2em 2em 0}.latest-flex .latest-flex-item:active,.latest-flex .latest-flex-item:focus,.latest-flex .latest-flex-item:hover{text-decoration:none}.latest-flex .latest-flex-item h3.latest-flex-title{border-bottom:0;margin:0;padding:0;font-size:1.25em;font-weight:400;text-align:center;padding:.4em .5em;margin:-1px;color:#fff}.latest-flex .latest-flex-item.latest-flex-red{border:.25em solid #ff2626}.latest-flex .latest-flex-item.latest-flex-red h3.latest-flex-title{background:#ff2626}.latest-flex .latest-flex-item.latest-flex-yellow{border:.25em solid #ff9f19}.latest-flex .latest-flex-item.latest-flex-yellow h3.latest-flex-title{background:#ff9f19}.latest-flex .latest-flex-item.latest-flex-blue{border:.25em solid #0a81cc}.latest-flex .latest-flex-item.latest-flex-blue h3.latest-flex-title{background:#0a81cc}.latest-flex .latest-flex-item.latest-flex-green{border:.25em solid #12b23f}.latest-flex .latest-flex-item.latest-flex-green h3.latest-flex-title{background:#12b23f}.latest-flex .latest-flex-item .latest-flex-detail{color:#373736;text-align:center;font-weight:300}.latest-flex .latest-flex-item .latest-flex-detail .latest-flex-build{font-size:2.33333em;margin:.33333em 0 .16667em}.latest-flex .latest-flex-item .latest-flex-detail .latest-flex-lab{font-size:1.5em;margin:0 0 .5em}.latest-flex .latest-flex-item .latest-flex-detail .latest-flex-time{margin:0 0 .75em}.latest-full{display:block;background:#0a81cc;font-weight:400;text-align:center;color:#fff;margin:0 0 2em;padding:.66667em}.latest-full:active,.latest-full:focus,.latest-full:hover{text-decoration:none}.build-details-flex{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;font-size:1.1em;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.build-details-flex .build-details-flex-item{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;margin-bottom:1.5em;width:180px}.build-details-flex .build-details-flex-item label{font-weight:700;display:inline-block;vertical-align:top;margin-right:1em;min-width:100px}.build-details-flex .build-details-flex-item .build-details-flex-value{display:inline-block;vertical-align:top;margin-right:20px}.form-group{margin-bottom:1.5em;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-group>label{width:20%;max-width:240px;min-width:120px;text-align:left;font-weight:700;margin-right:1em;display:inline-block;vertical-align:top;margin-top:.25em;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.form-group>div{margin-left:calc(20% + 1em);width:40%;min-width:240px;max-width:560px;display:inline-block;vertical-align:top;-webkit-box-flex:2;-webkit-flex-grow:2;-ms-flex-positive:2;flex-grow:2}.form-group>div input,.form-group>div select,.form-group>div textarea{width:100%;box-sizing:border-box;border:1px solid;padding:.33333em .5em;border-radius:.16667em;line-height:1em;border-color:#888}.form-group>div .group-input-button{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.form-group>div .group-input-button input{border-top-right-radius:0;border-bottom-right-radius:0;border-right:0}.form-group>div .group-input-button button{border-top-left-radius:0;border-bottom-left-radius:0;width:120px;border:1px solid #888;border-left:0}.form-group>div input[type=checkbox],.form-group>div input[type=submit]{width:auto}.form-group>div .group-input-button>button,.form-group>div>button,.form-group>div>input[type=submit]{display:inline-block;vertical-align:middle;padding:.33333em 1em;background-color:#12b23f;color:#fff;text-decoration:none;border-radius:.16667em;border:0;line-height:1.6}.form-group>div.wide-group{width:40%}.form-group>div.wide-group>.trumbowyg-box{width:100%;margin:0}.form-group>label+div{margin-left:0}.credits-wrapper{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.credits-wrapper .credits-list{width:480px;max-width:100%;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.credits-list dt{font-weight:700}.credits-list dd{margin-left:0}.credits-list dd+dt{margin-top:1.5em}ul.pagination{text-align:center;margin:.5em 0 1em;padding:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}ul.pagination>li{list-style:none;width:1.8em;line-height:1.8em;margin:0 .33333em}ul.pagination>li>a,ul.pagination>li>span{display:block;border-radius:.33333em;text-decoration:none}ul.pagination>li>span{cursor:not-allowed}footer#page-footer{padding:1.33333em 0 .66667em;font-size:.85em}footer#page-footer .footer-flex{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}footer#page-footer .footer-flex .footer-flex-item{width:50%;min-width:200px;text-align:center;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}footer#page-footer .footer-flex .footer-flex-item:last-child{text-align:right}footer#page-footer .footer-flex .footer-flex-item:first-child{text-align:left}footer#page-footer p{margin:0 0 .4em}#modal-search-overlay{display:none;position:fixed;top:0;bottom:0;left:0;right:0;transition:background-color linear .6s,-webkit-backdrop-filter linear .6s,backdrop-filter linear .6s;background-color:transparent;-webkit-backdrop-filter:blur(0);backdrop-filter:blur(0);z-index:100}#modal-search-overlay.open{display:block;background-color:rgba(0,0,0,.75);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}#modal-search-overlay #modal-search{position:absolute;top:15%;left:25%;width:50%;max-height:70%;overflow:auto;padding:2em;border-radius:2px;box-sizing:border-box;border:1px solid #ccc}#modal-search-overlay #modal-search h3{margin:0 0 1em;font-size:1.5em;font-weight:300}#modal-search-overlay #modal-search>#modal-search-box{width:100%}#modal-search-overlay #modal-search>#modal-search-box>*{display:inline-block;height:2.5em;padding:.5em;box-sizing:border-box;border:1px solid}#modal-search-overlay #modal-search>#modal-search-box>#modal-search-input{width:calc(100% - 3.33333em)}#modal-search-overlay #modal-search>#modal-search-box>#modal-search-button{width:2.66667em}#modal-search-overlay #modal-search>#modal-search-result{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}#modal-search-overlay #modal-search>#modal-search-result>.search-result-item{display:block;padding:0 1em;width:140px;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}#modal-search-overlay #modal-search>#modal-search-result>.search-result-item>.search-result-heading{font-size:1.2em;margin-bottom:0}#menu-open-overlay{display:none;position:fixed;top:0;bottom:0;left:0;right:0;z-index:5}#menu-open-overlay.open{display:block}@media (max-width:980px){#modal-search-overlay #modal-search{left:10%;width:80%}}@media (max-width:640px){header#page-header h1{text-align:center}nav#page-navigation button{display:block}nav#page-navigation #page-navigation-links{display:none}nav#page-navigation #page-navigation-links.open{display:block}nav#page-navigation #page-navigation-links>li{display:block;text-align:left}article h1,article h1.eager-wrapping{font-size:3em;line-height:1.33333}article h3{text-align:center}article .addthis_sharing_toolbox{text-align:center}footer#page-footer .footer-flex .footer-flex-item:first-child{text-align:center;margin-bottom:1.5em}footer#page-footer .footer-flex .footer-flex-item:last-child{text-align:center}}@media (max-width:1300px){.latest-flex .latest-flex-item{min-width:calc(50% - 2em)}} +body{font-family:Roboto,sans-serif;font-size:10pt;line-height:1.6;margin:0;box-sizing:border-box}a{text-decoration:none}a:active,a:focus,a:hover{text-decoration:underline}h1{font-size:3em;font-weight:500}p{margin:0 0 1em}table{width:100%;border-collapse:collapse}table td,table th{margin:0;border:0;padding:4px 6px}table thead th{border-bottom:1px solid;text-align:left}.at-share-btn-elements{margin-left:-8px}.at-share-btn-elements>.at_flat_counter{font-size:14px!important;vertical-align:top!important}.at-share-btn-elements>.at-share-btn{margin-left:8px!important}.container{width:1240px;max-width:80%;margin:0 auto;position:relative}.no-wrapping{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.eager-wrapping{word-wrap:break-word}.field-validation-error,.text-danger{display:block;margin:.33333em 0;color:#ff2626}a.button,input[type=submit].button{display:inline-block;vertical-align:middle;padding:.33333em 1em;background-color:#0a81cc;color:#fff;text-decoration:none;border-radius:.16667em;text-align:center}a.button.edit-button,input[type=submit].button.edit-button{background-color:#ff9f19}a.button.delete-button,input[type=submit].button.delete-button{background-color:#ff2626}a.button.add-button,input[type=submit].button.add-button{background-color:#12b23f}header#page-header h1{font-weight:300;margin:.33em 0}header#page-header h1 a{text-decoration:none}nav#page-navigation{border-bottom-width:1px;border-bottom-style:solid;position:relative;z-index:10}nav#page-navigation #page-navigation-links{margin:0 -15px .33333px;padding:0;text-align:right}nav#page-navigation #page-navigation-links>li{display:inline-block;vertical-align:top}nav#page-navigation #page-navigation-links>li>a{display:block;padding:15px}nav#page-navigation #page-navigation-links>li>a:active,nav#page-navigation #page-navigation-links>li>a:focus,nav#page-navigation #page-navigation-links>li>a:hover{text-decoration:none}nav#page-navigation button{display:none;width:100%;border:0;font-size:1.2em;font-weight:300;padding:.66667em 0;background:0 0}.dropdown-parent .dropdown-menu{box-sizing:border-box;display:none;position:absolute;left:-15px;right:-15px;max-width:1270px;padding:15px 15px 0;text-align:left;z-index:10;border-width:1px;border-style:solid;border-top-width:0;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.dropdown-parent .dropdown-menu h4{margin:0 0 .5em;font-size:1.2em;font-weight:500}.dropdown-parent .dropdown-menu .dropdown-menu-block{display:inline-block;vertical-align:top;margin-bottom:15px;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.dropdown-parent .dropdown-menu .dropdown-menu-block ul{padding:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.dropdown-parent .dropdown-menu .dropdown-menu-block li{list-style-type:none;line-height:2em;width:133px;vertical-align:top;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.dropdown-parent .dropdown-menu .dropdown-menu-block li>a{display:block;padding:0 1em}.dropdown-parent .dropdown-menu .dropdown-menu-block li>a:active,.dropdown-parent .dropdown-menu .dropdown-menu-block li>a:focus,.dropdown-parent .dropdown-menu .dropdown-menu-block li>a:hover{text-decoration:none}.dropdown-parent .dropdown-menu #settings-theme-menu{width:120px;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.dropdown-parent .dropdown-menu #settings-lang-menu{width:320px;-webkit-box-flex:1000;-webkit-flex-grow:1000;-ms-flex-positive:1000;flex-grow:1000}.dropdown-parent.open .dropdown-menu{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}article{padding:2em 0}article h1{font-size:4em;font-weight:300;text-align:center;margin:0 0 .5em}article h1.eager-wrapping{font-size:4em}article h3{font-size:2em;font-weight:700;margin:.33333em 0 .66667em;text-transform:uppercase;letter-spacing:.25em;border-bottom:1px solid}article h4{margin:1em 0 0}article .build-group-listing{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;margin:0 -1.5em}article .build-group-listing .build-group{width:250px;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;margin:0 1.5em 3em;box-sizing:border-box;border:3px solid #0a81cc;padding:.5em 1.5em}article .build-group-listing .build-group h3{background-color:#0a81cc;color:#fff;margin:-.25em -.75em .75em;font-size:2em;line-height:1.25em;font-weight:400;padding:0;letter-spacing:0;border-bottom:0;text-transform:lowercase;text-align:center}article .build-group-listing .build-group h3 a{color:#fff;text-decoration:none!important;display:block;padding:.25em 0}article .build-group-listing .build-group p{font-size:1.1em;font-weight:300;margin:0 0 .66667em}article .build-group-listing .build-group-empty{width:270px;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;margin:0 .75em;box-sizing:border-box}.latest-flex{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;margin:2em -2em 0 0}.latest-flex .latest-flex-item{width:240px;box-sizing:border-box;background:#fff;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;margin:0 2em 2em 0}.latest-flex .latest-flex-item:active,.latest-flex .latest-flex-item:focus,.latest-flex .latest-flex-item:hover{text-decoration:none}.latest-flex .latest-flex-item h3.latest-flex-title{border-bottom:0;margin:0;padding:0;font-size:1.25em;font-weight:400;text-align:center;padding:.4em .5em;margin:-1px;color:#fff}.latest-flex .latest-flex-item.latest-flex-red{border:.25em solid #ff2626}.latest-flex .latest-flex-item.latest-flex-red h3.latest-flex-title{background:#ff2626}.latest-flex .latest-flex-item.latest-flex-yellow{border:.25em solid #ff9f19}.latest-flex .latest-flex-item.latest-flex-yellow h3.latest-flex-title{background:#ff9f19}.latest-flex .latest-flex-item.latest-flex-blue{border:.25em solid #0a81cc}.latest-flex .latest-flex-item.latest-flex-blue h3.latest-flex-title{background:#0a81cc}.latest-flex .latest-flex-item.latest-flex-green{border:.25em solid #12b23f}.latest-flex .latest-flex-item.latest-flex-green h3.latest-flex-title{background:#12b23f}.latest-flex .latest-flex-item .latest-flex-detail{color:#373736;text-align:center;font-weight:300}.latest-flex .latest-flex-item .latest-flex-detail .latest-flex-build{font-size:2.33333em;margin:.33333em 0 .16667em}.latest-flex .latest-flex-item .latest-flex-detail .latest-flex-lab{font-size:1.5em;margin:0 0 .5em}.latest-flex .latest-flex-item .latest-flex-detail .latest-flex-time{margin:0 0 .75em}.latest-full{display:block;background:#0a81cc;font-weight:400;text-align:center;color:#fff;margin:0 0 2em;padding:.66667em}.latest-full:active,.latest-full:focus,.latest-full:hover{text-decoration:none}.build-details-flex{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;font-size:1.1em;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.build-details-flex .build-details-flex-item{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;margin-bottom:1.5em;width:180px}.build-details-flex .build-details-flex-item label{font-weight:700;display:inline-block;vertical-align:top;margin-right:1em;min-width:100px}.build-details-flex .build-details-flex-item .build-details-flex-value{display:inline-block;vertical-align:top;margin-right:20px}.form-group{margin-bottom:1.5em;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-group>label{width:20%;max-width:240px;min-width:120px;text-align:left;font-weight:700;margin-right:1em;display:inline-block;vertical-align:top;margin-top:.25em;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.form-group>div{margin-left:calc(20% + 1em);width:40%;min-width:240px;max-width:560px;display:inline-block;vertical-align:top;-webkit-box-flex:2;-webkit-flex-grow:2;-ms-flex-positive:2;flex-grow:2}.form-group>div input,.form-group>div select,.form-group>div textarea{width:100%;box-sizing:border-box;border:1px solid;padding:.33333em .5em;border-radius:.16667em;line-height:1em;border-color:#888}.form-group>div .group-input-button{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.form-group>div .group-input-button input{border-top-right-radius:0;border-bottom-right-radius:0;border-right:0}.form-group>div .group-input-button button{border-top-left-radius:0;border-bottom-left-radius:0;width:120px;border:1px solid #888;border-left:0}.form-group>div input[type=checkbox],.form-group>div input[type=submit]{width:auto}.form-group>div .group-input-button>button,.form-group>div>button,.form-group>div>input[type=submit]{display:inline-block;vertical-align:middle;padding:.33333em 1em;background-color:#12b23f;color:#fff;text-decoration:none;border-radius:.16667em;border:0;line-height:1.6}.form-group>div.wide-group{width:40%}.form-group>div.wide-group>.trumbowyg-box{width:100%;margin:0}.form-group>label+div{margin-left:0}.credits-wrapper{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.credits-wrapper .credits-list{width:480px;max-width:100%;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.credits-list dt{font-weight:700}.credits-list dd{margin-left:0}.credits-list dd+dt{margin-top:1.5em}ul.pagination{text-align:center;margin:.5em 0 1em;padding:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}ul.pagination>li{list-style:none;width:1.8em;line-height:1.8em;margin:0 .33333em}ul.pagination>li>a,ul.pagination>li>span{display:block;border-radius:.33333em;text-decoration:none}ul.pagination>li>span{cursor:not-allowed}footer#page-footer{padding:1.33333em 0 .66667em;font-size:.85em}footer#page-footer .footer-flex{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}footer#page-footer .footer-flex .footer-flex-item{width:50%;min-width:200px;text-align:center;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}footer#page-footer .footer-flex .footer-flex-item:last-child{text-align:right}footer#page-footer .footer-flex .footer-flex-item:first-child{text-align:left}footer#page-footer p{margin:0 0 .4em}#modal-search-overlay{display:none;position:fixed;top:0;bottom:0;left:0;right:0;transition:background-color linear .6s,-webkit-backdrop-filter linear .6s,backdrop-filter linear .6s;background-color:transparent;-webkit-backdrop-filter:blur(0);backdrop-filter:blur(0);z-index:100}#modal-search-overlay.open{display:block;background-color:rgba(0,0,0,.75);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}#modal-search-overlay #modal-search{position:absolute;top:15%;left:25%;width:50%;max-height:70%;overflow:auto;padding:2em;border-radius:2px;box-sizing:border-box;border:1px solid #ccc}#modal-search-overlay #modal-search h3{margin:0 0 1em;font-size:1.5em;font-weight:300}#modal-search-overlay #modal-search>#modal-search-box{width:100%}#modal-search-overlay #modal-search>#modal-search-box>*{display:inline-block;height:2.5em;padding:.5em;box-sizing:border-box;border:1px solid}#modal-search-overlay #modal-search>#modal-search-box>#modal-search-input{width:calc(100% - 3.33333em)}#modal-search-overlay #modal-search>#modal-search-box>#modal-search-button{width:2.66667em}#modal-search-overlay #modal-search>#modal-search-result{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}#modal-search-overlay #modal-search>#modal-search-result>.search-result-item{display:block;padding:0 1em;width:140px;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}#modal-search-overlay #modal-search>#modal-search-result>.search-result-item>.search-result-heading{font-size:1.2em;margin-bottom:0}#menu-open-overlay{display:none;position:fixed;top:0;bottom:0;left:0;right:0;z-index:5}#menu-open-overlay.open{display:block}@media (max-width:980px){#modal-search-overlay #modal-search{left:10%;width:80%}}@media (max-width:640px){header#page-header h1{text-align:center}nav#page-navigation button{display:block}nav#page-navigation #page-navigation-links{display:none}nav#page-navigation #page-navigation-links.open{display:block}nav#page-navigation #page-navigation-links>li{display:block;text-align:left}article h1,article h1.eager-wrapping{font-size:3em;line-height:1.33333}article h3{text-align:center}article .addthis_sharing_toolbox{text-align:center}footer#page-footer .footer-flex .footer-flex-item:first-child{text-align:center;margin-bottom:1.5em}footer#page-footer .footer-flex .footer-flex-item:last-child{text-align:center}}@media (max-width:1300px){.latest-flex .latest-flex-item{min-width:calc(50% - 2em)}} /*# sourceMappingURL=default.css.map */ diff --git a/BuildFeed/res/css/default.css.map b/BuildFeed/res/css/default.css.map index 0b47a25..a009321 100644 --- a/BuildFeed/res/css/default.css.map +++ b/BuildFeed/res/css/default.css.map @@ -1 +1 @@ -{"version":3,"sources":["default.scss","default.css"],"names":[],"mappings":"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","file":"default.css","sourcesContent":["body\r\n{\r\n font-family: 'Roboto', sans-serif;\r\n font-size: 10pt;\r\n line-height: 1.6;\r\n margin: 0;\r\n box-sizing: border-box;\r\n}\r\n\r\na\r\n{\r\n text-decoration: none;\r\n\r\n &:hover,\r\n &:active,\r\n &:focus\r\n {\r\n text-decoration: underline;\r\n }\r\n}\r\n\r\nh1\r\n{\r\n font-size: 3em;\r\n font-weight: 500;\r\n}\r\n\r\np\r\n{\r\n margin: 0 0 1em;\r\n}\r\n\r\ntable\r\n{\r\n width: 100%;\r\n border-collapse: collapse;\r\n\r\n th,\r\n td\r\n {\r\n margin: 0;\r\n border: 0;\r\n padding: 4px 6px;\r\n }\r\n\r\n thead th\r\n {\r\n border-bottom: 1px solid;\r\n text-align: left;\r\n }\r\n}\r\n\r\n.at-share-btn-elements\r\n{\r\n margin-left: -8px;\r\n\r\n > .at_flat_counter\r\n {\r\n font-size: 14px !important;\r\n vertical-align: top !important;\r\n }\r\n\r\n > .at-share-btn\r\n {\r\n margin-left: 8px !important;\r\n }\r\n}\r\n\r\n\r\n.container\r\n{\r\n width: 1240px;\r\n max-width: 80%;\r\n margin: 0 auto;\r\n position: relative;\r\n}\r\n\r\n.no-wrapping\r\n{\r\n text-overflow: ellipsis;\r\n overflow: hidden;\r\n white-space: nowrap;\r\n}\r\n\r\n.eager-wrapping\r\n{\r\n word-wrap: break-word;\r\n}\r\n\r\n.field-validation-error\r\n{\r\n display: block;\r\n margin: #{(1em / 3)} 0;\r\n color: #FF2626;\r\n}\r\n\r\na.button,\r\ninput[type=submit].button\r\n{\r\n display: inline-block;\r\n vertical-align: middle;\r\n padding: #{(1em / 3)} 1em;\r\n background-color: #0A81CC;\r\n color: #fff;\r\n text-decoration: none;\r\n border-radius: #{(1em / 6)};\r\n text-align: center;\r\n\r\n &.edit-button\r\n {\r\n background-color: #FF9F19;\r\n }\r\n\r\n &.delete-button\r\n {\r\n background-color: #FF2626;\r\n }\r\n\r\n &.add-button\r\n {\r\n background-color: #12B23F;\r\n }\r\n}\r\n\r\nheader#page-header\r\n{\r\n h1\r\n {\r\n font-weight: 300;\r\n margin: .33em 0;\r\n\r\n a\r\n {\r\n text-decoration: none;\r\n }\r\n }\r\n}\r\n\r\nnav#page-navigation\r\n{\r\n border-bottom-width: 1px;\r\n border-bottom-style: solid;\r\n position: relative;\r\n z-index: 10;\r\n\r\n #page-navigation-links\r\n {\r\n margin: 0 -15px;\r\n padding: 0;\r\n text-align: right;\r\n\r\n > li\r\n {\r\n display: inline-block;\r\n vertical-align: top;\r\n\r\n > a\r\n {\r\n display: block;\r\n padding: 15px;\r\n\r\n &:hover,\r\n &:active,\r\n &:focus\r\n {\r\n text-decoration: none;\r\n }\r\n }\r\n }\r\n }\r\n\r\n button\r\n {\r\n display: none;\r\n width: 100%;\r\n border: 0;\r\n font-size: 1.2em;\r\n font-weight: 300;\r\n padding: #{(2em / 3)} 0;\r\n background: none;\r\n }\r\n}\r\n\r\n.dropdown-parent\r\n{\r\n .dropdown-menu\r\n {\r\n box-sizing: border-box;\r\n display: none;\r\n position: absolute;\r\n left: -15px;\r\n right: -15px;\r\n max-width: 1270px;\r\n padding: 15px 15px 0;\r\n text-align: left;\r\n z-index: 10;\r\n border-width: 1px;\r\n border-style: solid;\r\n border-top-width: 0;\r\n flex-wrap: wrap;\r\n\r\n h4\r\n {\r\n margin: 0 0 .5em;\r\n font-size: 1.2em;\r\n font-weight: 500;\r\n }\r\n\r\n .dropdown-menu-block\r\n {\r\n display: inline-block;\r\n vertical-align: top;\r\n margin-bottom: 15px;\r\n flex-grow: 1;\r\n\r\n ul\r\n {\r\n padding: 0;\r\n display: flex;\r\n flex-wrap: wrap;\r\n }\r\n\r\n li\r\n {\r\n list-style-type: none;\r\n line-height: 2em;\r\n width: 133px;\r\n vertical-align: top;\r\n flex-grow: 1;\r\n\r\n > a\r\n {\r\n display: block;\r\n padding: 0 1em;\r\n\r\n &:hover,\r\n &:active,\r\n &:focus\r\n {\r\n text-decoration: none;\r\n }\r\n }\r\n }\r\n }\r\n\r\n #settings-theme-menu\r\n {\r\n width: 120px;\r\n flex-grow: 1;\r\n }\r\n\r\n #settings-lang-menu\r\n {\r\n width: 320px;\r\n flex-grow: 1000;\r\n }\r\n }\r\n\r\n &.open\r\n {\r\n .dropdown-menu\r\n {\r\n display: flex;\r\n }\r\n }\r\n}\r\n\r\narticle\r\n{\r\n padding: 2em 0;\r\n\r\n h1\r\n {\r\n font-size: 4em;\r\n font-weight: 300;\r\n text-align: center;\r\n margin: 0 0 #{(1em / 2)};\r\n\r\n &.eager-wrapping\r\n {\r\n font-size: 4em;\r\n }\r\n }\r\n\r\n h3\r\n {\r\n font-size: 2em;\r\n font-weight: bold;\r\n margin: #{(1em / 3)} 0 #{(2em / 3)};\r\n text-transform: uppercase;\r\n letter-spacing: #{(1em / 4)};\r\n border-bottom: 1px solid;\r\n }\r\n\r\n h4\r\n {\r\n margin: 1em 0 0;\r\n }\r\n\r\n .build-group-listing\r\n {\r\n display: flex;\r\n flex-wrap: wrap;\r\n justify-content: center;\r\n margin: 0 #{(-3em / 2)};\r\n\r\n .build-group\r\n {\r\n width: 250px;\r\n flex-grow: 1;\r\n margin: 0 #{(3em / 2)} 3em;\r\n box-sizing: border-box;\r\n border: 3px solid #0A81CC;\r\n padding: 0.5em 1.5em;\r\n\r\n h3\r\n {\r\n background-color: #0A81CC;\r\n color: #fff;\r\n margin: -0.25em -0.75em 0.75em;\r\n font-size: 2em;\r\n line-height: 1.25em;\r\n font-weight: normal;\r\n padding: 0;\r\n letter-spacing: 0;\r\n border-bottom: 0;\r\n text-transform: lowercase;\r\n text-align: center;\r\n\r\n a\r\n {\r\n color: #fff;\r\n text-decoration: none !important;\r\n display: block;\r\n padding: 0.25em 0;\r\n }\r\n }\r\n\r\n p\r\n {\r\n font-size: 1.1em;\r\n font-weight: 300;\r\n margin: 0 0 #{(2em / 3)};\r\n }\r\n }\r\n\r\n .build-group-empty\r\n {\r\n width: 270px;\r\n flex-grow: 1;\r\n margin: 0 0.75em;\r\n box-sizing: border-box;\r\n }\r\n }\r\n}\r\n\r\n.latest-flex\r\n{\r\n display: flex;\r\n flex-wrap: wrap;\r\n justify-content: space-between;\r\n margin: 2em -2em 0 0;\r\n\r\n .latest-flex-item\r\n {\r\n width: 240px;\r\n box-sizing: border-box;\r\n background: #fff;\r\n flex-grow: 1;\r\n margin: 0 2em 2em 0;\r\n\r\n &:hover,\r\n &:active,\r\n &:focus\r\n {\r\n text-decoration: none;\r\n }\r\n\r\n h3.latest-flex-title\r\n {\r\n border-bottom: 0;\r\n margin: 0;\r\n padding: 0;\r\n font-size: 1.25em;\r\n font-weight: normal;\r\n text-align: center;\r\n padding: 0.4em 0.5em;\r\n margin: -1px;\r\n color: #fff;\r\n }\r\n\r\n &.latest-flex-red\r\n {\r\n border: #{(1em / 4)} solid #FF2626;\r\n\r\n h3.latest-flex-title\r\n {\r\n background: #FF2626;\r\n }\r\n }\r\n\r\n &.latest-flex-yellow\r\n {\r\n border: #{(1em / 4)} solid #FF9F19;\r\n\r\n h3.latest-flex-title\r\n {\r\n background: #FF9F19;\r\n }\r\n }\r\n\r\n &.latest-flex-blue\r\n {\r\n border: #{(1em / 4)} solid #0A81CC;\r\n\r\n h3.latest-flex-title\r\n {\r\n background: #0A81CC;\r\n }\r\n }\r\n\r\n &.latest-flex-green\r\n {\r\n border: #{(1em / 4)} solid #12B23F;\r\n\r\n h3.latest-flex-title\r\n {\r\n background: #12B23F;\r\n }\r\n }\r\n\r\n .latest-flex-detail\r\n {\r\n color: #373736;\r\n text-align: center;\r\n font-weight: 300;\r\n\r\n .latest-flex-build\r\n {\r\n font-size: #{(7em / 3)};\r\n margin: #{(1em / 3)} 0 #{(1em / 6)};\r\n }\r\n\r\n .latest-flex-lab\r\n {\r\n font-size: 1.5em;\r\n margin: 0 0 0.5em;\r\n }\r\n\r\n .latest-flex-time\r\n {\r\n margin: 0 0 0.75em;\r\n }\r\n }\r\n }\r\n}\r\n\r\n.latest-full\r\n{\r\n display: block;\r\n background: #0A81CC;\r\n font-weight: normal;\r\n text-align: center;\r\n color: #fff;\r\n margin: 0 0 2em;\r\n padding: #{(2em / 3)};\r\n\r\n &:hover,\r\n &:active,\r\n &:focus\r\n {\r\n text-decoration: none;\r\n }\r\n}\r\n\r\n.build-details-flex\r\n{\r\n display: flex;\r\n font-size: 1.1em;\r\n flex-wrap: wrap;\r\n\r\n .build-details-flex-item\r\n {\r\n flex-grow: 1;\r\n margin-bottom: 1.5em;\r\n width: 180px;\r\n\r\n label\r\n {\r\n font-weight: bold;\r\n display: inline-block;\r\n vertical-align: top;\r\n margin-right: 1em;\r\n min-width: 100px;\r\n }\r\n\r\n .build-details-flex-value\r\n {\r\n display: inline-block;\r\n vertical-align: top;\r\n margin-right: 20px;\r\n }\r\n }\r\n}\r\n\r\n.form-group\r\n{\r\n margin-bottom: 1.5em;\r\n display: flex;\r\n flex-wrap: wrap;\r\n\r\n > label\r\n {\r\n width: 20%;\r\n max-width: 240px;\r\n min-width: 120px;\r\n text-align: left;\r\n font-weight: bold;\r\n margin-right: 1em;\r\n display: inline-block;\r\n vertical-align: top;\r\n margin-top: #{(1em / 4)};\r\n flex-grow: 1;\r\n }\r\n\r\n > div\r\n {\r\n margin-left: calc(20% + 1em);\r\n width: 40%;\r\n min-width: 240px;\r\n max-width: 560px;\r\n display: inline-block;\r\n vertical-align: top;\r\n flex-grow: 2;\r\n\r\n input, textarea, select\r\n {\r\n width: 100%;\r\n box-sizing: border-box;\r\n border: 1px solid;\r\n padding: #{(1em / 3)} #{(1em / 2)};\r\n border-radius: #{(1em / 6)};\r\n line-height: 1em;\r\n border-color: #888;\r\n }\r\n\r\n .group-input-button\r\n {\r\n display: flex;\r\n\r\n input\r\n {\r\n border-top-right-radius: 0;\r\n border-bottom-right-radius: 0;\r\n border-right: 0;\r\n }\r\n\r\n button\r\n {\r\n border-top-left-radius: 0;\r\n border-bottom-left-radius: 0;\r\n width: 120px;\r\n border: 1px solid #888;\r\n border-left: 0;\r\n }\r\n }\r\n\r\n input[type=submit],\r\n input[type=checkbox]\r\n {\r\n width: auto;\r\n }\r\n\r\n > input[type=submit],\r\n > button,\r\n .group-input-button > button\r\n {\r\n display: inline-block;\r\n vertical-align: middle;\r\n padding: #{(1em / 3)} 1em;\r\n background-color: #12B23F;\r\n color: #fff;\r\n text-decoration: none;\r\n border-radius: #{(1em / 6)};\r\n border: 0;\r\n line-height: 1.6;\r\n }\r\n\r\n &.wide-group\r\n {\r\n width: 40%;\r\n\r\n > .trumbowyg-box\r\n {\r\n width: 100%;\r\n margin: 0;\r\n }\r\n }\r\n }\r\n\r\n > label + div\r\n {\r\n margin-left: 0;\r\n }\r\n}\r\n\r\n.credits-wrapper\r\n{\r\n display: flex;\r\n flex-wrap: wrap;\r\n\r\n .credits-list\r\n {\r\n width: 480px;\r\n max-width: 100%;\r\n flex-grow: 1;\r\n }\r\n}\r\n\r\n.credits-list\r\n{\r\n dt\r\n {\r\n font-weight: bold;\r\n }\r\n\r\n dd\r\n {\r\n margin-left: 0;\r\n }\r\n\r\n dd + dt\r\n {\r\n margin-top: 1.5em;\r\n }\r\n}\r\n\r\nul.pagination\r\n{\r\n text-align: center;\r\n margin: 0.5em 0 1em;\r\n padding: 0;\r\n display: flex;\r\n justify-content: center;\r\n\r\n > li\r\n {\r\n list-style: none;\r\n width: 1.8em;\r\n line-height: 1.8em;\r\n margin: 0 #{(1em / 3)};\r\n\r\n > a,\r\n > span\r\n {\r\n display: block;\r\n border-radius: #{(1em / 3)};\r\n text-decoration: none;\r\n }\r\n\r\n > span\r\n {\r\n cursor: not-allowed;\r\n }\r\n }\r\n}\r\n\r\nfooter#page-footer\r\n{\r\n padding: #{(4em / 3)} 0 #{(4em / 6)};\r\n font-size: 0.85em;\r\n\r\n .footer-flex\r\n {\r\n display: flex;\r\n flex-wrap: wrap;\r\n\r\n .footer-flex-item\r\n {\r\n width: 50%;\r\n min-width: 200px;\r\n text-align: center;\r\n flex-grow: 1;\r\n\r\n &:last-child\r\n {\r\n text-align: right;\r\n }\r\n\r\n &:first-child\r\n {\r\n text-align: left;\r\n }\r\n }\r\n }\r\n\r\n p\r\n {\r\n margin: 0 0 #{(2em / 5)};\r\n }\r\n}\r\n\r\n#modal-search-overlay\r\n{\r\n display: none;\r\n position: fixed;\r\n top: 0;\r\n bottom: 0;\r\n left: 0;\r\n right: 0;\r\n transition: background-color linear 0.6s, -webkit-backdrop-filter linear 0.6s, backdrop-filter linear 0.6s;\r\n background-color: rgba(0,0,0, 0);\r\n -webkit-backdrop-filter: blur(0);\r\n backdrop-filter: blur(0);\r\n z-index: 100;\r\n\r\n &.open\r\n {\r\n display: block;\r\n background-color: rgba(0,0,0, 0.75);\r\n -webkit-backdrop-filter: blur(10px);\r\n backdrop-filter: blur(10px);\r\n }\r\n\r\n #modal-search\r\n {\r\n position: absolute;\r\n top: 15%;\r\n left: 25%;\r\n width: 50%;\r\n max-height: 70%;\r\n overflow: auto;\r\n padding: 2em;\r\n border-radius: 2px;\r\n box-sizing: border-box;\r\n border: 1px solid #ccc;\r\n\r\n h3\r\n {\r\n margin: 0 0 1em;\r\n font-size: 1.5em;\r\n font-weight: 300;\r\n }\r\n\r\n > #modal-search-box\r\n {\r\n width: 100%;\r\n\r\n > *\r\n {\r\n display: inline-block;\r\n height: 2.5em;\r\n padding: 0.5em;\r\n box-sizing: border-box;\r\n border: 1px solid;\r\n }\r\n\r\n > #modal-search-input\r\n {\r\n width: calc(100% - #{(10em / 3)});\r\n }\r\n\r\n > #modal-search-button\r\n {\r\n width: #{(8em / 3)};\r\n }\r\n }\r\n\r\n > #modal-search-result\r\n {\r\n display: flex;\r\n flex-wrap: wrap;\r\n justify-content: space-between;\r\n\r\n > .search-result-item\r\n {\r\n display: block;\r\n padding: 0 1em;\r\n width: 140px;\r\n flex-grow: 1;\r\n\r\n > .search-result-heading\r\n {\r\n font-size: 1.2em;\r\n margin-bottom: 0;\r\n }\r\n }\r\n }\r\n }\r\n}\r\n\r\n#menu-open-overlay\r\n{\r\n display: none;\r\n position: fixed;\r\n top: 0;\r\n bottom: 0;\r\n left: 0;\r\n right: 0;\r\n z-index: 5;\r\n\r\n &.open\r\n {\r\n display: block;\r\n }\r\n}\r\n\r\n@media (max-width: 980px)\r\n{\r\n #modal-search-overlay #modal-search\r\n {\r\n left: 10%;\r\n width: 80%;\r\n }\r\n}\r\n\r\n@media (max-width: 640px)\r\n{\r\n header#page-header h1\r\n {\r\n text-align: center;\r\n }\r\n\r\n nav#page-navigation\r\n {\r\n button\r\n {\r\n display: block;\r\n }\r\n\r\n #page-navigation-links\r\n {\r\n display: none;\r\n\r\n &.open\r\n {\r\n display: block;\r\n }\r\n\r\n > li\r\n {\r\n display: block;\r\n text-align: left;\r\n }\r\n }\r\n }\r\n\r\n article\r\n {\r\n h1,\r\n h1.eager-wrapping\r\n {\r\n font-size: 3em;\r\n line-height: #{(4 / 3)};\r\n }\r\n\r\n h3\r\n {\r\n text-align: center;\r\n }\r\n\r\n .addthis_sharing_toolbox\r\n {\r\n text-align: center;\r\n }\r\n }\r\n\r\n footer#page-footer .footer-flex .footer-flex-item\r\n {\r\n &:first-child\r\n {\r\n text-align: center;\r\n margin-bottom: #{(3em / 2)};\r\n }\r\n\r\n &:last-child\r\n {\r\n text-align: center;\r\n }\r\n }\r\n}\r\n\r\n@media (max-width: 1300px)\r\n{\r\n .latest-flex .latest-flex-item\r\n {\r\n min-width: calc(50% - 2em);\r\n }\r\n}","body{font-family:Roboto,sans-serif;font-size:10pt;line-height:1.6;margin:0;box-sizing:border-box}a{text-decoration:none}a:active,a:focus,a:hover{text-decoration:underline}h1{font-size:3em;font-weight:500}p{margin:0 0 1em}table{width:100%;border-collapse:collapse}table td,table th{margin:0;border:0;padding:4px 6px}table thead th{border-bottom:1px solid;text-align:left}.at-share-btn-elements{margin-left:-8px}.at-share-btn-elements>.at_flat_counter{font-size:14px!important;vertical-align:top!important}.at-share-btn-elements>.at-share-btn{margin-left:8px!important}.container{width:1240px;max-width:80%;margin:0 auto;position:relative}.no-wrapping{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.eager-wrapping{word-wrap:break-word}.field-validation-error{display:block;margin:.33333em 0;color:#ff2626}a.button,input[type=submit].button{display:inline-block;vertical-align:middle;padding:.33333em 1em;background-color:#0a81cc;color:#fff;text-decoration:none;border-radius:.16667em;text-align:center}a.button.edit-button,input[type=submit].button.edit-button{background-color:#ff9f19}a.button.delete-button,input[type=submit].button.delete-button{background-color:#ff2626}a.button.add-button,input[type=submit].button.add-button{background-color:#12b23f}header#page-header h1{font-weight:300;margin:.33em 0}header#page-header h1 a{text-decoration:none}nav#page-navigation{border-bottom-width:1px;border-bottom-style:solid;position:relative;z-index:10}nav#page-navigation #page-navigation-links{margin:0 -15px;padding:0;text-align:right}nav#page-navigation #page-navigation-links>li{display:inline-block;vertical-align:top}nav#page-navigation #page-navigation-links>li>a{display:block;padding:15px}nav#page-navigation #page-navigation-links>li>a:active,nav#page-navigation #page-navigation-links>li>a:focus,nav#page-navigation #page-navigation-links>li>a:hover{text-decoration:none}nav#page-navigation button{display:none;width:100%;border:0;font-size:1.2em;font-weight:300;padding:.66667em 0;background:0 0}.dropdown-parent .dropdown-menu{box-sizing:border-box;display:none;position:absolute;left:-15px;right:-15px;max-width:1270px;padding:15px 15px 0;text-align:left;z-index:10;border-width:1px;border-style:solid;border-top-width:0;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.dropdown-parent .dropdown-menu h4{margin:0 0 .5em;font-size:1.2em;font-weight:500}.dropdown-parent .dropdown-menu .dropdown-menu-block{display:inline-block;vertical-align:top;margin-bottom:15px;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.dropdown-parent .dropdown-menu .dropdown-menu-block ul{padding:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.dropdown-parent .dropdown-menu .dropdown-menu-block li{list-style-type:none;line-height:2em;width:133px;vertical-align:top;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.dropdown-parent .dropdown-menu .dropdown-menu-block li>a{display:block;padding:0 1em}.dropdown-parent .dropdown-menu .dropdown-menu-block li>a:active,.dropdown-parent .dropdown-menu .dropdown-menu-block li>a:focus,.dropdown-parent .dropdown-menu .dropdown-menu-block li>a:hover{text-decoration:none}.dropdown-parent .dropdown-menu #settings-theme-menu{width:120px;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.dropdown-parent .dropdown-menu #settings-lang-menu{width:320px;-webkit-box-flex:1000;-webkit-flex-grow:1000;-ms-flex-positive:1000;flex-grow:1000}.dropdown-parent.open .dropdown-menu{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}article{padding:2em 0}article h1{font-size:4em;font-weight:300;text-align:center;margin:0 0 .5em}article h1.eager-wrapping{font-size:4em}article h3{font-size:2em;font-weight:700;margin:.33333em 0 .66667em;text-transform:uppercase;letter-spacing:.25em;border-bottom:1px solid}article h4{margin:1em 0 0}article .build-group-listing{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;margin:0 -1.5em}article .build-group-listing .build-group{width:250px;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;margin:0 1.5em 3em;box-sizing:border-box;border:3px solid #0a81cc;padding:.5em 1.5em}article .build-group-listing .build-group h3{background-color:#0a81cc;color:#fff;margin:-.25em -.75em .75em;font-size:2em;line-height:1.25em;font-weight:400;padding:0;letter-spacing:0;border-bottom:0;text-transform:lowercase;text-align:center}article .build-group-listing .build-group h3 a{color:#fff;text-decoration:none!important;display:block;padding:.25em 0}article .build-group-listing .build-group p{font-size:1.1em;font-weight:300;margin:0 0 .66667em}article .build-group-listing .build-group-empty{width:270px;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;margin:0 .75em;box-sizing:border-box}.latest-flex{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;margin:2em -2em 0 0}.latest-flex .latest-flex-item{width:240px;box-sizing:border-box;background:#fff;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;margin:0 2em 2em 0}.latest-flex .latest-flex-item:active,.latest-flex .latest-flex-item:focus,.latest-flex .latest-flex-item:hover{text-decoration:none}.latest-flex .latest-flex-item h3.latest-flex-title{border-bottom:0;margin:0;padding:0;font-size:1.25em;font-weight:400;text-align:center;padding:.4em .5em;margin:-1px;color:#fff}.latest-flex .latest-flex-item.latest-flex-red{border:.25em solid #ff2626}.latest-flex .latest-flex-item.latest-flex-red h3.latest-flex-title{background:#ff2626}.latest-flex .latest-flex-item.latest-flex-yellow{border:.25em solid #ff9f19}.latest-flex .latest-flex-item.latest-flex-yellow h3.latest-flex-title{background:#ff9f19}.latest-flex .latest-flex-item.latest-flex-blue{border:.25em solid #0a81cc}.latest-flex .latest-flex-item.latest-flex-blue h3.latest-flex-title{background:#0a81cc}.latest-flex .latest-flex-item.latest-flex-green{border:.25em solid #12b23f}.latest-flex .latest-flex-item.latest-flex-green h3.latest-flex-title{background:#12b23f}.latest-flex .latest-flex-item .latest-flex-detail{color:#373736;text-align:center;font-weight:300}.latest-flex .latest-flex-item .latest-flex-detail .latest-flex-build{font-size:2.33333em;margin:.33333em 0 .16667em}.latest-flex .latest-flex-item .latest-flex-detail .latest-flex-lab{font-size:1.5em;margin:0 0 .5em}.latest-flex .latest-flex-item .latest-flex-detail .latest-flex-time{margin:0 0 .75em}.latest-full{display:block;background:#0a81cc;font-weight:400;text-align:center;color:#fff;margin:0 0 2em;padding:.66667em}.latest-full:active,.latest-full:focus,.latest-full:hover{text-decoration:none}.build-details-flex{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;font-size:1.1em;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.build-details-flex .build-details-flex-item{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;margin-bottom:1.5em;width:180px}.build-details-flex .build-details-flex-item label{font-weight:700;display:inline-block;vertical-align:top;margin-right:1em;min-width:100px}.build-details-flex .build-details-flex-item .build-details-flex-value{display:inline-block;vertical-align:top;margin-right:20px}.form-group{margin-bottom:1.5em;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-group>label{width:20%;max-width:240px;min-width:120px;text-align:left;font-weight:700;margin-right:1em;display:inline-block;vertical-align:top;margin-top:.25em;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.form-group>div{margin-left:calc(20% + 1em);width:40%;min-width:240px;max-width:560px;display:inline-block;vertical-align:top;-webkit-box-flex:2;-webkit-flex-grow:2;-ms-flex-positive:2;flex-grow:2}.form-group>div input,.form-group>div select,.form-group>div textarea{width:100%;box-sizing:border-box;border:1px solid;padding:.33333em .5em;border-radius:.16667em;line-height:1em;border-color:#888}.form-group>div .group-input-button{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.form-group>div .group-input-button input{border-top-right-radius:0;border-bottom-right-radius:0;border-right:0}.form-group>div .group-input-button button{border-top-left-radius:0;border-bottom-left-radius:0;width:120px;border:1px solid #888;border-left:0}.form-group>div input[type=checkbox],.form-group>div input[type=submit]{width:auto}.form-group>div .group-input-button>button,.form-group>div>button,.form-group>div>input[type=submit]{display:inline-block;vertical-align:middle;padding:.33333em 1em;background-color:#12b23f;color:#fff;text-decoration:none;border-radius:.16667em;border:0;line-height:1.6}.form-group>div.wide-group{width:40%}.form-group>div.wide-group>.trumbowyg-box{width:100%;margin:0}.form-group>label+div{margin-left:0}.credits-wrapper{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.credits-wrapper .credits-list{width:480px;max-width:100%;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.credits-list dt{font-weight:700}.credits-list dd{margin-left:0}.credits-list dd+dt{margin-top:1.5em}ul.pagination{text-align:center;margin:.5em 0 1em;padding:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}ul.pagination>li{list-style:none;width:1.8em;line-height:1.8em;margin:0 .33333em}ul.pagination>li>a,ul.pagination>li>span{display:block;border-radius:.33333em;text-decoration:none}ul.pagination>li>span{cursor:not-allowed}footer#page-footer{padding:1.33333em 0 .66667em;font-size:.85em}footer#page-footer .footer-flex{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}footer#page-footer .footer-flex .footer-flex-item{width:50%;min-width:200px;text-align:center;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}footer#page-footer .footer-flex .footer-flex-item:last-child{text-align:right}footer#page-footer .footer-flex .footer-flex-item:first-child{text-align:left}footer#page-footer p{margin:0 0 .4em}#modal-search-overlay{display:none;position:fixed;top:0;bottom:0;left:0;right:0;transition:background-color linear .6s,-webkit-backdrop-filter linear .6s,backdrop-filter linear .6s;background-color:transparent;-webkit-backdrop-filter:blur(0);backdrop-filter:blur(0);z-index:100}#modal-search-overlay.open{display:block;background-color:rgba(0,0,0,.75);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}#modal-search-overlay #modal-search{position:absolute;top:15%;left:25%;width:50%;max-height:70%;overflow:auto;padding:2em;border-radius:2px;box-sizing:border-box;border:1px solid #ccc}#modal-search-overlay #modal-search h3{margin:0 0 1em;font-size:1.5em;font-weight:300}#modal-search-overlay #modal-search>#modal-search-box{width:100%}#modal-search-overlay #modal-search>#modal-search-box>*{display:inline-block;height:2.5em;padding:.5em;box-sizing:border-box;border:1px solid}#modal-search-overlay #modal-search>#modal-search-box>#modal-search-input{width:calc(100% - 3.33333em)}#modal-search-overlay #modal-search>#modal-search-box>#modal-search-button{width:2.66667em}#modal-search-overlay #modal-search>#modal-search-result{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}#modal-search-overlay #modal-search>#modal-search-result>.search-result-item{display:block;padding:0 1em;width:140px;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}#modal-search-overlay #modal-search>#modal-search-result>.search-result-item>.search-result-heading{font-size:1.2em;margin-bottom:0}#menu-open-overlay{display:none;position:fixed;top:0;bottom:0;left:0;right:0;z-index:5}#menu-open-overlay.open{display:block}@media (max-width:980px){#modal-search-overlay #modal-search{left:10%;width:80%}}@media (max-width:640px){header#page-header h1{text-align:center}nav#page-navigation button{display:block}nav#page-navigation #page-navigation-links{display:none}nav#page-navigation #page-navigation-links.open{display:block}nav#page-navigation #page-navigation-links>li{display:block;text-align:left}article h1,article h1.eager-wrapping{font-size:3em;line-height:1.33333}article h3{text-align:center}article .addthis_sharing_toolbox{text-align:center}footer#page-footer .footer-flex .footer-flex-item:first-child{text-align:center;margin-bottom:1.5em}footer#page-footer .footer-flex .footer-flex-item:last-child{text-align:center}}@media (max-width:1300px){.latest-flex .latest-flex-item{min-width:calc(50% - 2em)}}\n/*# sourceMappingURL=default.css.map */\n"]} \ No newline at end of file +{"version":3,"sources":["default.scss","default.css"],"names":[],"mappings":"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","file":"default.css","sourcesContent":["body\r\n{\r\n font-family: 'Roboto', sans-serif;\r\n font-size: 10pt;\r\n line-height: 1.6;\r\n margin: 0;\r\n box-sizing: border-box;\r\n}\r\n\r\na\r\n{\r\n text-decoration: none;\r\n\r\n &:hover,\r\n &:active,\r\n &:focus\r\n {\r\n text-decoration: underline;\r\n }\r\n}\r\n\r\nh1\r\n{\r\n font-size: 3em;\r\n font-weight: 500;\r\n}\r\n\r\np\r\n{\r\n margin: 0 0 1em;\r\n}\r\n\r\ntable\r\n{\r\n width: 100%;\r\n border-collapse: collapse;\r\n\r\n th,\r\n td\r\n {\r\n margin: 0;\r\n border: 0;\r\n padding: 4px 6px;\r\n }\r\n\r\n thead th\r\n {\r\n border-bottom: 1px solid;\r\n text-align: left;\r\n }\r\n}\r\n\r\n.at-share-btn-elements\r\n{\r\n margin-left: -8px;\r\n\r\n > .at_flat_counter\r\n {\r\n font-size: 14px !important;\r\n vertical-align: top !important;\r\n }\r\n\r\n > .at-share-btn\r\n {\r\n margin-left: 8px !important;\r\n }\r\n}\r\n\r\n\r\n.container\r\n{\r\n width: 1240px;\r\n max-width: 80%;\r\n margin: 0 auto;\r\n position: relative;\r\n}\r\n\r\n.no-wrapping\r\n{\r\n text-overflow: ellipsis;\r\n overflow: hidden;\r\n white-space: nowrap;\r\n}\r\n\r\n.eager-wrapping\r\n{\r\n word-wrap: break-word;\r\n}\r\n\r\n.field-validation-error,\r\n.text-danger\r\n{\r\n display: block;\r\n margin: #{(1em / 3)} 0;\r\n color: #FF2626;\r\n}\r\n\r\na.button,\r\ninput[type=submit].button\r\n{\r\n display: inline-block;\r\n vertical-align: middle;\r\n padding: #{(1em / 3)} 1em;\r\n background-color: #0A81CC;\r\n color: #fff;\r\n text-decoration: none;\r\n border-radius: #{(1em / 6)};\r\n text-align: center;\r\n\r\n &.edit-button\r\n {\r\n background-color: #FF9F19;\r\n }\r\n\r\n &.delete-button\r\n {\r\n background-color: #FF2626;\r\n }\r\n\r\n &.add-button\r\n {\r\n background-color: #12B23F;\r\n }\r\n}\r\n\r\nheader#page-header\r\n{\r\n h1\r\n {\r\n font-weight: 300;\r\n margin: .33em 0;\r\n\r\n a\r\n {\r\n text-decoration: none;\r\n }\r\n }\r\n}\r\n\r\nnav#page-navigation\r\n{\r\n border-bottom-width: 1px;\r\n border-bottom-style: solid;\r\n position: relative;\r\n z-index: 10;\r\n\r\n #page-navigation-links\r\n {\r\n margin: 0 -15px #{(1px / 3)}; /* bottom margin fixes Chrome in 4k */\r\n padding: 0;\r\n text-align: right;\r\n\r\n > li\r\n {\r\n display: inline-block;\r\n vertical-align: top;\r\n\r\n > a\r\n {\r\n display: block;\r\n padding: 15px;\r\n\r\n &:hover,\r\n &:active,\r\n &:focus\r\n {\r\n text-decoration: none;\r\n }\r\n }\r\n }\r\n }\r\n\r\n button\r\n {\r\n display: none;\r\n width: 100%;\r\n border: 0;\r\n font-size: 1.2em;\r\n font-weight: 300;\r\n padding: #{(2em / 3)} 0;\r\n background: none;\r\n }\r\n}\r\n\r\n.dropdown-parent\r\n{\r\n .dropdown-menu\r\n {\r\n box-sizing: border-box;\r\n display: none;\r\n position: absolute;\r\n left: -15px;\r\n right: -15px;\r\n max-width: 1270px;\r\n padding: 15px 15px 0;\r\n text-align: left;\r\n z-index: 10;\r\n border-width: 1px;\r\n border-style: solid;\r\n border-top-width: 0;\r\n flex-wrap: wrap;\r\n\r\n h4\r\n {\r\n margin: 0 0 .5em;\r\n font-size: 1.2em;\r\n font-weight: 500;\r\n }\r\n\r\n .dropdown-menu-block\r\n {\r\n display: inline-block;\r\n vertical-align: top;\r\n margin-bottom: 15px;\r\n flex-grow: 1;\r\n\r\n ul\r\n {\r\n padding: 0;\r\n display: flex;\r\n flex-wrap: wrap;\r\n }\r\n\r\n li\r\n {\r\n list-style-type: none;\r\n line-height: 2em;\r\n width: 133px;\r\n vertical-align: top;\r\n flex-grow: 1;\r\n\r\n > a\r\n {\r\n display: block;\r\n padding: 0 1em;\r\n\r\n &:hover,\r\n &:active,\r\n &:focus\r\n {\r\n text-decoration: none;\r\n }\r\n }\r\n }\r\n }\r\n\r\n #settings-theme-menu\r\n {\r\n width: 120px;\r\n flex-grow: 1;\r\n }\r\n\r\n #settings-lang-menu\r\n {\r\n width: 320px;\r\n flex-grow: 1000;\r\n }\r\n }\r\n\r\n &.open\r\n {\r\n .dropdown-menu\r\n {\r\n display: flex;\r\n }\r\n }\r\n}\r\n\r\narticle\r\n{\r\n padding: 2em 0;\r\n\r\n h1\r\n {\r\n font-size: 4em;\r\n font-weight: 300;\r\n text-align: center;\r\n margin: 0 0 #{(1em / 2)};\r\n\r\n &.eager-wrapping\r\n {\r\n font-size: 4em;\r\n }\r\n }\r\n\r\n h3\r\n {\r\n font-size: 2em;\r\n font-weight: bold;\r\n margin: #{(1em / 3)} 0 #{(2em / 3)};\r\n text-transform: uppercase;\r\n letter-spacing: #{(1em / 4)};\r\n border-bottom: 1px solid;\r\n }\r\n\r\n h4\r\n {\r\n margin: 1em 0 0;\r\n }\r\n\r\n .build-group-listing\r\n {\r\n display: flex;\r\n flex-wrap: wrap;\r\n justify-content: center;\r\n margin: 0 #{(-3em / 2)};\r\n\r\n .build-group\r\n {\r\n width: 250px;\r\n flex-grow: 1;\r\n margin: 0 #{(3em / 2)} 3em;\r\n box-sizing: border-box;\r\n border: 3px solid #0A81CC;\r\n padding: 0.5em 1.5em;\r\n\r\n h3\r\n {\r\n background-color: #0A81CC;\r\n color: #fff;\r\n margin: -0.25em -0.75em 0.75em;\r\n font-size: 2em;\r\n line-height: 1.25em;\r\n font-weight: normal;\r\n padding: 0;\r\n letter-spacing: 0;\r\n border-bottom: 0;\r\n text-transform: lowercase;\r\n text-align: center;\r\n\r\n a\r\n {\r\n color: #fff;\r\n text-decoration: none !important;\r\n display: block;\r\n padding: 0.25em 0;\r\n }\r\n }\r\n\r\n p\r\n {\r\n font-size: 1.1em;\r\n font-weight: 300;\r\n margin: 0 0 #{(2em / 3)};\r\n }\r\n }\r\n\r\n .build-group-empty\r\n {\r\n width: 270px;\r\n flex-grow: 1;\r\n margin: 0 0.75em;\r\n box-sizing: border-box;\r\n }\r\n }\r\n}\r\n\r\n.latest-flex\r\n{\r\n display: flex;\r\n flex-wrap: wrap;\r\n justify-content: space-between;\r\n margin: 2em -2em 0 0;\r\n\r\n .latest-flex-item\r\n {\r\n width: 240px;\r\n box-sizing: border-box;\r\n background: #fff;\r\n flex-grow: 1;\r\n margin: 0 2em 2em 0;\r\n\r\n &:hover,\r\n &:active,\r\n &:focus\r\n {\r\n text-decoration: none;\r\n }\r\n\r\n h3.latest-flex-title\r\n {\r\n border-bottom: 0;\r\n margin: 0;\r\n padding: 0;\r\n font-size: 1.25em;\r\n font-weight: normal;\r\n text-align: center;\r\n padding: 0.4em 0.5em;\r\n margin: -1px;\r\n color: #fff;\r\n }\r\n\r\n &.latest-flex-red\r\n {\r\n border: #{(1em / 4)} solid #FF2626;\r\n\r\n h3.latest-flex-title\r\n {\r\n background: #FF2626;\r\n }\r\n }\r\n\r\n &.latest-flex-yellow\r\n {\r\n border: #{(1em / 4)} solid #FF9F19;\r\n\r\n h3.latest-flex-title\r\n {\r\n background: #FF9F19;\r\n }\r\n }\r\n\r\n &.latest-flex-blue\r\n {\r\n border: #{(1em / 4)} solid #0A81CC;\r\n\r\n h3.latest-flex-title\r\n {\r\n background: #0A81CC;\r\n }\r\n }\r\n\r\n &.latest-flex-green\r\n {\r\n border: #{(1em / 4)} solid #12B23F;\r\n\r\n h3.latest-flex-title\r\n {\r\n background: #12B23F;\r\n }\r\n }\r\n\r\n .latest-flex-detail\r\n {\r\n color: #373736;\r\n text-align: center;\r\n font-weight: 300;\r\n\r\n .latest-flex-build\r\n {\r\n font-size: #{(7em / 3)};\r\n margin: #{(1em / 3)} 0 #{(1em / 6)};\r\n }\r\n\r\n .latest-flex-lab\r\n {\r\n font-size: 1.5em;\r\n margin: 0 0 0.5em;\r\n }\r\n\r\n .latest-flex-time\r\n {\r\n margin: 0 0 0.75em;\r\n }\r\n }\r\n }\r\n}\r\n\r\n.latest-full\r\n{\r\n display: block;\r\n background: #0A81CC;\r\n font-weight: normal;\r\n text-align: center;\r\n color: #fff;\r\n margin: 0 0 2em;\r\n padding: #{(2em / 3)};\r\n\r\n &:hover,\r\n &:active,\r\n &:focus\r\n {\r\n text-decoration: none;\r\n }\r\n}\r\n\r\n.build-details-flex\r\n{\r\n display: flex;\r\n font-size: 1.1em;\r\n flex-wrap: wrap;\r\n\r\n .build-details-flex-item\r\n {\r\n flex-grow: 1;\r\n margin-bottom: 1.5em;\r\n width: 180px;\r\n\r\n label\r\n {\r\n font-weight: bold;\r\n display: inline-block;\r\n vertical-align: top;\r\n margin-right: 1em;\r\n min-width: 100px;\r\n }\r\n\r\n .build-details-flex-value\r\n {\r\n display: inline-block;\r\n vertical-align: top;\r\n margin-right: 20px;\r\n }\r\n }\r\n}\r\n\r\n.form-group\r\n{\r\n margin-bottom: 1.5em;\r\n display: flex;\r\n flex-wrap: wrap;\r\n\r\n > label\r\n {\r\n width: 20%;\r\n max-width: 240px;\r\n min-width: 120px;\r\n text-align: left;\r\n font-weight: bold;\r\n margin-right: 1em;\r\n display: inline-block;\r\n vertical-align: top;\r\n margin-top: #{(1em / 4)};\r\n flex-grow: 1;\r\n }\r\n\r\n > div\r\n {\r\n margin-left: calc(20% + 1em);\r\n width: 40%;\r\n min-width: 240px;\r\n max-width: 560px;\r\n display: inline-block;\r\n vertical-align: top;\r\n flex-grow: 2;\r\n\r\n input, textarea, select\r\n {\r\n width: 100%;\r\n box-sizing: border-box;\r\n border: 1px solid;\r\n padding: #{(1em / 3)} #{(1em / 2)};\r\n border-radius: #{(1em / 6)};\r\n line-height: 1em;\r\n border-color: #888;\r\n }\r\n\r\n .group-input-button\r\n {\r\n display: flex;\r\n\r\n input\r\n {\r\n border-top-right-radius: 0;\r\n border-bottom-right-radius: 0;\r\n border-right: 0;\r\n }\r\n\r\n button\r\n {\r\n border-top-left-radius: 0;\r\n border-bottom-left-radius: 0;\r\n width: 120px;\r\n border: 1px solid #888;\r\n border-left: 0;\r\n }\r\n }\r\n\r\n input[type=submit],\r\n input[type=checkbox]\r\n {\r\n width: auto;\r\n }\r\n\r\n > input[type=submit],\r\n > button,\r\n .group-input-button > button\r\n {\r\n display: inline-block;\r\n vertical-align: middle;\r\n padding: #{(1em / 3)} 1em;\r\n background-color: #12B23F;\r\n color: #fff;\r\n text-decoration: none;\r\n border-radius: #{(1em / 6)};\r\n border: 0;\r\n line-height: 1.6;\r\n }\r\n\r\n &.wide-group\r\n {\r\n width: 40%;\r\n\r\n > .trumbowyg-box\r\n {\r\n width: 100%;\r\n margin: 0;\r\n }\r\n }\r\n }\r\n\r\n > label + div\r\n {\r\n margin-left: 0;\r\n }\r\n}\r\n\r\n.credits-wrapper\r\n{\r\n display: flex;\r\n flex-wrap: wrap;\r\n\r\n .credits-list\r\n {\r\n width: 480px;\r\n max-width: 100%;\r\n flex-grow: 1;\r\n }\r\n}\r\n\r\n.credits-list\r\n{\r\n dt\r\n {\r\n font-weight: bold;\r\n }\r\n\r\n dd\r\n {\r\n margin-left: 0;\r\n }\r\n\r\n dd + dt\r\n {\r\n margin-top: 1.5em;\r\n }\r\n}\r\n\r\nul.pagination\r\n{\r\n text-align: center;\r\n margin: 0.5em 0 1em;\r\n padding: 0;\r\n display: flex;\r\n justify-content: center;\r\n\r\n > li\r\n {\r\n list-style: none;\r\n width: 1.8em;\r\n line-height: 1.8em;\r\n margin: 0 #{(1em / 3)};\r\n\r\n > a,\r\n > span\r\n {\r\n display: block;\r\n border-radius: #{(1em / 3)};\r\n text-decoration: none;\r\n }\r\n\r\n > span\r\n {\r\n cursor: not-allowed;\r\n }\r\n }\r\n}\r\n\r\nfooter#page-footer\r\n{\r\n padding: #{(4em / 3)} 0 #{(4em / 6)};\r\n font-size: 0.85em;\r\n\r\n .footer-flex\r\n {\r\n display: flex;\r\n flex-wrap: wrap;\r\n\r\n .footer-flex-item\r\n {\r\n width: 50%;\r\n min-width: 200px;\r\n text-align: center;\r\n flex-grow: 1;\r\n\r\n &:last-child\r\n {\r\n text-align: right;\r\n }\r\n\r\n &:first-child\r\n {\r\n text-align: left;\r\n }\r\n }\r\n }\r\n\r\n p\r\n {\r\n margin: 0 0 #{(2em / 5)};\r\n }\r\n}\r\n\r\n#modal-search-overlay\r\n{\r\n display: none;\r\n position: fixed;\r\n top: 0;\r\n bottom: 0;\r\n left: 0;\r\n right: 0;\r\n transition: background-color linear 0.6s, -webkit-backdrop-filter linear 0.6s, backdrop-filter linear 0.6s;\r\n background-color: rgba(0,0,0, 0);\r\n -webkit-backdrop-filter: blur(0);\r\n backdrop-filter: blur(0);\r\n z-index: 100;\r\n\r\n &.open\r\n {\r\n display: block;\r\n background-color: rgba(0,0,0, 0.75);\r\n -webkit-backdrop-filter: blur(10px);\r\n backdrop-filter: blur(10px);\r\n }\r\n\r\n #modal-search\r\n {\r\n position: absolute;\r\n top: 15%;\r\n left: 25%;\r\n width: 50%;\r\n max-height: 70%;\r\n overflow: auto;\r\n padding: 2em;\r\n border-radius: 2px;\r\n box-sizing: border-box;\r\n border: 1px solid #ccc;\r\n\r\n h3\r\n {\r\n margin: 0 0 1em;\r\n font-size: 1.5em;\r\n font-weight: 300;\r\n }\r\n\r\n > #modal-search-box\r\n {\r\n width: 100%;\r\n\r\n > *\r\n {\r\n display: inline-block;\r\n height: 2.5em;\r\n padding: 0.5em;\r\n box-sizing: border-box;\r\n border: 1px solid;\r\n }\r\n\r\n > #modal-search-input\r\n {\r\n width: calc(100% - #{(10em / 3)});\r\n }\r\n\r\n > #modal-search-button\r\n {\r\n width: #{(8em / 3)};\r\n }\r\n }\r\n\r\n > #modal-search-result\r\n {\r\n display: flex;\r\n flex-wrap: wrap;\r\n justify-content: space-between;\r\n\r\n > .search-result-item\r\n {\r\n display: block;\r\n padding: 0 1em;\r\n width: 140px;\r\n flex-grow: 1;\r\n\r\n > .search-result-heading\r\n {\r\n font-size: 1.2em;\r\n margin-bottom: 0;\r\n }\r\n }\r\n }\r\n }\r\n}\r\n\r\n#menu-open-overlay\r\n{\r\n display: none;\r\n position: fixed;\r\n top: 0;\r\n bottom: 0;\r\n left: 0;\r\n right: 0;\r\n z-index: 5;\r\n\r\n &.open\r\n {\r\n display: block;\r\n }\r\n}\r\n\r\n@media (max-width: 980px)\r\n{\r\n #modal-search-overlay #modal-search\r\n {\r\n left: 10%;\r\n width: 80%;\r\n }\r\n}\r\n\r\n@media (max-width: 640px)\r\n{\r\n header#page-header h1\r\n {\r\n text-align: center;\r\n }\r\n\r\n nav#page-navigation\r\n {\r\n button\r\n {\r\n display: block;\r\n }\r\n\r\n #page-navigation-links\r\n {\r\n display: none;\r\n\r\n &.open\r\n {\r\n display: block;\r\n }\r\n\r\n > li\r\n {\r\n display: block;\r\n text-align: left;\r\n }\r\n }\r\n }\r\n\r\n article\r\n {\r\n h1,\r\n h1.eager-wrapping\r\n {\r\n font-size: 3em;\r\n line-height: #{(4 / 3)};\r\n }\r\n\r\n h3\r\n {\r\n text-align: center;\r\n }\r\n\r\n .addthis_sharing_toolbox\r\n {\r\n text-align: center;\r\n }\r\n }\r\n\r\n footer#page-footer .footer-flex .footer-flex-item\r\n {\r\n &:first-child\r\n {\r\n text-align: center;\r\n margin-bottom: #{(3em / 2)};\r\n }\r\n\r\n &:last-child\r\n {\r\n text-align: center;\r\n }\r\n }\r\n}\r\n\r\n@media (max-width: 1300px)\r\n{\r\n .latest-flex .latest-flex-item\r\n {\r\n min-width: calc(50% - 2em);\r\n }\r\n}","body{font-family:Roboto,sans-serif;font-size:10pt;line-height:1.6;margin:0;box-sizing:border-box}a{text-decoration:none}a:active,a:focus,a:hover{text-decoration:underline}h1{font-size:3em;font-weight:500}p{margin:0 0 1em}table{width:100%;border-collapse:collapse}table td,table th{margin:0;border:0;padding:4px 6px}table thead th{border-bottom:1px solid;text-align:left}.at-share-btn-elements{margin-left:-8px}.at-share-btn-elements>.at_flat_counter{font-size:14px!important;vertical-align:top!important}.at-share-btn-elements>.at-share-btn{margin-left:8px!important}.container{width:1240px;max-width:80%;margin:0 auto;position:relative}.no-wrapping{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.eager-wrapping{word-wrap:break-word}.field-validation-error,.text-danger{display:block;margin:.33333em 0;color:#ff2626}a.button,input[type=submit].button{display:inline-block;vertical-align:middle;padding:.33333em 1em;background-color:#0a81cc;color:#fff;text-decoration:none;border-radius:.16667em;text-align:center}a.button.edit-button,input[type=submit].button.edit-button{background-color:#ff9f19}a.button.delete-button,input[type=submit].button.delete-button{background-color:#ff2626}a.button.add-button,input[type=submit].button.add-button{background-color:#12b23f}header#page-header h1{font-weight:300;margin:.33em 0}header#page-header h1 a{text-decoration:none}nav#page-navigation{border-bottom-width:1px;border-bottom-style:solid;position:relative;z-index:10}nav#page-navigation #page-navigation-links{margin:0 -15px .33333px;padding:0;text-align:right}nav#page-navigation #page-navigation-links>li{display:inline-block;vertical-align:top}nav#page-navigation #page-navigation-links>li>a{display:block;padding:15px}nav#page-navigation #page-navigation-links>li>a:active,nav#page-navigation #page-navigation-links>li>a:focus,nav#page-navigation #page-navigation-links>li>a:hover{text-decoration:none}nav#page-navigation button{display:none;width:100%;border:0;font-size:1.2em;font-weight:300;padding:.66667em 0;background:0 0}.dropdown-parent .dropdown-menu{box-sizing:border-box;display:none;position:absolute;left:-15px;right:-15px;max-width:1270px;padding:15px 15px 0;text-align:left;z-index:10;border-width:1px;border-style:solid;border-top-width:0;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.dropdown-parent .dropdown-menu h4{margin:0 0 .5em;font-size:1.2em;font-weight:500}.dropdown-parent .dropdown-menu .dropdown-menu-block{display:inline-block;vertical-align:top;margin-bottom:15px;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.dropdown-parent .dropdown-menu .dropdown-menu-block ul{padding:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.dropdown-parent .dropdown-menu .dropdown-menu-block li{list-style-type:none;line-height:2em;width:133px;vertical-align:top;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.dropdown-parent .dropdown-menu .dropdown-menu-block li>a{display:block;padding:0 1em}.dropdown-parent .dropdown-menu .dropdown-menu-block li>a:active,.dropdown-parent .dropdown-menu .dropdown-menu-block li>a:focus,.dropdown-parent .dropdown-menu .dropdown-menu-block li>a:hover{text-decoration:none}.dropdown-parent .dropdown-menu #settings-theme-menu{width:120px;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.dropdown-parent .dropdown-menu #settings-lang-menu{width:320px;-webkit-box-flex:1000;-webkit-flex-grow:1000;-ms-flex-positive:1000;flex-grow:1000}.dropdown-parent.open .dropdown-menu{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}article{padding:2em 0}article h1{font-size:4em;font-weight:300;text-align:center;margin:0 0 .5em}article h1.eager-wrapping{font-size:4em}article h3{font-size:2em;font-weight:700;margin:.33333em 0 .66667em;text-transform:uppercase;letter-spacing:.25em;border-bottom:1px solid}article h4{margin:1em 0 0}article .build-group-listing{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;margin:0 -1.5em}article .build-group-listing .build-group{width:250px;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;margin:0 1.5em 3em;box-sizing:border-box;border:3px solid #0a81cc;padding:.5em 1.5em}article .build-group-listing .build-group h3{background-color:#0a81cc;color:#fff;margin:-.25em -.75em .75em;font-size:2em;line-height:1.25em;font-weight:400;padding:0;letter-spacing:0;border-bottom:0;text-transform:lowercase;text-align:center}article .build-group-listing .build-group h3 a{color:#fff;text-decoration:none!important;display:block;padding:.25em 0}article .build-group-listing .build-group p{font-size:1.1em;font-weight:300;margin:0 0 .66667em}article .build-group-listing .build-group-empty{width:270px;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;margin:0 .75em;box-sizing:border-box}.latest-flex{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;margin:2em -2em 0 0}.latest-flex .latest-flex-item{width:240px;box-sizing:border-box;background:#fff;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;margin:0 2em 2em 0}.latest-flex .latest-flex-item:active,.latest-flex .latest-flex-item:focus,.latest-flex .latest-flex-item:hover{text-decoration:none}.latest-flex .latest-flex-item h3.latest-flex-title{border-bottom:0;margin:0;padding:0;font-size:1.25em;font-weight:400;text-align:center;padding:.4em .5em;margin:-1px;color:#fff}.latest-flex .latest-flex-item.latest-flex-red{border:.25em solid #ff2626}.latest-flex .latest-flex-item.latest-flex-red h3.latest-flex-title{background:#ff2626}.latest-flex .latest-flex-item.latest-flex-yellow{border:.25em solid #ff9f19}.latest-flex .latest-flex-item.latest-flex-yellow h3.latest-flex-title{background:#ff9f19}.latest-flex .latest-flex-item.latest-flex-blue{border:.25em solid #0a81cc}.latest-flex .latest-flex-item.latest-flex-blue h3.latest-flex-title{background:#0a81cc}.latest-flex .latest-flex-item.latest-flex-green{border:.25em solid #12b23f}.latest-flex .latest-flex-item.latest-flex-green h3.latest-flex-title{background:#12b23f}.latest-flex .latest-flex-item .latest-flex-detail{color:#373736;text-align:center;font-weight:300}.latest-flex .latest-flex-item .latest-flex-detail .latest-flex-build{font-size:2.33333em;margin:.33333em 0 .16667em}.latest-flex .latest-flex-item .latest-flex-detail .latest-flex-lab{font-size:1.5em;margin:0 0 .5em}.latest-flex .latest-flex-item .latest-flex-detail .latest-flex-time{margin:0 0 .75em}.latest-full{display:block;background:#0a81cc;font-weight:400;text-align:center;color:#fff;margin:0 0 2em;padding:.66667em}.latest-full:active,.latest-full:focus,.latest-full:hover{text-decoration:none}.build-details-flex{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;font-size:1.1em;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.build-details-flex .build-details-flex-item{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;margin-bottom:1.5em;width:180px}.build-details-flex .build-details-flex-item label{font-weight:700;display:inline-block;vertical-align:top;margin-right:1em;min-width:100px}.build-details-flex .build-details-flex-item .build-details-flex-value{display:inline-block;vertical-align:top;margin-right:20px}.form-group{margin-bottom:1.5em;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-group>label{width:20%;max-width:240px;min-width:120px;text-align:left;font-weight:700;margin-right:1em;display:inline-block;vertical-align:top;margin-top:.25em;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.form-group>div{margin-left:calc(20% + 1em);width:40%;min-width:240px;max-width:560px;display:inline-block;vertical-align:top;-webkit-box-flex:2;-webkit-flex-grow:2;-ms-flex-positive:2;flex-grow:2}.form-group>div input,.form-group>div select,.form-group>div textarea{width:100%;box-sizing:border-box;border:1px solid;padding:.33333em .5em;border-radius:.16667em;line-height:1em;border-color:#888}.form-group>div .group-input-button{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.form-group>div .group-input-button input{border-top-right-radius:0;border-bottom-right-radius:0;border-right:0}.form-group>div .group-input-button button{border-top-left-radius:0;border-bottom-left-radius:0;width:120px;border:1px solid #888;border-left:0}.form-group>div input[type=checkbox],.form-group>div input[type=submit]{width:auto}.form-group>div .group-input-button>button,.form-group>div>button,.form-group>div>input[type=submit]{display:inline-block;vertical-align:middle;padding:.33333em 1em;background-color:#12b23f;color:#fff;text-decoration:none;border-radius:.16667em;border:0;line-height:1.6}.form-group>div.wide-group{width:40%}.form-group>div.wide-group>.trumbowyg-box{width:100%;margin:0}.form-group>label+div{margin-left:0}.credits-wrapper{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}.credits-wrapper .credits-list{width:480px;max-width:100%;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.credits-list dt{font-weight:700}.credits-list dd{margin-left:0}.credits-list dd+dt{margin-top:1.5em}ul.pagination{text-align:center;margin:.5em 0 1em;padding:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}ul.pagination>li{list-style:none;width:1.8em;line-height:1.8em;margin:0 .33333em}ul.pagination>li>a,ul.pagination>li>span{display:block;border-radius:.33333em;text-decoration:none}ul.pagination>li>span{cursor:not-allowed}footer#page-footer{padding:1.33333em 0 .66667em;font-size:.85em}footer#page-footer .footer-flex{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}footer#page-footer .footer-flex .footer-flex-item{width:50%;min-width:200px;text-align:center;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}footer#page-footer .footer-flex .footer-flex-item:last-child{text-align:right}footer#page-footer .footer-flex .footer-flex-item:first-child{text-align:left}footer#page-footer p{margin:0 0 .4em}#modal-search-overlay{display:none;position:fixed;top:0;bottom:0;left:0;right:0;transition:background-color linear .6s,-webkit-backdrop-filter linear .6s,backdrop-filter linear .6s;background-color:transparent;-webkit-backdrop-filter:blur(0);backdrop-filter:blur(0);z-index:100}#modal-search-overlay.open{display:block;background-color:rgba(0,0,0,.75);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}#modal-search-overlay #modal-search{position:absolute;top:15%;left:25%;width:50%;max-height:70%;overflow:auto;padding:2em;border-radius:2px;box-sizing:border-box;border:1px solid #ccc}#modal-search-overlay #modal-search h3{margin:0 0 1em;font-size:1.5em;font-weight:300}#modal-search-overlay #modal-search>#modal-search-box{width:100%}#modal-search-overlay #modal-search>#modal-search-box>*{display:inline-block;height:2.5em;padding:.5em;box-sizing:border-box;border:1px solid}#modal-search-overlay #modal-search>#modal-search-box>#modal-search-input{width:calc(100% - 3.33333em)}#modal-search-overlay #modal-search>#modal-search-box>#modal-search-button{width:2.66667em}#modal-search-overlay #modal-search>#modal-search-result{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}#modal-search-overlay #modal-search>#modal-search-result>.search-result-item{display:block;padding:0 1em;width:140px;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}#modal-search-overlay #modal-search>#modal-search-result>.search-result-item>.search-result-heading{font-size:1.2em;margin-bottom:0}#menu-open-overlay{display:none;position:fixed;top:0;bottom:0;left:0;right:0;z-index:5}#menu-open-overlay.open{display:block}@media (max-width:980px){#modal-search-overlay #modal-search{left:10%;width:80%}}@media (max-width:640px){header#page-header h1{text-align:center}nav#page-navigation button{display:block}nav#page-navigation #page-navigation-links{display:none}nav#page-navigation #page-navigation-links.open{display:block}nav#page-navigation #page-navigation-links>li{display:block;text-align:left}article h1,article h1.eager-wrapping{font-size:3em;line-height:1.33333}article h3{text-align:center}article .addthis_sharing_toolbox{text-align:center}footer#page-footer .footer-flex .footer-flex-item:first-child{text-align:center;margin-bottom:1.5em}footer#page-footer .footer-flex .footer-flex-item:last-child{text-align:center}}@media (max-width:1300px){.latest-flex .latest-flex-item{min-width:calc(50% - 2em)}}\n/*# sourceMappingURL=default.css.map */\n"]} \ No newline at end of file diff --git a/BuildFeed/res/css/default.scss b/BuildFeed/res/css/default.scss index d0154f5..c0a30ee 100644 --- a/BuildFeed/res/css/default.scss +++ b/BuildFeed/res/css/default.scss @@ -87,7 +87,8 @@ table word-wrap: break-word; } -.field-validation-error +.field-validation-error, +.text-danger { display: block; margin: #{(1em / 3)} 0; @@ -145,7 +146,7 @@ nav#page-navigation #page-navigation-links { - margin: 0 -15px; + margin: 0 -15px #{(1px / 3)}; /* bottom margin fixes Chrome in 4k */ padding: 0; text-align: right; diff --git a/BuildFeed/res/ts/bfs.js b/BuildFeed/res/ts/bfs.js index f0bcf13..4482236 100644 --- a/BuildFeed/res/ts/bfs.js +++ b/BuildFeed/res/ts/bfs.js @@ -1,2 +1,2 @@ -"use strict";var BuildFeed;!function(e){function t(e){e.preventDefault();var t=this;t.nextElementSibling.classList.toggle("open")}function n(e){e.preventDefault();var t=this;t.parentElement.classList.toggle("open");var n=document.getElementById("menu-open-overlay");n.classList.add("open")}function a(e){e.preventDefault();for(var t=document.getElementsByClassName("dropdown-parent"),n=0;n\r\n/// \r\n\"use strict\";\r\n\r\nmodule BuildFeed\r\n{\r\n let ajax: XMLHttpRequest;\r\n let timeout: number;\r\n\r\n export function MobileMenuToggle(ev: MouseEvent)\r\n {\r\n ev.preventDefault();\r\n\r\n const button = this as HTMLButtonElement;\r\n button.nextElementSibling.classList.toggle(\"open\");\r\n }\r\n\r\n export function DropdownClick(ev: MouseEvent)\r\n {\r\n ev.preventDefault();\r\n\r\n const link = this as HTMLAnchorElement;\r\n link.parentElement.classList.toggle(\"open\");\r\n\r\n const menuClickCapture = document.getElementById(\"menu-open-overlay\") as HTMLDivElement;\r\n menuClickCapture.classList.add(\"open\");\r\n }\r\n\r\n export function CloseDropdowns(ev: MouseEvent)\r\n {\r\n ev.preventDefault();\r\n\r\n const ddParents = document.getElementsByClassName(\"dropdown-parent\");\r\n for (let i = 0; i < ddParents.length; i++)\r\n {\r\n ddParents[i].classList.remove(\"open\");\r\n }\r\n\r\n const menuClickCapture = document.getElementById(\"menu-open-overlay\") as HTMLDivElement;\r\n menuClickCapture.classList.remove(\"open\");\r\n }\r\n\r\n export function SwitchTheme(ev: MouseEvent)\r\n {\r\n ev.preventDefault();\r\n\r\n const link = this as HTMLAnchorElement;\r\n document.cookie = `bf_theme=${link.dataset[\"theme\"]}; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/`;\r\n location.reload(true);\r\n }\r\n\r\n export function SwitchLanguage(ev: MouseEvent)\r\n {\r\n ev.preventDefault();\r\n\r\n const link = this as HTMLAnchorElement;\r\n document.cookie = `bf_lang=${link.dataset[\"lang\"]}; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/`;\r\n location.reload(true);\r\n }\r\n\r\n export function OpenSearch(ev: MouseEvent)\r\n {\r\n ev.preventDefault();\r\n\r\n const modal = document.getElementById(\"modal-search-overlay\") as HTMLDivElement;\r\n modal.classList.add(\"open\");\r\n }\r\n\r\n export function CloseSearch(ev: MouseEvent)\r\n {\r\n ev.preventDefault();\r\n\r\n const modal = document.getElementById(\"modal-search-overlay\") as HTMLDivElement;\r\n modal.classList.remove(\"open\");\r\n }\r\n\r\n export function StopClick(ev: MouseEvent)\r\n {\r\n ev.preventDefault();\r\n ev.stopPropagation();\r\n }\r\n\r\n export function InitiateSearch(ev: KeyboardEvent)\r\n {\r\n const resultPane = document.getElementById(\"modal-search-result\") as HTMLDivElement;\r\n resultPane.innerHTML = \"\";\r\n\r\n if (typeof (timeout) !== \"undefined\")\r\n {\r\n clearTimeout(timeout);\r\n }\r\n\r\n if (typeof (ajax) !== \"undefined\" && ajax.readyState !== XMLHttpRequest.DONE)\r\n {\r\n ajax.abort();\r\n }\r\n\r\n timeout = setInterval(SendSearch, 200);\r\n }\r\n\r\n export function SendSearch()\r\n {\r\n if (typeof (timeout) !== \"undefined\")\r\n {\r\n clearTimeout(timeout);\r\n }\r\n\r\n const modalInput = document.getElementById(\"modal-search-input\") as HTMLInputElement;\r\n\r\n ajax = new XMLHttpRequest();\r\n ajax.onreadystatechange = CompleteSearch;\r\n ajax.open(\"GET\", `/api/GetSearchResult/${modalInput.value}/`, true);\r\n ajax.setRequestHeader(\"accept\", \"application/json\");\r\n ajax.send(null);\r\n }\r\n\r\n export function CompleteSearch(ev: ProgressEvent)\r\n {\r\n if (ajax.readyState !== XMLHttpRequest.DONE || ajax.status !== 200)\r\n {\r\n return;\r\n }\r\n\r\n const resultPane = document.getElementById(\"modal-search-result\") as HTMLDivElement;\r\n const templateContent = document.getElementById(\"result-template\") as HTMLDivElement;\r\n const template = jsrender.templates(templateContent.innerHTML);\r\n const content = template.render(JSON.parse(ajax.responseText));\r\n resultPane.innerHTML = content;\r\n\r\n const resultLinks = resultPane.getElementsByTagName(\"a\");\r\n for (let i = 0; i < resultLinks.length; i++)\r\n {\r\n resultLinks[i].addEventListener(\"click\", (mev: MouseEvent) =>\r\n {\r\n mev.preventDefault();\r\n const modalInput = document.getElementById(\"modal-search-input\") as HTMLInputElement;\r\n ga(\"send\", \"pageview\", `/api/GetSearchResult/${modalInput.value}/`);\r\n location.assign((mev.currentTarget as HTMLAnchorElement).href);\r\n });\r\n }\r\n }\r\n\r\n export function BuildFeedSetup(ev: Event)\r\n {\r\n const ddParents = document.getElementsByClassName(\"dropdown-parent\");\r\n for (let i = 0; i < ddParents.length; i++)\r\n {\r\n for (let j = 0; j < ddParents[i].childNodes.length; j++)\r\n {\r\n const el = ddParents[i].childNodes[j];\r\n\r\n if (el.nodeName === \"A\")\r\n {\r\n el.addEventListener(\"click\", DropdownClick);\r\n }\r\n }\r\n }\r\n\r\n const menuClickCapture = document.getElementById(\"menu-open-overlay\") as HTMLDivElement;\r\n menuClickCapture.addEventListener(\"click\", CloseDropdowns);\r\n\r\n const ddThemes = document.getElementById(\"settings-theme-menu\").getElementsByTagName(\"a\");\r\n for (let i = 0; i < ddThemes.length; i++)\r\n {\r\n ddThemes[i].addEventListener(\"click\", SwitchTheme);\r\n }\r\n\r\n const ddLangs = document.getElementById(\"settings-lang-menu\").getElementsByTagName(\"a\");\r\n for (let i = 0; i < ddLangs.length; i++)\r\n {\r\n ddLangs[i].addEventListener(\"click\", SwitchLanguage);\r\n }\r\n\r\n const btnNav = document.getElementById(\"page-navigation-toggle\");\r\n btnNav.addEventListener(\"click\", MobileMenuToggle);\r\n\r\n const btnSearch = document.getElementById(\"page-navigation-search\");\r\n btnSearch.addEventListener(\"click\", OpenSearch);\r\n\r\n const modalOverlay = document.getElementById(\"modal-search-overlay\") as HTMLDivElement;\r\n modalOverlay.addEventListener(\"click\", CloseSearch);\r\n\r\n const modalDialog = document.getElementById(\"modal-search\") as HTMLDivElement;\r\n modalDialog.addEventListener(\"click\", StopClick);\r\n\r\n const modalInput = document.getElementById(\"modal-search-input\") as HTMLInputElement;\r\n modalInput.addEventListener(\"keyup\", InitiateSearch);\r\n }\r\n}\r\n\r\nwindow.addEventListener(\"load\", BuildFeed.BuildFeedSetup);"]} \ No newline at end of file +{"version":3,"sources":["bfs.ts"],"names":["BuildFeed","MobileMenuToggle","ev","preventDefault","this","nextElementSibling","classList","toggle","DropdownClick","parentElement","document","getElementById","add","CloseDropdowns","ddParents","getElementsByClassName","i","length","remove","SwitchTheme","link","cookie","dataset","location","reload","SwitchLanguage","OpenSearch","CloseSearch","StopClick","stopPropagation","InitiateSearch","innerHTML","clearTimeout","timeout","ajax","readyState","XMLHttpRequest","DONE","abort","setInterval","SendSearch","modalInput","onreadystatechange","CompleteSearch","open","value","setRequestHeader","send","status","resultPane","templateContent","template","jsrender","templates","content","render","JSON","parse","responseText","resultLinks","getElementsByTagName","addEventListener","mev","ga","assign","currentTarget","href","BuildFeedSetup","j","childNodes","el","nodeName","ddThemes","ddLangs","window"],"mappings":"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","file":"bfs.js","sourcesContent":["/// \r\n/// \r\n\"use strict\";\r\n\r\nmodule BuildFeed\r\n{\r\n let ajax: XMLHttpRequest;\r\n let timeout: number;\r\n\r\n export function MobileMenuToggle(ev: MouseEvent)\r\n {\r\n ev.preventDefault();\r\n\r\n const button = this as HTMLButtonElement;\r\n button.nextElementSibling.classList.toggle(\"open\");\r\n }\r\n\r\n export function DropdownClick(ev: MouseEvent)\r\n {\r\n ev.preventDefault();\r\n\r\n const link = this as HTMLAnchorElement;\r\n link.parentElement.classList.toggle(\"open\");\r\n\r\n const menuClickCapture = document.getElementById(\"menu-open-overlay\") as HTMLDivElement;\r\n menuClickCapture.classList.add(\"open\");\r\n }\r\n\r\n export function CloseDropdowns(ev: MouseEvent)\r\n {\r\n ev.preventDefault();\r\n\r\n const ddParents = document.getElementsByClassName(\"dropdown-parent\");\r\n for (let i = 0; i < ddParents.length; i++)\r\n {\r\n ddParents[i].classList.remove(\"open\");\r\n }\r\n\r\n const menuClickCapture = document.getElementById(\"menu-open-overlay\") as HTMLDivElement;\r\n menuClickCapture.classList.remove(\"open\");\r\n }\r\n\r\n export function SwitchTheme(ev: MouseEvent)\r\n {\r\n ev.preventDefault();\r\n\r\n const link = this as HTMLAnchorElement;\r\n document.cookie = `bf_theme=${link.dataset[\"theme\"]}; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/`;\r\n location.reload(true);\r\n }\r\n\r\n export function SwitchLanguage(ev: MouseEvent)\r\n {\r\n ev.preventDefault();\r\n\r\n const link = this as HTMLAnchorElement;\r\n document.cookie = `bf_lang=${link.dataset[\"lang\"]}; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/`;\r\n location.reload(true);\r\n }\r\n\r\n export function OpenSearch(ev: MouseEvent)\r\n {\r\n ev.preventDefault();\r\n\r\n const modal = document.getElementById(\"modal-search-overlay\") as HTMLDivElement;\r\n modal.classList.add(\"open\");\r\n }\r\n\r\n export function CloseSearch(ev: MouseEvent)\r\n {\r\n ev.preventDefault();\r\n\r\n const modal = document.getElementById(\"modal-search-overlay\") as HTMLDivElement;\r\n modal.classList.remove(\"open\");\r\n }\r\n\r\n export function StopClick(ev: MouseEvent)\r\n {\r\n ev.preventDefault();\r\n ev.stopPropagation();\r\n }\r\n\r\n export function InitiateSearch(ev: KeyboardEvent)\r\n {\r\n const resultPane = document.getElementById(\"modal-search-result\") as HTMLDivElement;\r\n resultPane.innerHTML = \"\";\r\n\r\n if (typeof (timeout) !== \"undefined\")\r\n {\r\n clearTimeout(timeout);\r\n }\r\n\r\n if (typeof (ajax) !== \"undefined\" && ajax.readyState !== XMLHttpRequest.DONE)\r\n {\r\n ajax.abort();\r\n }\r\n\r\n timeout = setInterval(SendSearch, 200);\r\n }\r\n\r\n export function SendSearch()\r\n {\r\n if (typeof (timeout) !== \"undefined\")\r\n {\r\n clearTimeout(timeout);\r\n }\r\n\r\n const modalInput = document.getElementById(\"modal-search-input\") as HTMLInputElement;\r\n\r\n ajax = new XMLHttpRequest();\r\n ajax.onreadystatechange = CompleteSearch;\r\n ajax.open(\"GET\", `/api/GetSearchResult/${modalInput.value}/`, true);\r\n ajax.setRequestHeader(\"accept\", \"application/json\");\r\n ajax.send(null);\r\n }\r\n\r\n export function CompleteSearch(ev: ProgressEvent)\r\n {\r\n if (ajax.readyState !== XMLHttpRequest.DONE || ajax.status !== 200)\r\n {\r\n return;\r\n }\r\n\r\n const resultPane = document.getElementById(\"modal-search-result\") as HTMLDivElement;\r\n const templateContent = document.getElementById(\"result-template\") as HTMLDivElement;\r\n const template = jsrender.templates(templateContent.innerHTML);\r\n const content = template.render(JSON.parse(ajax.responseText));\r\n resultPane.innerHTML = content;\r\n\r\n const resultLinks = resultPane.getElementsByTagName(\"a\");\r\n for (let i = 0; i < resultLinks.length; i++)\r\n {\r\n resultLinks[i].addEventListener(\"click\", (mev: MouseEvent) =>\r\n {\r\n mev.preventDefault();\r\n const modalInput = document.getElementById(\"modal-search-input\") as HTMLInputElement;\r\n ga(\"send\", \"pageview\", `/api/GetSearchResult/${modalInput.value}/`);\r\n location.assign((mev.currentTarget as HTMLAnchorElement).href);\r\n });\r\n }\r\n }\r\n\r\n export function BuildFeedSetup(ev: Event)\r\n {\r\n const ddParents = document.getElementsByClassName(\"dropdown-parent\");\r\n for (let i = 0; i < ddParents.length; i++)\r\n {\r\n for (let j = 0; j < ddParents[i].childNodes.length; j++)\r\n {\r\n const el = ddParents[i].childNodes[j];\r\n\r\n if (el.nodeName === \"A\")\r\n {\r\n el.addEventListener(\"click\", DropdownClick);\r\n }\r\n }\r\n }\r\n\r\n const menuClickCapture = document.getElementById(\"menu-open-overlay\") as HTMLDivElement;\r\n menuClickCapture.addEventListener(\"click\", CloseDropdowns);\r\n\r\n const ddThemes = document.getElementById(\"settings-theme-menu\").getElementsByTagName(\"a\");\r\n for (let i = 0; i < ddThemes.length; i++)\r\n {\r\n ddThemes[i].addEventListener(\"click\", SwitchTheme);\r\n }\r\n\r\n const ddLangs = document.getElementById(\"settings-lang-menu\").getElementsByTagName(\"a\");\r\n for (let i = 0; i < ddLangs.length; i++)\r\n {\r\n ddLangs[i].addEventListener(\"click\", SwitchLanguage);\r\n }\r\n\r\n const btnNav = document.getElementById(\"page-navigation-toggle\");\r\n btnNav.addEventListener(\"click\", MobileMenuToggle);\r\n\r\n const btnSearch = document.getElementById(\"page-navigation-search\");\r\n btnSearch.addEventListener(\"click\", OpenSearch);\r\n\r\n const modalOverlay = document.getElementById(\"modal-search-overlay\") as HTMLDivElement;\r\n modalOverlay.addEventListener(\"click\", CloseSearch);\r\n\r\n const modalDialog = document.getElementById(\"modal-search\") as HTMLDivElement;\r\n modalDialog.addEventListener(\"click\", StopClick);\r\n\r\n const modalInput = document.getElementById(\"modal-search-input\") as HTMLInputElement;\r\n modalInput.addEventListener(\"keyup\", InitiateSearch);\r\n }\r\n}\r\n\r\nwindow.addEventListener(\"load\", BuildFeed.BuildFeedSetup);"]} \ No newline at end of file