mirror of https://github.com/abpframework/abp.git
committed by
GitHub
69 changed files with 632 additions and 110 deletions
@ -0,0 +1,80 @@ |
|||||
|
# Caps Lock Directive |
||||
|
|
||||
|
In password inputs, You may want to show if Caps Lock is on. To make this even easier, you can use the `TrackCapsLockDirective` which has been exposed by the `@abp/ng.core` package. |
||||
|
|
||||
|
|
||||
|
## Getting Started |
||||
|
|
||||
|
`TrackCapsLockDirective` is standalone. In order to use the `TrackCapsLockDirective` in an HTML template, import it to related module or your standalone component: |
||||
|
|
||||
|
**Importing to NgModule** |
||||
|
```ts |
||||
|
import { TrackCapsLockDirective } from '@abp/ng.core'; |
||||
|
|
||||
|
@NgModule({ |
||||
|
//... |
||||
|
declarations: [ |
||||
|
..., |
||||
|
TestComponent |
||||
|
], |
||||
|
imports: [ |
||||
|
..., |
||||
|
TrackCapsLockDirective |
||||
|
], |
||||
|
}) |
||||
|
export class MyFeatureModule {} |
||||
|
``` |
||||
|
|
||||
|
## Usage |
||||
|
|
||||
|
The `TrackCapsLockDirective` is very easy to use. The directive's selector is **`abpCapsLock`**. By adding the `abpCapsLock` event to an element, you can track the status of Caps Lock. You can use this to warn user. |
||||
|
|
||||
|
See an example usage: |
||||
|
|
||||
|
**NgModule Component usage** |
||||
|
```ts |
||||
|
@Component({ |
||||
|
selector: 'test-component', |
||||
|
template: ` |
||||
|
<div class="d-flex flex-column"> |
||||
|
<label>Password</label> |
||||
|
<input (abpCapsLock)="capsLock = $event"/> |
||||
|
<i *ngIf="capsLock">icon</i> |
||||
|
</div> |
||||
|
` |
||||
|
}) |
||||
|
export class TestComponent{ |
||||
|
capsLock = false; |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
**Standalone Component usage** |
||||
|
```ts |
||||
|
import { TrackCapsLockDirective } from '@abp/ng.core' |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'standalone-component', |
||||
|
standalone: true, |
||||
|
template: ` |
||||
|
<div class="d-flex flex-column"> |
||||
|
<label>Password</label> |
||||
|
<input (abpCapsLock)="capsLock = $event"/> |
||||
|
<i *ngIf="capsLock">icon</i> |
||||
|
</div> |
||||
|
`, |
||||
|
imports: [TrackCapsLockDirective] |
||||
|
}) |
||||
|
export class StandaloneComponent{ |
||||
|
capsLock = false; |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
The `abpCapsLock` event has been added to the `<input>` element. Press Caps Lock to activate the `TrackCapsLockDirective`. |
||||
|
|
||||
|
See the result: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
To see Caps Lock icon press Caps Lock. |
||||
|
|
||||
|
 |
||||
@ -0,0 +1,77 @@ |
|||||
|
# Show Password Directive |
||||
|
|
||||
|
In password input, text can be shown easily via changing input type attribute to `text`. To make this even easier, you can use the `ShowPasswordDirective` which has been exposed by the `@abp/ng.core` package. |
||||
|
|
||||
|
|
||||
|
## Getting Started |
||||
|
`ShowPasswordDirective` is standalone. In order to use the `ShowPasswordDirective` in an HTML template, import it to related module or your standalone component: |
||||
|
|
||||
|
**Importing to NgModule** |
||||
|
```ts |
||||
|
import { ShowPasswordDirective } from '@abp/ng.core'; |
||||
|
|
||||
|
@NgModule({ |
||||
|
//... |
||||
|
declarations: [ |
||||
|
..., |
||||
|
TestComponent |
||||
|
], |
||||
|
imports: [ |
||||
|
..., |
||||
|
ShowPasswordDirective |
||||
|
], |
||||
|
}) |
||||
|
export class MyFeatureModule {} |
||||
|
``` |
||||
|
|
||||
|
## Usage |
||||
|
|
||||
|
The `ShowPasswordDirective` is very easy to use. The directive's selector is **`abpShowPassword`**. By adding the `abpShowPassword` attribute to an input element, you can activate the `ShowPasswordDirective` for the input element. |
||||
|
|
||||
|
See an example usage: |
||||
|
|
||||
|
**NgModule Component usage** |
||||
|
```ts |
||||
|
@Component({ |
||||
|
selector: 'test-component', |
||||
|
template: ` |
||||
|
<div class="d-flex flex-column"> |
||||
|
<label>Password</label> |
||||
|
<input [abpShowPassword]="showPassword"/> |
||||
|
<i (click)="showPassword = !showPassword">icon</i> |
||||
|
</div> |
||||
|
` |
||||
|
}) |
||||
|
export class TestComponent{ |
||||
|
showPassword = false; |
||||
|
} |
||||
|
``` |
||||
|
**Standalone Component usage** |
||||
|
```ts |
||||
|
import { ShowPasswordDirective } from '@abp/ng.core'; |
||||
|
@Component({ |
||||
|
selector: 'standalone-component', |
||||
|
standalone: true, |
||||
|
template: ` |
||||
|
<div class="d-flex flex-column"> |
||||
|
<label>Password</label> |
||||
|
<input [abpShowPassword]="showPassword"/> |
||||
|
<i (click)="showPassword = !showPassword">icon</i> |
||||
|
</div> |
||||
|
`, |
||||
|
imports: [ShowPasswordDirective] |
||||
|
}) |
||||
|
export class StandaloneComponent{ |
||||
|
showPassword = false; |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
The `abpShowPassword` attribute has been added to the `<input>` element. Click icon to activate the `ShowPasswordDirective`. |
||||
|
|
||||
|
See the result: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
To see password input click icon. |
||||
|
|
||||
|
 |
||||
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
@ -1,8 +0,0 @@ |
|||||
using System.Threading.Tasks; |
|
||||
|
|
||||
namespace Volo.Abp.Http.Client.IdentityModel.MauiBlazor; |
|
||||
|
|
||||
public interface IAbpMauiAccessTokenProvider |
|
||||
{ |
|
||||
Task<string> GetAccessTokenAsync(); |
|
||||
} |
|
||||
@ -0,0 +1,14 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Http.Client.Authentication; |
||||
|
|
||||
|
namespace Volo.Abp.Http.Client.IdentityModel.MauiBlazor; |
||||
|
|
||||
|
[Dependency(ReplaceServices = true)] |
||||
|
public class MauiBlazorAbpAccessTokenProvider : IAbpAccessTokenProvider, ITransientDependency |
||||
|
{ |
||||
|
public virtual Task<string> GetTokenAsync() |
||||
|
{ |
||||
|
return Task.FromResult(null as string); |
||||
|
} |
||||
|
} |
||||
@ -1,13 +0,0 @@ |
|||||
using System.Threading.Tasks; |
|
||||
using Volo.Abp.DependencyInjection; |
|
||||
|
|
||||
namespace Volo.Abp.Http.Client.IdentityModel.MauiBlazor; |
|
||||
|
|
||||
[Dependency(TryRegister = true)] |
|
||||
public class NullAbpMauiAccessTokenProvider : IAbpMauiAccessTokenProvider |
|
||||
{ |
|
||||
public Task<string> GetAccessTokenAsync() |
|
||||
{ |
|
||||
return Task.FromResult(null as string); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,29 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Authentication; |
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Http.Client.Authentication; |
||||
|
|
||||
|
namespace Volo.Abp.Http.Client.IdentityModel.Web; |
||||
|
|
||||
|
[Dependency(ReplaceServices = true)] |
||||
|
public class HttpContextAbpAccessTokenProvider : IAbpAccessTokenProvider, ITransientDependency |
||||
|
{ |
||||
|
protected IHttpContextAccessor HttpContextAccessor { get; } |
||||
|
|
||||
|
public HttpContextAbpAccessTokenProvider(IHttpContextAccessor httpContextAccessor) |
||||
|
{ |
||||
|
HttpContextAccessor = httpContextAccessor; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<string> GetTokenAsync() |
||||
|
{ |
||||
|
var httpContext = HttpContextAccessor?.HttpContext; |
||||
|
if (httpContext == null) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
return await httpContext.GetTokenAsync("access_token"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Components.WebAssembly.Authentication; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Http.Client.Authentication; |
||||
|
|
||||
|
namespace Volo.Abp.Http.Client.IdentityModel.WebAssembly; |
||||
|
|
||||
|
[Dependency(ReplaceServices = true)] |
||||
|
public class WebAssemblyAbpAccessTokenProvider : IAbpAccessTokenProvider, ITransientDependency |
||||
|
{ |
||||
|
protected IAccessTokenProvider AccessTokenProvider { get; } |
||||
|
|
||||
|
public WebAssemblyAbpAccessTokenProvider(IAccessTokenProvider accessTokenProvider) |
||||
|
{ |
||||
|
AccessTokenProvider = accessTokenProvider; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<string> GetTokenAsync() |
||||
|
{ |
||||
|
if (AccessTokenProvider == null) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var result = await AccessTokenProvider.RequestAccessToken(); |
||||
|
if (result.Status != AccessTokenResultStatus.Success) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
result.TryGetToken(out var token); |
||||
|
return token.Value; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.Http.Client.Authentication; |
||||
|
|
||||
|
public interface IAbpAccessTokenProvider |
||||
|
{ |
||||
|
Task<string> GetTokenAsync(); |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.Http.Client.Authentication; |
||||
|
|
||||
|
public class NullAbpAccessTokenProvider : IAbpAccessTokenProvider, ITransientDependency |
||||
|
{ |
||||
|
public Task<string> GetTokenAsync() |
||||
|
{ |
||||
|
return Task.FromResult(null as string); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Identity; |
||||
|
using Microsoft.Extensions.Localization; |
||||
|
using Volo.Abp.Identity.Localization; |
||||
|
|
||||
|
namespace Volo.Abp.Identity |
||||
|
{ |
||||
|
public class AbpIdentityUserValidator : IUserValidator<IdentityUser> |
||||
|
{ |
||||
|
protected IStringLocalizer<IdentityResource> Localizer { get; } |
||||
|
|
||||
|
public AbpIdentityUserValidator(IStringLocalizer<IdentityResource> localizer) |
||||
|
{ |
||||
|
Localizer = localizer; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<IdentityResult> ValidateAsync(UserManager<IdentityUser> manager, IdentityUser user) |
||||
|
{ |
||||
|
var describer = new IdentityErrorDescriber(); |
||||
|
|
||||
|
Check.NotNull(manager, nameof(manager)); |
||||
|
Check.NotNull(user, nameof(user)); |
||||
|
|
||||
|
var errors = new List<IdentityError>(); |
||||
|
|
||||
|
var userName = await manager.GetUserNameAsync(user); |
||||
|
if (userName == null) |
||||
|
{ |
||||
|
errors.Add(describer.InvalidUserName(null)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var owner = await manager.FindByEmailAsync(userName); |
||||
|
if (owner != null && !string.Equals(await manager.GetUserIdAsync(owner), await manager.GetUserIdAsync(user))) |
||||
|
{ |
||||
|
errors.Add(new IdentityError |
||||
|
{ |
||||
|
Code = "InvalidUserName", |
||||
|
Description = Localizer["InvalidUserName", userName] |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return errors.Count > 0 ? IdentityResult.Failed(errors.ToArray()) : IdentityResult.Success; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.Localization; |
||||
|
using Shouldly; |
||||
|
using Volo.Abp.Identity.Localization; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.Identity.AspNetCore; |
||||
|
|
||||
|
public class AbpIdentityUserValidator_Tests : AbpIdentityAspNetCoreTestBase |
||||
|
{ |
||||
|
private readonly IdentityUserManager _identityUserManager; |
||||
|
private readonly IStringLocalizer<IdentityResource> Localizer; |
||||
|
|
||||
|
public AbpIdentityUserValidator_Tests() |
||||
|
{ |
||||
|
_identityUserManager = GetRequiredService<IdentityUserManager>(); |
||||
|
Localizer = GetRequiredService<IStringLocalizer<IdentityResource>>(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Can_Not_Use_Another_Users_Email_As_Your_Username_Test() |
||||
|
{ |
||||
|
var user1 = new IdentityUser(Guid.NewGuid(), "user1", "user1@volosoft.com"); |
||||
|
var identityResult = await _identityUserManager.CreateAsync(user1); |
||||
|
identityResult.Succeeded.ShouldBeTrue(); |
||||
|
|
||||
|
var user2 = new IdentityUser(Guid.NewGuid(), "user1@volosoft.com", "user2@volosoft.com"); |
||||
|
identityResult = await _identityUserManager.CreateAsync(user2); |
||||
|
identityResult.Succeeded.ShouldBeFalse(); |
||||
|
identityResult.Errors.Count().ShouldBe(1); |
||||
|
identityResult.Errors.First().Code.ShouldBe("InvalidUserName"); |
||||
|
identityResult.Errors.First().Description.ShouldBe(Localizer["InvalidUserName", "user1@volosoft.com"]); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,57 @@ |
|||||
|
import { Component, DebugElement } from '@angular/core' |
||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing' |
||||
|
import { TrackCapsLockDirective } from '../directives'; |
||||
|
import { By } from '@angular/platform-browser'; |
||||
|
|
||||
|
@Component({ |
||||
|
standalone:true, |
||||
|
template: ` |
||||
|
<input (abpCapsLock)="capsLock = $event" /> |
||||
|
`,
|
||||
|
imports:[TrackCapsLockDirective] |
||||
|
}) |
||||
|
class TestComponent { |
||||
|
capsLock:boolean = false |
||||
|
} |
||||
|
|
||||
|
describe('TrackCapsLockDirective',()=>{ |
||||
|
let fixture: ComponentFixture<TestComponent>;; |
||||
|
let des : DebugElement[]; |
||||
|
|
||||
|
beforeEach(()=>{ |
||||
|
fixture = TestBed.configureTestingModule({ |
||||
|
imports: [ TestComponent ] |
||||
|
}).createComponent(TestComponent); |
||||
|
|
||||
|
fixture.detectChanges(); |
||||
|
|
||||
|
des = fixture.debugElement.queryAll(By.directive(TrackCapsLockDirective)); |
||||
|
}); |
||||
|
|
||||
|
test.each(['keydown','keyup'])('is %p works when press capslock and is emit status', (eventName) => { |
||||
|
const event = new KeyboardEvent(eventName, { |
||||
|
key: 'CapsLock', |
||||
|
modifierCapsLock: true |
||||
|
}); |
||||
|
window.dispatchEvent(event); |
||||
|
fixture.detectChanges(); |
||||
|
expect(fixture.componentInstance.capsLock).toBe(true) |
||||
|
}); |
||||
|
|
||||
|
test.each(['keydown','keyup'])('is %p detect the change capslock is emit status', (eventName) => { |
||||
|
const trueEvent = new KeyboardEvent(eventName, { |
||||
|
key: 'CapsLock', |
||||
|
modifierCapsLock: true |
||||
|
}); |
||||
|
window.dispatchEvent(trueEvent); |
||||
|
fixture.detectChanges(); |
||||
|
expect(fixture.componentInstance.capsLock).toBe(true) |
||||
|
const falseEvent = new KeyboardEvent(eventName, { |
||||
|
key: 'CapsLock', |
||||
|
modifierCapsLock: false |
||||
|
}); |
||||
|
window.dispatchEvent(falseEvent); |
||||
|
fixture.detectChanges(); |
||||
|
expect(fixture.componentInstance.capsLock).toBe(false) |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,55 @@ |
|||||
|
import { Component, DebugElement } from '@angular/core' |
||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing' |
||||
|
import { ShowPasswordDirective } from '../directives'; |
||||
|
import { By } from '@angular/platform-browser'; |
||||
|
|
||||
|
@Component({ |
||||
|
standalone:true, |
||||
|
template: ` |
||||
|
<input [abpShowPassword]="true"> |
||||
|
<input [abpShowPassword]="false"> |
||||
|
<input /> |
||||
|
<input [abpShowPassword]="showPassword" />`,
|
||||
|
imports:[ShowPasswordDirective] |
||||
|
}) |
||||
|
class TestComponent { |
||||
|
showPassword:boolean = false |
||||
|
} |
||||
|
|
||||
|
describe('ShowPasswordDirective',()=>{ |
||||
|
let fixture: ComponentFixture<TestComponent>;; |
||||
|
let des : DebugElement[]; |
||||
|
let desAll : DebugElement[]; |
||||
|
let bareInput; |
||||
|
|
||||
|
beforeEach(()=>{ |
||||
|
fixture = TestBed.configureTestingModule({ |
||||
|
imports: [ TestComponent ] |
||||
|
}).createComponent(TestComponent) |
||||
|
|
||||
|
fixture.detectChanges(); |
||||
|
|
||||
|
des = fixture.debugElement.queryAll(By.directive(ShowPasswordDirective)); |
||||
|
|
||||
|
desAll = fixture.debugElement.queryAll(By.all()); |
||||
|
|
||||
|
bareInput = fixture.debugElement.query(By.css('input:not([abpShowPassword])')); |
||||
|
}) |
||||
|
|
||||
|
it('should have three input has ShowPasswordDirective elements', () => { |
||||
|
expect(des.length).toBe(3); |
||||
|
}); |
||||
|
|
||||
|
test.each([[0,'text'],[1,'password'],[2,'text'],[3,'password']])('%p. input type must be %p)', (index,inpType) => { |
||||
|
const inputType = desAll[index].nativeElement.type; |
||||
|
expect(inputType).toBe(inpType); |
||||
|
}); |
||||
|
|
||||
|
it('should have three input has ShowPasswordDirective elements', () => { |
||||
|
let input = des[2].nativeElement |
||||
|
expect(input.type).toBe('password') |
||||
|
fixture.componentInstance.showPassword = true |
||||
|
fixture.detectChanges() |
||||
|
expect(input.type).toBe('text') |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,27 @@ |
|||||
|
import { InjectionToken, inject } from '@angular/core'; |
||||
|
import { LocalizationService } from '../services'; |
||||
|
|
||||
|
export const SORT_COMPARE_FUNC = new InjectionToken< 0 | 1 | -1 >('SORT_COMPARE_FUNC'); |
||||
|
|
||||
|
export function compareFuncFactory() { |
||||
|
const localizationService = inject(LocalizationService) |
||||
|
const fn = (a,b) => { |
||||
|
const aName = localizationService.instant(a.name); |
||||
|
const bName = localizationService.instant(b.name); |
||||
|
const aNumber = a.order; |
||||
|
const bNumber = b.order; |
||||
|
|
||||
|
if (!Number.isInteger(aNumber)) return 1; |
||||
|
if (!Number.isInteger(bNumber)) return -1; |
||||
|
|
||||
|
if (aNumber > bNumber) return 1 |
||||
|
if (aNumber < bNumber) return -1 |
||||
|
|
||||
|
if ( aName > bName ) return 1; |
||||
|
if ( aName < bName ) return -1; |
||||
|
|
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
return fn |
||||
|
} |
||||
Loading…
Reference in new issue