mirror of https://github.com/abpframework/abp
commit
81ea8f248c
@ -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 |
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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