Merge pull request #1 from abpframework/dev

Sync ABP to my dev branch
pull/4038/head
Khaled 6 years ago committed by GitHub
commit 752d98b261
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,6 +1,6 @@
# Customizing the Existing Modules
ABP Framework provides was designed to support to build fully [modular applications](Module-Development-Basics.md) and systems. It also provides some [pre-built application modules](Modules/Index.md) those are **ready to use** in any kind of application.
ABP Framework has been designed to support to build fully [modular applications](Module-Development-Basics.md) and systems. It also provides some [pre-built application modules](Modules/Index.md) those are **ready to use** in any kind of application.
For example, you can **re-use** the [Identity Management Module](Modules/Identity.md) to add user, role and permission management to your application. The [application startup template](Startup-Templates/Application.md) already comes with Identity and some other modules **pre-installed**.
@ -59,4 +59,4 @@ Also, see the following documents:
* See [the localization document](Localization.md) to learn how to extend existing localization resources.
* See [the settings document](Settings.md) to learn how to change setting definitions of a depended module.
* See [the authorization document](Authorization.md) to learn how to change permission definitions of a depended module.
* See [the authorization document](Authorization.md) to learn how to change permission definitions of a depended module.

@ -2,13 +2,15 @@
Dapper is a light-weight and simple database provider. The major benefit of using Dapper is writing T-SQL queries. It provides some extension methods for `IDbConnection` interface.
ABP does not encapsulate many functions for Dapper. ABP Dapper library provides a `DapperRepository<TDbContext>` base class based on ABP EntityFrameworkCore module, which provides the `IDbConnection` and `IDbTransaction` properties required by Dapper. `IDbConnection` and `IDbTransaction` works well with the [ABP Unit-Of-Work](Unit-Of-Work.md).
ABP does not encapsulate many functions for Dapper. ABP Dapper library provides a `DapperRepository<TDbContext>` base class based on ABP EntityFrameworkCore module, which provides the `IDbConnection` and `IDbTransaction` properties required by Dapper.
`IDbConnection` and `IDbTransaction` works well with the [ABP Unit-Of-Work](Unit-Of-Work.md).
## Installation
Install and configure EF Core according to [EF Core's integrated documentation](Entity-Framework-Core.md).
`Volo.Abp.Dapper` is the library for the Dapper integration.
`Volo.Abp.Dapper` is the main nuget package for the Dapper integration.
You can find it on NuGet Gallery: https://www.nuget.org/packages/Volo.Abp.Dapper

@ -23,8 +23,8 @@ One common advise to start a new solution is **always to start with a monolith**
However, developing such a well-modular application can be a problem since it is **hard to keep modules isolated** from each other as you would do it for microservices (see [Stefan Tilkov's article](https://martinfowler.com/articles/dont-start-monolith.html) about that). Microservice architecture naturally forces you to develop well isolated services, but in a modular monolithic application it's easy to tight couple modules to each other and design **weak module boundaries** and API contracts.
ABP can help you in that point by oferring a **microservice-compatible, strict module architecture** where your module is splitted into multiple layers/projects and developed in its own VS solution completely isolated and independent from other modules. Such a developed module is a natural microservice yet it can be easily plugged-in a monolithic application. See the [module development best practice guide](Best-Practices/Index.md) that offers a **microservice-first module design**. All [standard ABP modules](https://github.com/abpframework/abp/tree/master/modules) are developed based on this guide. So, you can use these modules by embedding into your monolithic solution or deploy them separately and use via remote APIs. They can share a single database or can have their own database based on your simple configuration.
ABP can help you in that point by offerring a **microservice-compatible, strict module architecture** where your module is splitted into multiple layers/projects and developed in its own VS solution completely isolated and independent from other modules. Such a developed module is a natural microservice yet it can be easily plugged-in a monolithic application. See the [module development best practice guide](Best-Practices/Index.md) that offers a **microservice-first module design**. All [standard ABP modules](https://github.com/abpframework/abp/tree/master/modules) are developed based on this guide. So, you can use these modules by embedding into your monolithic solution or deploy them separately and use via remote APIs. They can share a single database or can have their own database based on your simple configuration.
## Microservice Demo Solution
The [sample microservice solution](Samples/Microservice-Demo.md) demonstrates a complete microservice solution based on the ABP framework.
The [sample microservice solution](Samples/Microservice-Demo.md) demonstrates a complete microservice solution based on the ABP framework.

@ -8,10 +8,14 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Bootstrap
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
if(CultureHelper.IsRtl)
if (CultureHelper.IsRtl)
{
context.Files.AddIfNotContains("/libs/bootstrap/css/bootstrap-rtl.css");
}
else
{
context.Files.AddIfNotContains("/libs/bootstrap/css/bootstrap.css");
}
}
}
}

@ -24,13 +24,11 @@
Layout = null;
AbpAntiForgeryManager.SetCookie();
var containerClass = ViewBag.FluidLayout == true ? "container-fluid" : "container"; //TODO: Better and type-safe options
}
<!DOCTYPE html>
<html lang="@CultureInfo.CurrentCulture.Name" dir=@(CultureHelper.IsRtl ? "rtl" : "")>
<!--<![endif]-->">
<head>
@await Component.InvokeLayoutHookAsync(LayoutHooks.Head.First, StandardLayouts.Account)

@ -1,6 +1,5 @@
using System;
using System.Globalization;
using System.Threading;
using JetBrains.Annotations;
namespace Volo.Abp.Localization
@ -36,10 +35,8 @@ namespace Volo.Abp.Localization
});
}
public static bool IsRtl
{
get { return CultureInfo.CurrentUICulture.TextInfo.IsRightToLeft; }
}
public static bool IsRtl => CultureInfo.CurrentUICulture.TextInfo.IsRightToLeft;
public static bool IsValidCultureCode(string cultureCode)
{
if (cultureCode.IsNullOrWhiteSpace())

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,7 +1,15 @@
a, a:hover {
text-decoration: none;
color: #000000;
}
.post-content {
color: #555;
a {
text-decoration: underline !important;
color: #555;
}
a:hover {
text-decoration: underline !important;
color: #000000;
}
}
p {
color: #444;
a {

@ -1,4 +1,14 @@
@media (min-width: 767px) {
.box-articles {
}
}
.box-articles {
h3 {
a {
color: #000;
font-weight: 700;
&:hover {
text-decoration: none;
}
}
}
}

@ -6,11 +6,35 @@
overflow: hidden;
.hero-content {
h1 {
margin-top: 2em;
font-size: 2em;
font-weight: bold;
line-height: 1.25;
a {
color: #000;
font-weight: 700;
&:hover {
text-decoration: none;
}
}
}
h2 {
margin-top: .5rem;
font-size: 1.75em;
font-weight: bold;
line-height: 1.25;
a {
color: #000;
&:hover {
text-decoration: none;
}
}
}
}

@ -3,10 +3,15 @@
background: white;
padding: 10px;
font-family: 'Open Sans', Helvetica, Arial, sans-serif;
font-size: 16px; }
div.vs-blog a, div.vs-blog a:hover {
text-decoration: none;
color: #000000; }
font-size: 15px; }
div.vs-blog .post-content {
color: #555; }
div.vs-blog .post-content a {
text-decoration: underline !important;
color: #555; }
div.vs-blog .post-content a:hover {
text-decoration: underline !important;
color: #000000; }
div.vs-blog p {
color: #444; }
div.vs-blog p a {
@ -96,11 +101,25 @@
div.vs-blog .hero-section .hero-articles {
position: relative;
overflow: hidden; }
div.vs-blog .hero-section .hero-articles .hero-content h1 {
margin-top: 2em;
font-size: 2em;
font-weight: bold;
line-height: 1.25; }
div.vs-blog .hero-section .hero-articles .hero-content h1 a {
color: #000;
font-weight: 700; }
div.vs-blog .hero-section .hero-articles .hero-content h1 a:hover {
text-decoration: none; }
div.vs-blog .hero-section .hero-articles .hero-content h2 {
margin-top: .5rem;
font-size: 1.75em;
font-weight: bold;
line-height: 1.25; }
div.vs-blog .hero-section .hero-articles .hero-content h2 a {
color: #000; }
div.vs-blog .hero-section .hero-articles .hero-content h2 a:hover {
text-decoration: none; }
div.vs-blog .hero-section .hero-articles .tags .tag {
background: rgba(208, 208, 208, 0.3);
color: #fff !important; }
@ -294,5 +313,10 @@
width: 64px; }
div.vs-blog .comment-area .answer-avatar {
width: 64px; }
div.vs-blog .box-articles h3 a {
color: #000;
font-weight: 700; }
div.vs-blog .box-articles h3 a:hover {
text-decoration: none; }
div.vs-blog > .form-group {
margin: 0 !important; }

File diff suppressed because one or more lines are too long

@ -3,7 +3,7 @@ div.vs-blog {
background: white;
padding: 10px;
font-family: 'Open Sans', Helvetica, Arial, sans-serif;
font-size: 16px;
font-size: 15px;
@import "bootstrap-overwrite.scss";
@import "header.scss";
@import "home.scss";

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,3 +1,5 @@
import { ABP } from './common';
export class ListResultDto<T> {
items?: T[];
@ -122,3 +124,95 @@ export class FullAuditedEntityWithUserDto<
super(initialValues);
}
}
export class ExtensibleObject {
extraProperties: ABP.Dictionary<any>;
constructor(initialValues: Partial<ExtensibleObject> = {}) {
for (const key in initialValues) {
if (initialValues.hasOwnProperty(key)) {
this[key] = initialValues[key];
}
}
}
}
export class ExtensibleEntityDto<TKey = string> extends ExtensibleObject {
id: TKey;
constructor(initialValues: Partial<ExtensibleEntityDto<TKey>> = {}) {
super(initialValues);
}
}
export class ExtensibleCreationAuditedEntityDto<TPrimaryKey = string> extends ExtensibleEntityDto<
TPrimaryKey
> {
creationTime: Date | string;
creatorId?: string;
constructor(initialValues: Partial<ExtensibleCreationAuditedEntityDto<TPrimaryKey>> = {}) {
super(initialValues);
}
}
export class ExtensibleAuditedEntityDto<
TPrimaryKey = string
> extends ExtensibleCreationAuditedEntityDto<TPrimaryKey> {
lastModificationTime?: Date | string;
lastModifierId?: string;
constructor(initialValues: Partial<ExtensibleAuditedEntityDto<TPrimaryKey>> = {}) {
super(initialValues);
}
}
export class ExtensibleAuditedEntityWithUserDto<
TPrimaryKey = string,
TUserDto = any
> extends ExtensibleAuditedEntityDto<TPrimaryKey> {
creator: TUserDto;
lastModifier: TUserDto;
constructor(initialValues: Partial<ExtensibleAuditedEntityWithUserDto<TPrimaryKey>> = {}) {
super(initialValues);
}
}
export class ExtensibleCreationAuditedEntityWithUserDto<
TPrimaryKey = string,
TUserDto = any
> extends ExtensibleCreationAuditedEntityDto<TPrimaryKey> {
creator: TUserDto;
constructor(
initialValues: Partial<ExtensibleCreationAuditedEntityWithUserDto<TPrimaryKey>> = {},
) {
super(initialValues);
}
}
export class ExtensibleFullAuditedEntityDto<
TPrimaryKey = string
> extends ExtensibleAuditedEntityDto<TPrimaryKey> {
isDeleted: boolean;
deleterId?: string;
deletionTime: Date | string;
constructor(initialValues: Partial<ExtensibleFullAuditedEntityDto<TPrimaryKey>> = {}) {
super(initialValues);
}
}
export class ExtensibleFullAuditedEntityWithUserDto<
TPrimaryKey = string,
TUserDto = any
> extends ExtensibleFullAuditedEntityDto<TPrimaryKey> {
creator: TUserDto;
lastModifier: TUserDto;
deleter: TUserDto;
constructor(initialValues: Partial<ExtensibleFullAuditedEntityWithUserDto<TPrimaryKey>> = {}) {
super(initialValues);
}
}

@ -1,4 +1,4 @@
import { Injectable, NgZone, Optional, SkipSelf } from '@angular/core';
import { Injectable, Injector, NgZone, Optional, SkipSelf } from '@angular/core';
import { ActivatedRouteSnapshot, Router } from '@angular/router';
import { Actions, ofActionSuccessful, Store } from '@ngxs/store';
import { noop, Observable } from 'rxjs';
@ -27,7 +27,7 @@ export class LocalizationService {
constructor(
private actions: Actions,
private store: Store,
private router: Router,
private injector: Injector,
private ngZone: NgZone,
@Optional()
@SkipSelf()
@ -42,19 +42,16 @@ export class LocalizationService {
this.languageChange.subscribe(({ payload }) => this.registerLocale(payload));
}
setRouteReuse(reuse: ShouldReuseRoute) {
this.router.routeReuseStrategy.shouldReuseRoute = reuse;
}
registerLocale(locale: string) {
const { shouldReuseRoute } = this.router.routeReuseStrategy;
this.setRouteReuse(() => false);
this.router.navigated = false;
const router = this.injector.get(Router);
const { shouldReuseRoute } = router.routeReuseStrategy;
router.routeReuseStrategy.shouldReuseRoute = () => false;
router.navigated = false;
return registerLocale(locale).then(() => {
this.ngZone.run(async () => {
await this.router.navigateByUrl(this.router.url).catch(noop);
this.setRouteReuse(shouldReuseRoute);
await router.navigateByUrl(router.url).catch(noop);
router.routeReuseStrategy.shouldReuseRoute = shouldReuseRoute;
});
});
}

@ -0,0 +1,191 @@
import { FormControl, Validators } from '@angular/forms';
import { AbpValidators, validateRange } from '../validators';
import { validateCreditCard } from '../validators/credit-card.validator';
import { validateRequired } from '../validators/required.validator';
import { validateStringLength } from '../validators/string-length.validator';
import { validateUrl } from '../validators/url.validator';
describe('Validators', () => {
describe('Credit Card Validator', () => {
const error = { creditCard: true };
test.each`
input | expected
${undefined} | ${null}
${null} | ${null}
${''} | ${null}
${'0'} | ${error}
${'5105105105105100' /* Mastercard */} | ${null}
${'5105105105105101' /* Mastercard */} | ${error}
${'5105 1051 0510 5100'} | ${null}
${'5105-1051-0510-5100'} | ${null}
${'5105 - 1051 - 0510 - 5100'} | ${null}
${'4111111111111111' /*Visa*/} | ${null}
${'4111111111111112' /*Visa*/} | ${error}
${'4012888888881881' /* Visa */} | ${null}
${'4012888888881882' /* Visa */} | ${error}
${'4222222222222' /* Visa */} | ${null}
${'4222222222223' /* Visa */} | ${error}
${'378282246310005' /* American Express */} | ${null}
${'378282246310006' /* American Express */} | ${error}
${'6011111111111117' /* Discover */} | ${null}
${'6011111111111118' /* Discover */} | ${error}
`('should return $expected when input is $input', ({ input, expected }) => {
const control = new FormControl(input, [validateCreditCard()]);
control.markAsDirty({ onlySelf: true });
control.updateValueAndValidity({ onlySelf: true, emitEvent: false });
expect(control.errors).toEqual(expected);
});
it('should return null when control is pristine', () => {
const invalidNumber = '5105105105105101';
const control = new FormControl(invalidNumber, [validateCreditCard()]);
// control is not dirty
expect(control.valid).toBe(true);
});
});
describe('Email Validator', () => {
it('should return email validator of Angular', () => {
expect(AbpValidators.emailAddress()).toBe(Validators.email);
});
});
describe('Range Validator', () => {
test.each`
input | options | expected
${null} | ${undefined} | ${null}
${undefined} | ${undefined} | ${null}
${''} | ${undefined} | ${null}
${0} | ${undefined} | ${null}
${Infinity} | ${undefined} | ${null}
${'-1'} | ${{ minimum: 0 }} | ${{ range: { min: 0, max: Infinity } }}
${-1} | ${{ minimum: 0 }} | ${{ range: { min: 0, max: Infinity } }}
${2} | ${{ minimum: 3, maximum: 5 }} | ${{ range: { min: 3, max: 5 } }}
${3} | ${{ minimum: 3, maximum: 5 }} | ${null}
${5} | ${{ minimum: 3, maximum: 5 }} | ${null}
${6} | ${{ minimum: 3, maximum: 5 }} | ${{ range: { min: 3, max: 5 } }}
`(
'should return $expected when input is $input and options are $options',
({ input, options, expected }) => {
const control = new FormControl(input, [validateRange(options)]);
control.markAsDirty({ onlySelf: true });
control.updateValueAndValidity({ onlySelf: true, emitEvent: false });
expect(control.errors).toEqual(expected);
},
);
it('should return null when control is pristine', () => {
const invalidUrl = '';
const control = new FormControl(invalidUrl, [validateRange({ minimum: 3 })]);
// control is not dirty
expect(control.valid).toBe(true);
});
});
describe('Required Validator', () => {
const error = { required: true };
test.each`
input | options | expected
${0} | ${undefined} | ${null}
${false} | ${undefined} | ${null}
${null} | ${undefined} | ${error}
${undefined} | ${undefined} | ${error}
${''} | ${undefined} | ${error}
${''} | ${{}} | ${error}
${''} | ${{ allowEmptyStrings: false }} | ${error}
${''} | ${{ allowEmptyStrings: true }} | ${null}
`(
'should return $expected when input is $input and options are $options',
({ input, options, expected }) => {
const control = new FormControl(input, [validateRequired(options)]);
control.markAsDirty({ onlySelf: true });
control.updateValueAndValidity({ onlySelf: true, emitEvent: false });
expect(control.errors).toEqual(expected);
},
);
it('should return null when control is pristine', () => {
const invalidUrl = '';
const control = new FormControl(invalidUrl, [validateRequired()]);
// control is not dirty
expect(control.valid).toBe(true);
});
});
describe('String Length Validator', () => {
test.each`
input | options | expected
${null} | ${undefined} | ${null}
${undefined} | ${undefined} | ${null}
${''} | ${undefined} | ${null}
${'ab'} | ${{ minimumLength: 3 }} | ${{ minlength: { requiredLength: 3 } }}
${'abp'} | ${{ minimumLength: 3 }} | ${null}
${'abp'} | ${{ maximumLength: 2 }} | ${{ maxlength: { requiredLength: 2 } }}
${'abp'} | ${{ maximumLength: 3 }} | ${null}
`(
'should return $expected when input is $input and options are $options',
({ input, options, expected }) => {
const control = new FormControl(input, [validateStringLength(options)]);
control.markAsDirty({ onlySelf: true });
control.updateValueAndValidity({ onlySelf: true, emitEvent: false });
expect(control.errors).toEqual(expected);
},
);
it('should return null when control is pristine', () => {
const invalidUrl = '';
const control = new FormControl(invalidUrl, [validateStringLength({ minimumLength: 3 })]);
// control is not dirty
expect(control.valid).toBe(true);
});
});
describe('Url Validator', () => {
const error = { url: true };
test.each`
input | expected
${undefined} | ${null}
${null} | ${null}
${''} | ${null}
${'http://x'} | ${null}
${'http:///x'} | ${error}
${'https://x'} | ${null}
${'https:///x'} | ${error}
${'ftp://x'} | ${null}
${'ftp:///x'} | ${error}
${'http://x.com'} | ${null}
${'http://x.photography'} | ${null}
${'http://www.x.org'} | ${null}
${'http://sub.x.gov.tr'} | ${null}
${'x'} | ${error}
${'x.com'} | ${error}
${'www.x.org'} | ${error}
${'sub.x.gov.tr'} | ${error}
`('should return $expected when input is $input', ({ input, expected }) => {
const control = new FormControl(input, [validateUrl()]);
control.markAsDirty({ onlySelf: true });
control.updateValueAndValidity({ onlySelf: true, emitEvent: false });
expect(control.errors).toEqual(expected);
});
it('should return null when control is pristine', () => {
const invalidUrl = 'x';
const control = new FormControl(invalidUrl, [validateUrl()]);
// control is not dirty
expect(control.valid).toBe(true);
});
});
});

@ -0,0 +1,34 @@
import { AbstractControl, ValidatorFn } from '@angular/forms';
export interface CreditCardError {
creditCard: true;
}
export function validateCreditCard(): ValidatorFn {
return (control: AbstractControl): CreditCardError | null => {
if (control.pristine) return null;
if (['', null, undefined].indexOf(control.value) > -1) return null;
return isValidCreditCard(String(control.value)) ? null : { creditCard: true };
};
}
function isValidCreditCard(value: string): boolean {
value = value.replace(/[ -]/g, '');
if (!/^[0-9]{13,19}$/.test(value)) return false;
let checksum = 0;
let multiplier = 1;
for (let i = value.length; i > 0; i--) {
const digit = Number(value[i - 1]) * multiplier;
/* tslint:disable-next-line:no-bitwise */
checksum += (digit % 10) + ~~(digit / 10);
multiplier = (multiplier * 2) % 3;
}
return checksum % 10 === 0;
}

@ -0,0 +1,20 @@
import { Validators } from '@angular/forms';
import { validateCreditCard } from './credit-card.validator';
import { validateRange } from './range.validator';
import { validateRequired } from './required.validator';
import { validateStringLength } from './string-length.validator';
import { validateUrl } from './url.validator';
export * from './credit-card.validator';
export * from './range.validator';
export * from './required.validator';
export * from './string-length.validator';
export * from './url.validator';
export const AbpValidators = {
creditCard: validateCreditCard,
emailAddress: () => Validators.email,
range: validateRange,
required: validateRequired,
stringLength: validateStringLength,
url: validateUrl,
};

@ -0,0 +1,32 @@
import { AbstractControl, ValidatorFn } from '@angular/forms';
export interface RangeError {
range: {
max: number;
min: number;
};
}
export interface RangeOptions {
maximum?: number;
minimum?: number;
}
export function validateRange({ maximum = Infinity, minimum = 0 }: RangeOptions = {}): ValidatorFn {
return (control: AbstractControl): RangeError | null => {
if (control.pristine) return null;
if (['', null, undefined].indexOf(control.value) > -1) return null;
const value = Number(control.value);
return getMinError(value, minimum, maximum) || getMaxError(value, maximum, minimum);
};
}
function getMaxError(value: number, max: number, min: number): RangeError {
return value > max ? { range: { max, min } } : null;
}
function getMinError(value: number, min: number, max: number): RangeError {
return value < min ? { range: { min, max } } : null;
}

@ -0,0 +1,25 @@
import { AbstractControl, ValidatorFn } from '@angular/forms';
export interface RequiredError {
required: true;
}
export interface RequiredOptions {
allowEmptyStrings?: boolean;
}
export function validateRequired({ allowEmptyStrings }: RequiredOptions = {}): ValidatorFn {
return (control: AbstractControl): RequiredError | null => {
return control.pristine || isValidRequired(control.value, allowEmptyStrings)
? null
: { required: true };
};
}
function isValidRequired(value: any, allowEmptyStrings: boolean): boolean {
if (value || value === 0 || value === false) return true;
if (allowEmptyStrings && value === '') return true;
return false;
}

@ -0,0 +1,38 @@
import { AbstractControl, ValidatorFn } from '@angular/forms';
export interface StringLengthError {
maxlength?: {
requiredLength: number;
};
minlength?: {
requiredLength: number;
};
}
export interface StringLengthOptions {
maximumLength?: number;
minimumLength?: number;
}
export function validateStringLength({
maximumLength = Infinity,
minimumLength = 0,
}: StringLengthOptions = {}): ValidatorFn {
return (control: AbstractControl): StringLengthError | null => {
if (control.pristine) return null;
if (['', null, undefined].indexOf(control.value) > -1) return null;
const value = String(control.value);
return getMinLengthError(value, minimumLength) || getMaxLengthError(value, maximumLength);
};
}
function getMaxLengthError(value: string, requiredLength: number): StringLengthError {
return value.length > requiredLength ? { maxlength: { requiredLength } } : null;
}
function getMinLengthError(value: string, requiredLength: number): StringLengthError {
return value.length < requiredLength ? { minlength: { requiredLength } } : null;
}

@ -0,0 +1,25 @@
import { AbstractControl, ValidatorFn } from '@angular/forms';
export interface UrlError {
url: true;
}
export function validateUrl(): ValidatorFn {
return (control: AbstractControl): UrlError | null => {
if (control.pristine) return null;
if (['', null, undefined].indexOf(control.value) > -1) return null;
return isValidUrl(control.value) ? null : { url: true };
};
}
function isValidUrl(value: string): boolean {
if (/^http(s)?:\/\/[^/]/.test(value) || /^ftp:\/\/[^/]/.test(value)) {
const a = document.createElement('a');
a.href = value;
return !!a.host;
}
return false;
}

@ -20,3 +20,4 @@ export * from './lib/states';
export * from './lib/strategies';
export * from './lib/tokens';
export * from './lib/utils';
export * from './lib/validators';

@ -7,12 +7,12 @@ import { NgxsModule } from '@ngxs/store';
import { AccountLayoutComponent } from './components/account-layout/account-layout.component';
import { ApplicationLayoutComponent } from './components/application-layout/application-layout.component';
import { EmptyLayoutComponent } from './components/empty-layout/empty-layout.component';
import { LayoutState } from './states/layout.state';
import { ValidationErrorComponent } from './components/validation-error/validation-error.component';
import { InitialService } from './services/initial.service';
import { LogoComponent } from './components/logo/logo.component';
import { RoutesComponent } from './components/routes/routes.component';
import { NavItemsComponent } from './components/nav-items/nav-items.component';
import { RoutesComponent } from './components/routes/routes.component';
import { ValidationErrorComponent } from './components/validation-error/validation-error.component';
import { InitialService } from './services/initial.service';
import { LayoutState } from './states/layout.state';
export const LAYOUTS = [ApplicationLayoutComponent, AccountLayoutComponent, EmptyLayoutComponent];
@ -34,15 +34,19 @@ export const LAYOUTS = [ApplicationLayoutComponent, AccountLayoutComponent, Empt
NgxValidateCoreModule.forRoot({
targetSelector: '.form-group',
blueprints: {
email: 'AbpAccount::ThisFieldIsNotAValidEmailAddress.',
max: 'AbpAccount::ThisFieldMustBeBetween{0}And{1}[{{ min }},{{ max }}]',
creditCard: 'AbpValidation::ThisFieldIsNotAValidCreditCardNumber.',
email: 'AbpValidation::ThisFieldIsNotAValidEmailAddress.',
max: 'AbpValidation::ThisFieldMustBeBetween{0}And{1}[{{ min }},{{ max }}]',
maxlength:
'AbpAccount::ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}[{{ requiredLength }}]',
min: 'AbpAccount::ThisFieldMustBeBetween{0}And{1}[{{ min }},{{ max }}]',
'AbpValidation::ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}[{{ requiredLength }}]',
min: 'AbpValidation::ThisFieldMustBeBetween{0}And{1}[{{ min }},{{ max }}]',
minlength:
'AbpAccount::ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}[{{ requiredLength }}]',
required: 'AbpAccount::ThisFieldIsRequired.',
'AbpValidation::ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}[{{ requiredLength }}]',
ngbDate: 'AbpValidation::ThisFieldIsNotValid.',
passwordMismatch: 'AbpIdentity::Identity.PasswordConfirmationFailed',
range: 'AbpValidation::ThisFieldMustBeBetween{0}And{1}[{{ min }},{{ max }}]',
required: 'AbpValidation::ThisFieldIsRequired.',
url: 'AbpValidation::ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl',
},
errorTemplate: ValidationErrorComponent,
}),

@ -416,6 +416,20 @@
"resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
"integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ=="
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
@ -481,6 +495,11 @@
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"copy-descriptor": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
@ -601,6 +620,11 @@
"universalify": "^0.1.0"
}
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"get-stream": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz",
@ -609,6 +633,19 @@
"pump": "^3.0.0"
}
},
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"globals": {
"version": "9.18.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
@ -627,11 +664,25 @@
"ansi-regex": "^2.0.0"
}
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"info-symbol": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/info-symbol/-/info-symbol-0.1.0.tgz",
"integrity": "sha1-J4QdcoZ920JCzWEtecEGM4gcang="
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
@ -838,6 +889,14 @@
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
@ -972,6 +1031,11 @@
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz",
"integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw=="
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"path-key": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",

@ -8,6 +8,7 @@
"build:prod": "ts-node -r tsconfig-paths/register prod-build.ts",
"publish-packages": "ts-node -r tsconfig-paths/register publish.ts",
"install-new-dependencies": "ts-node -r tsconfig-paths/register install-new-dependencies.ts",
"replace-with-tilde": "ts-node -r tsconfig-paths/register replace-with-tilde.ts",
"sync": "ts-node -r tsconfig-paths/register sync.ts"
},
"author": "",
@ -16,6 +17,7 @@
"commander": "^4.1.1",
"execa": "^2.0.3",
"fs-extra": "^8.1.0",
"glob": "^7.1.6",
"prompt-confirm": "^2.0.4"
},
"devDependencies": {

@ -42,6 +42,8 @@ const publish = async () => {
await fse.rename('../lerna.json', '../lerna.version.json');
await execa('yarn', ['replace-with-tilde']);
await execa('yarn', ['build', '--noInstall'], { stdout: 'inherit' });
await fse.rename('../lerna.publish.json', '../lerna.json');

@ -0,0 +1,29 @@
import execa from 'execa';
import fse from 'fs-extra';
import glob from 'glob';
async function replace(filePath: string) {
const pkg = await fse.readJson(filePath);
const { dependencies } = pkg;
if (!dependencies) return;
Object.keys(dependencies).forEach(key => {
if (key.includes('@abp/') && key !== '@abp/utils') {
dependencies[key] = dependencies[key].replace('^', '~');
}
});
await fse.writeJson(filePath, { ...pkg, dependencies }, { spaces: 2 });
}
glob('../packages/**/package.json', {}, (er, files) => {
files.forEach(path => {
if (path.includes('node_modules')) {
return;
}
replace(path);
});
});

File diff suppressed because it is too large Load Diff

@ -1,7 +1,8 @@
module.exports = {
mappings: {
"@node_modules/bootstrap/dist/css/bootstrap.css": "@libs/bootstrap/css/",
"@node_modules/@laylazi/bootstrap-rtl/dist/css/bootstrap-rtl.css": "@libs/bootstrap/css/",
"@node_modules/bootstrap/dist/js/bootstrap.bundle.js": "@libs/bootstrap/js/"
"@node_modules//bootstrap/dist/css/bootstrap.css*": "@libs/bootstrap/css/",
"@node_modules//bootstrap/dist/css/bootstrap.min.css*": "@libs/bootstrap/css/",
"@node_modules/@laylazi/bootstrap-rtl/dist/css/bootstrap-rtl*": "@libs/bootstrap/css/",
"@node_modules/bootstrap/dist/js/bootstrap.bundle*": "@libs/bootstrap/js/"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -18,4 +18,7 @@ export const environment = {
url: 'https://localhost:44305',
},
},
localization: {
defaultResourceName: 'MyProjectName',
},
};

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save