mirror of https://github.com/abpframework/abp.git
99 changed files with 4802 additions and 561 deletions
@ -0,0 +1,144 @@ |
|||
name: Auto-merge forward |
|||
|
|||
# Push to a rel-x.y branch opens a merge PR into the next newer rel-* line, |
|||
# or into dev when this line is the newest. Merge of that PR retriggers the |
|||
# next hop, so a bug-fix on rel-1.0 flows rel-1.0 -> rel-1.1 -> ... -> dev. |
|||
on: |
|||
push: |
|||
branches: |
|||
- 'rel-*' |
|||
workflow_dispatch: |
|||
|
|||
concurrency: |
|||
group: auto-merge-forward-${{ github.ref_name }} |
|||
cancel-in-progress: false |
|||
|
|||
permissions: |
|||
contents: read |
|||
|
|||
jobs: |
|||
forward: |
|||
runs-on: ubuntu-latest |
|||
permissions: |
|||
contents: write |
|||
pull-requests: write |
|||
steps: |
|||
- uses: actions/checkout@v4 |
|||
with: |
|||
fetch-depth: 0 |
|||
|
|||
- name: Resolve forward target |
|||
id: target |
|||
run: | |
|||
set -euo pipefail |
|||
SOURCE="${GITHUB_REF_NAME}" |
|||
if [[ ! "$SOURCE" =~ ^rel-[0-9]+\.[0-9]+$ ]]; then |
|||
echo "Not a rel-x.y branch ($SOURCE); skipping." |
|||
echo "skip=true" >> "$GITHUB_OUTPUT" |
|||
exit 0 |
|||
fi |
|||
|
|||
git fetch origin --prune |
|||
|
|||
mapfile -t RELS < <( |
|||
git ls-remote --heads origin 'rel-*' \ |
|||
| awk '{print $2}' \ |
|||
| sed 's|refs/heads/||' \ |
|||
| grep -E '^rel-[0-9]+\.[0-9]+$' \ |
|||
| sort -t. -k1.5,1n -k2,2n |
|||
) |
|||
|
|||
TARGET="dev" |
|||
found=0 |
|||
for branch in "${RELS[@]}"; do |
|||
if [[ "$found" -eq 1 ]]; then |
|||
TARGET="$branch" |
|||
break |
|||
fi |
|||
if [[ "$branch" == "$SOURCE" ]]; then |
|||
found=1 |
|||
fi |
|||
done |
|||
|
|||
if [[ "$found" -eq 0 ]]; then |
|||
echo "::error::Source branch $SOURCE was not listed among origin rel-* heads." |
|||
exit 1 |
|||
fi |
|||
|
|||
if ! git rev-parse --verify "origin/$TARGET" >/dev/null 2>&1; then |
|||
echo "::error::Target branch origin/$TARGET does not exist." |
|||
exit 1 |
|||
fi |
|||
|
|||
if git merge-base --is-ancestor "origin/$SOURCE" "origin/$TARGET"; then |
|||
echo "origin/$SOURCE is already an ancestor of origin/$TARGET; nothing to forward." |
|||
echo "skip=true" >> "$GITHUB_OUTPUT" |
|||
exit 0 |
|||
fi |
|||
|
|||
echo "skip=false" >> "$GITHUB_OUTPUT" |
|||
echo "source=$SOURCE" >> "$GITHUB_OUTPUT" |
|||
echo "target=$TARGET" >> "$GITHUB_OUTPUT" |
|||
echo "Auto-merge forward: $SOURCE -> $TARGET" |
|||
|
|||
- name: Merge into forward branch |
|||
if: steps.target.outputs.skip != 'true' |
|||
id: merge |
|||
run: | |
|||
set -euo pipefail |
|||
SOURCE="${{ steps.target.outputs.source }}" |
|||
TARGET="${{ steps.target.outputs.target }}" |
|||
FORWARD_BRANCH="auto-merge-forward/${SOURCE}-to-${TARGET}-${{ github.run_number }}" |
|||
|
|||
git config user.name "github-actions[bot]" |
|||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com" |
|||
|
|||
git checkout -B "$FORWARD_BRANCH" "origin/$TARGET" |
|||
if git merge --no-edit "origin/$SOURCE"; then |
|||
echo "conflict=false" >> "$GITHUB_OUTPUT" |
|||
else |
|||
git merge --abort |
|||
git checkout -B "$FORWARD_BRANCH" "origin/$SOURCE" |
|||
echo "conflict=true" >> "$GITHUB_OUTPUT" |
|||
echo "::warning::Merge conflict forwarding ${SOURCE} to ${TARGET}. PR left open for manual resolution." |
|||
fi |
|||
|
|||
git push origin "$FORWARD_BRANCH" |
|||
echo "branch=$FORWARD_BRANCH" >> "$GITHUB_OUTPUT" |
|||
|
|||
- name: Create pull request |
|||
if: steps.target.outputs.skip != 'true' |
|||
id: pr |
|||
env: |
|||
GH_TOKEN: ${{ github.token }} |
|||
run: | |
|||
set -euo pipefail |
|||
SOURCE="${{ steps.target.outputs.source }}" |
|||
TARGET="${{ steps.target.outputs.target }}" |
|||
FORWARD_BRANCH="${{ steps.merge.outputs.branch }}" |
|||
CONFLICT="${{ steps.merge.outputs.conflict }}" |
|||
|
|||
BODY="Automated forward merge of \`${SOURCE}\` into \`${TARGET}\`." |
|||
if [[ "$CONFLICT" == "true" ]]; then |
|||
BODY+=$'\n\n**Merge conflict:** this branch is \`${SOURCE}\` as-is. Resolve against \`${TARGET}\` before merging.' |
|||
fi |
|||
|
|||
URL="$(gh pr create \ |
|||
--base "$TARGET" \ |
|||
--head "$FORWARD_BRANCH" \ |
|||
--title "Auto-merge forward ${SOURCE} → ${TARGET}" \ |
|||
--body "$BODY")" |
|||
echo "url=$URL" >> "$GITHUB_OUTPUT" |
|||
echo "Created $URL" |
|||
|
|||
# BOT_SECRET, not github.token: a merge performed with the default token produces a push |
|||
# that triggers no workflow, which would stop the chain at the first hop. |
|||
- name: Approve and auto-merge |
|||
if: steps.target.outputs.skip != 'true' && steps.merge.outputs.conflict != 'true' |
|||
env: |
|||
GH_TOKEN: ${{ secrets.BOT_SECRET }} |
|||
run: | |
|||
set -euo pipefail |
|||
FORWARD_BRANCH="${{ steps.merge.outputs.branch }}" |
|||
gh pr review "$FORWARD_BRANCH" --approve |
|||
gh pr merge "$FORWARD_BRANCH" --merge --auto --delete-branch |
|||
@ -1,37 +0,0 @@ |
|||
name: Merge branch dev with rel-10.7 |
|||
on: |
|||
push: |
|||
branches: |
|||
- rel-10.7 |
|||
permissions: |
|||
contents: read |
|||
|
|||
jobs: |
|||
merge-dev-with-rel-10-7: |
|||
permissions: |
|||
contents: write # for peter-evans/create-pull-request to create branch |
|||
pull-requests: write # for peter-evans/create-pull-request to create a PR |
|||
runs-on: ubuntu-latest |
|||
steps: |
|||
- uses: actions/checkout@v2 |
|||
with: |
|||
ref: dev |
|||
- name: Reset promotion branch |
|||
run: | |
|||
git fetch origin rel-10.7:rel-10.7 |
|||
git reset --hard rel-10.7 |
|||
- name: Create Pull Request |
|||
uses: peter-evans/create-pull-request@v3 |
|||
with: |
|||
branch: auto-merge/rel-10-7/${{github.run_number}} |
|||
title: Merge branch dev with rel-10.7 |
|||
body: This PR generated automatically to merge dev with rel-10.7. Please review the changed files before merging to prevent any errors that may occur. |
|||
draft: true |
|||
token: ${{ github.token }} |
|||
- name: Merge Pull Request |
|||
env: |
|||
GH_TOKEN: ${{ secrets.BOT_SECRET }} |
|||
run: | |
|||
gh pr ready |
|||
gh pr review auto-merge/rel-10-7/${{github.run_number}} --approve |
|||
gh pr merge auto-merge/rel-10-7/${{github.run_number}} --merge --auto --delete-branch |
|||
@ -1,12 +0,0 @@ |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.MalihuCustomScrollbar; |
|||
|
|||
public class MalihuCustomScrollbarPluginScriptBundleContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files.AddIfNotContains("/libs/malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.concat.min.js"); |
|||
} |
|||
} |
|||
@ -1,12 +0,0 @@ |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.MalihuCustomScrollbar; |
|||
|
|||
public class MalihuCustomScrollbarPluginStyleBundleContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files.AddIfNotContains("/libs/malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.css"); |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.OwlCarousel; |
|||
|
|||
[DependsOn(typeof(JQueryScriptContributor))] |
|||
public class OwlCarouselScriptContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files.AddIfNotContains("/libs/owl.carousel/owl.carousel.min.js"); |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.Bundling; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.OwlCarousel; |
|||
|
|||
public class OwlCarouselStyleContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
//TODO: Theming!
|
|||
context.Files.AddIfNotContains("/libs/owl.carousel/assets/owl.carousel.min.css"); |
|||
context.Files.AddIfNotContains("/libs/owl.carousel/assets/owl.theme.default.min.css"); |
|||
context.Files.AddIfNotContains("/libs/owl.carousel/assets/owl.theme.green.min.css"); |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
using System.Linq; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.ObjectMapping; |
|||
|
|||
/// <summary>
|
|||
/// Maps a query to another.
|
|||
/// Implement this interface to project a query on the data store side, instead of loading the
|
|||
/// source objects into the memory and mapping them one by one.
|
|||
/// Implement it once for a source and destination pair. Use the ReplaceServices option of the
|
|||
/// DependencyAttribute to replace an existing implementation.
|
|||
/// </summary>
|
|||
/// <typeparam name="TSource">Type of the source objects</typeparam>
|
|||
/// <typeparam name="TDestination">Type of the destination objects</typeparam>
|
|||
public interface IQueryProjector<TSource, TDestination> : ITransientDependency |
|||
{ |
|||
/// <summary>
|
|||
/// Projects the given query. The returned query must be built on top of it and must keep its order,
|
|||
/// with a single destination object for each source object, using expressions the query provider can
|
|||
/// translate. The caller may have already sorted, paged or counted the source query.
|
|||
/// </summary>
|
|||
/// <param name="source">The query to project</param>
|
|||
IQueryable<TDestination> ProjectTo(IQueryable<TSource> source); |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
using System; |
|||
using Volo.Abp.Domain.Entities; |
|||
|
|||
namespace Volo.Abp.Application.Services.QueryProjection; |
|||
|
|||
public class Book : Entity<Guid> |
|||
{ |
|||
public string Name { get; set; } = default!; |
|||
|
|||
public int Price { get; set; } |
|||
|
|||
public Book() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public Book(Guid id, string name, int price) |
|||
: base(id) |
|||
{ |
|||
Name = name; |
|||
Price = price; |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace Volo.Abp.Application.Services.QueryProjection; |
|||
|
|||
public class BookAbstractKeyAppService : AbstractKeyReadOnlyAppService<Book, BookDto, Guid> |
|||
{ |
|||
public BookAbstractKeyAppService(IReadOnlyRepository<Book> repository) |
|||
: base(repository) |
|||
{ |
|||
|
|||
} |
|||
|
|||
protected override async Task<Book> GetEntityByIdAsync(Guid id) |
|||
{ |
|||
var query = await ReadOnlyRepository.GetQueryableAsync(); |
|||
|
|||
return await AsyncExecuter.FirstAsync(query, book => book.Id == id); |
|||
} |
|||
|
|||
protected override IQueryable<Book> ApplyDefaultSorting(IQueryable<Book> query) |
|||
{ |
|||
return query.OrderBy(book => book.Id); |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
#nullable enable |
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace Volo.Abp.Application.Services.QueryProjection; |
|||
|
|||
public class BookAbstractKeyProjectingAppService : AbstractKeyReadOnlyAppService<Book, BookDto, Guid> |
|||
{ |
|||
public BookAbstractKeyProjectingAppService(IReadOnlyRepository<Book> repository) |
|||
: base(repository) |
|||
{ |
|||
|
|||
} |
|||
|
|||
protected override async Task<Book> GetEntityByIdAsync(Guid id) |
|||
{ |
|||
var query = await ReadOnlyRepository.GetQueryableAsync(); |
|||
|
|||
return await AsyncExecuter.FirstAsync(query, book => book.Id == id); |
|||
} |
|||
|
|||
protected override async Task<IQueryable<Book>?> CreateEntityQueryOrNullAsync(Guid id) |
|||
{ |
|||
var query = await ReadOnlyRepository.GetQueryableAsync(); |
|||
|
|||
return query.Where(book => book.Id == id); |
|||
} |
|||
|
|||
protected override IQueryable<Book> ApplyDefaultSorting(IQueryable<Book> query) |
|||
{ |
|||
return query.OrderBy(book => book.Id); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace Volo.Abp.Application.Services.QueryProjection; |
|||
|
|||
public class BookAppService : CrudAppService<Book, BookDto, Guid> |
|||
{ |
|||
public BookAppService(IRepository<Book, Guid> repository) |
|||
: base(repository) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
#nullable enable |
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace Volo.Abp.Application.Services.QueryProjection; |
|||
|
|||
public class BookAsyncProjectionAppService : CrudAppService<Book, BookDto, Guid> |
|||
{ |
|||
public const string Marker = "-async"; |
|||
|
|||
private readonly IBookNameSuffixProvider _suffixProvider; |
|||
|
|||
public BookAsyncProjectionAppService( |
|||
IRepository<Book, Guid> repository, |
|||
IBookNameSuffixProvider suffixProvider) |
|||
: base(repository) |
|||
{ |
|||
_suffixProvider = suffixProvider; |
|||
} |
|||
|
|||
protected override async Task<IQueryable<BookDto>?> CreateGetOutputDtoQueryOrNullAsync(Guid id) |
|||
{ |
|||
var query = await Repository.GetQueryableAsync(); |
|||
|
|||
return await ProjectAsync(query.Where(book => book.Id == id)); |
|||
} |
|||
|
|||
protected override async Task<IQueryable<BookDto>?> CreateGetListOutputDtoQueryOrNullAsync(IQueryable<Book> query) |
|||
{ |
|||
return await ProjectAsync(query); |
|||
} |
|||
|
|||
private async Task<IQueryable<BookDto>> ProjectAsync(IQueryable<Book> query) |
|||
{ |
|||
var suffix = await _suffixProvider.GetAsync(); |
|||
|
|||
return query.Select(book => new BookDto |
|||
{ |
|||
Id = book.Id, |
|||
Name = book.Name + suffix |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace Volo.Abp.Application.Services.QueryProjection; |
|||
|
|||
public class BookCustomizedAppService : CrudAppService<Book, BookDto, Guid> |
|||
{ |
|||
public const string Marker = "-customized"; |
|||
|
|||
public BookCustomizedAppService(IRepository<Book, Guid> repository) |
|||
: base(repository) |
|||
{ |
|||
|
|||
} |
|||
|
|||
protected override async Task<Book> GetEntityByIdAsync(Guid id) |
|||
{ |
|||
var book = await base.GetEntityByIdAsync(id); |
|||
book.Name += Marker; |
|||
return book; |
|||
} |
|||
|
|||
protected override Task<BookDto> MapToGetOutputDtoAsync(Book entity) |
|||
{ |
|||
return Task.FromResult(new BookDto { Id = entity.Id, Name = entity.Name + Marker }); |
|||
} |
|||
|
|||
protected override Task<List<BookDto>> MapToGetListOutputDtosAsync(List<Book> entities) |
|||
{ |
|||
return Task.FromResult(entities.ConvertAll(entity => new BookDto { Id = entity.Id, Name = entity.Name + Marker })); |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace Volo.Abp.Application.Services.QueryProjection; |
|||
|
|||
public class BookDetailAppService : |
|||
ReadOnlyAppService<Book, BookDetailDto, BookDto, Guid, PagedAndSortedResultRequestDto> |
|||
{ |
|||
public BookDetailAppService(IReadOnlyRepository<Book, Guid> repository) |
|||
: base(repository) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace Volo.Abp.Application.Services.QueryProjection; |
|||
|
|||
public class BookDetailDto : EntityDto<Guid> |
|||
{ |
|||
public string Name { get; set; } = default!; |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using System.Linq; |
|||
using Volo.Abp.ObjectMapping; |
|||
|
|||
namespace Volo.Abp.Application.Services.QueryProjection; |
|||
|
|||
public class BookDetailProjector : IQueryProjector<Book, BookDetailDto> |
|||
{ |
|||
public const string Marker = "-detail"; |
|||
|
|||
public IQueryable<BookDetailDto> ProjectTo(IQueryable<Book> source) |
|||
{ |
|||
return source.Select(book => new BookDetailDto |
|||
{ |
|||
Id = book.Id, |
|||
Name = book.Name + Marker |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace Volo.Abp.Application.Services.QueryProjection; |
|||
|
|||
public class BookDto : EntityDto<Guid> |
|||
{ |
|||
public string Name { get; set; } = default!; |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace Volo.Abp.Application.Services.QueryProjection; |
|||
|
|||
public class BookLiteAppService : CrudAppService<Book, BookLiteDto, Guid> |
|||
{ |
|||
public BookLiteAppService(IRepository<Book, Guid> repository) |
|||
: base(repository) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace Volo.Abp.Application.Services.QueryProjection; |
|||
|
|||
public class BookLiteDto : EntityDto<Guid> |
|||
{ |
|||
public string Name { get; set; } = default!; |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.ObjectMapping; |
|||
|
|||
namespace Volo.Abp.Application.Services.QueryProjection; |
|||
|
|||
public class BookObjectMapper : |
|||
IObjectMapper<Book, BookDto>, |
|||
IObjectMapper<Book, BookLiteDto>, |
|||
IObjectMapper<BookDto, Book>, |
|||
ITransientDependency |
|||
{ |
|||
public const string Marker = "-mapped"; |
|||
|
|||
public BookDto Map(Book source) |
|||
{ |
|||
return new BookDto { Id = source.Id, Name = source.Name + Marker }; |
|||
} |
|||
|
|||
public BookDto Map(Book source, BookDto destination) |
|||
{ |
|||
destination.Id = source.Id; |
|||
destination.Name = source.Name + Marker; |
|||
return destination; |
|||
} |
|||
|
|||
BookLiteDto IObjectMapper<Book, BookLiteDto>.Map(Book source) |
|||
{ |
|||
return new BookLiteDto { Id = source.Id, Name = source.Name + Marker }; |
|||
} |
|||
|
|||
BookLiteDto IObjectMapper<Book, BookLiteDto>.Map(Book source, BookLiteDto destination) |
|||
{ |
|||
destination.Id = source.Id; |
|||
destination.Name = source.Name + Marker; |
|||
return destination; |
|||
} |
|||
|
|||
Book IObjectMapper<BookDto, Book>.Map(BookDto source) |
|||
{ |
|||
return new Book(source.Id, source.Name, 0); |
|||
} |
|||
|
|||
Book IObjectMapper<BookDto, Book>.Map(BookDto source, Book destination) |
|||
{ |
|||
destination.Name = source.Name; |
|||
return destination; |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace Volo.Abp.Application.Services.QueryProjection; |
|||
|
|||
public class BookPolicyCheckedException : Exception |
|||
{ |
|||
|
|||
} |
|||
|
|||
public class BookPolicyCheckedAppService : CrudAppService<Book, BookDto, Guid> |
|||
{ |
|||
public BookPolicyCheckedAppService(IRepository<Book, Guid> repository) |
|||
: base(repository) |
|||
{ |
|||
|
|||
} |
|||
|
|||
protected override Task CheckGetPolicyAsync() |
|||
{ |
|||
throw new BookPolicyCheckedException(); |
|||
} |
|||
|
|||
protected override Task CheckGetListPolicyAsync() |
|||
{ |
|||
throw new BookPolicyCheckedException(); |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using System.Linq; |
|||
using Volo.Abp.ObjectMapping; |
|||
|
|||
namespace Volo.Abp.Application.Services.QueryProjection; |
|||
|
|||
public class BookProjector : IQueryProjector<Book, BookDto> |
|||
{ |
|||
public const string Marker = "-projected"; |
|||
|
|||
public IQueryable<BookDto> ProjectTo(IQueryable<Book> source) |
|||
{ |
|||
return source.Select(book => new BookDto |
|||
{ |
|||
Id = book.Id, |
|||
Name = book.Name + Marker |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace Volo.Abp.Application.Services.QueryProjection; |
|||
|
|||
public class BookReadOnlyAppService : ReadOnlyAppService<Book, BookDto, Guid> |
|||
{ |
|||
public BookReadOnlyAppService(IReadOnlyRepository<Book, Guid> repository) |
|||
: base(repository) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,106 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Linq.Expressions; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Domain.Entities; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace Volo.Abp.Application.Services.QueryProjection; |
|||
|
|||
[ExposeServices( |
|||
typeof(IRepository<Book, Guid>), |
|||
typeof(IReadOnlyRepository<Book, Guid>), |
|||
typeof(IReadOnlyRepository<Book>))] |
|||
public class BookRepository : RepositoryBase<Book, Guid>, ISingletonDependency |
|||
{ |
|||
private readonly List<Book> _books = new(); |
|||
|
|||
public BookRepository() |
|||
: base("InMemory") |
|||
{ |
|||
|
|||
} |
|||
|
|||
public override Task<IQueryable<Book>> GetQueryableAsync() |
|||
{ |
|||
return Task.FromResult(_books.AsQueryable()); |
|||
} |
|||
|
|||
[Obsolete("Use GetQueryableAsync method.")] |
|||
protected override IQueryable<Book> GetQueryable() |
|||
{ |
|||
return _books.AsQueryable(); |
|||
} |
|||
|
|||
public override Task<Book> GetAsync(Guid id, bool includeDetails = true, CancellationToken cancellationToken = default) |
|||
{ |
|||
var book = _books.FirstOrDefault(x => x.Id == id); |
|||
if (book == null) |
|||
{ |
|||
throw new EntityNotFoundException(typeof(Book), id); |
|||
} |
|||
|
|||
return Task.FromResult(book); |
|||
} |
|||
|
|||
public override Task<Book> FindAsync(Guid id, bool includeDetails = true, CancellationToken cancellationToken = default) |
|||
{ |
|||
return Task.FromResult(_books.FirstOrDefault(x => x.Id == id)); |
|||
} |
|||
|
|||
public override Task<Book> FindAsync(Expression<Func<Book, bool>> predicate, bool includeDetails = true, CancellationToken cancellationToken = default) |
|||
{ |
|||
return Task.FromResult(_books.AsQueryable().FirstOrDefault(predicate)); |
|||
} |
|||
|
|||
public override Task<List<Book>> GetListAsync(bool includeDetails = false, CancellationToken cancellationToken = default) |
|||
{ |
|||
return Task.FromResult(_books.ToList()); |
|||
} |
|||
|
|||
public override Task<List<Book>> GetListAsync(Expression<Func<Book, bool>> predicate, bool includeDetails = false, CancellationToken cancellationToken = default) |
|||
{ |
|||
return Task.FromResult(_books.AsQueryable().Where(predicate).ToList()); |
|||
} |
|||
|
|||
public override Task<List<Book>> GetPagedListAsync(int skipCount, int maxResultCount, string sorting, bool includeDetails = false, CancellationToken cancellationToken = default) |
|||
{ |
|||
return Task.FromResult(_books.Skip(skipCount).Take(maxResultCount).ToList()); |
|||
} |
|||
|
|||
public override Task<long> GetCountAsync(CancellationToken cancellationToken = default) |
|||
{ |
|||
return Task.FromResult((long)_books.Count); |
|||
} |
|||
|
|||
public override Task<Book> InsertAsync(Book entity, bool autoSave = false, CancellationToken cancellationToken = default) |
|||
{ |
|||
_books.Add(entity); |
|||
return Task.FromResult(entity); |
|||
} |
|||
|
|||
public override Task<Book> UpdateAsync(Book entity, bool autoSave = false, CancellationToken cancellationToken = default) |
|||
{ |
|||
return Task.FromResult(entity); |
|||
} |
|||
|
|||
public override Task DeleteAsync(Book entity, bool autoSave = false, CancellationToken cancellationToken = default) |
|||
{ |
|||
_books.Remove(entity); |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public override Task DeleteAsync(Expression<Func<Book, bool>> predicate, bool autoSave = false, CancellationToken cancellationToken = default) |
|||
{ |
|||
_books.RemoveAll(new Predicate<Book>(predicate.Compile())); |
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
public override Task DeleteDirectAsync(Expression<Func<Book, bool>> predicate, CancellationToken cancellationToken = default) |
|||
{ |
|||
return DeleteAsync(predicate); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace Volo.Abp.Application.Services.QueryProjection; |
|||
|
|||
public class BookStructAppService : ReadOnlyAppService<Book, BookStructDto, Guid> |
|||
{ |
|||
public BookStructAppService(IReadOnlyRepository<Book, Guid> repository) |
|||
: base(repository) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.Application.Services.QueryProjection; |
|||
|
|||
public struct BookStructDto |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
public string Name { get; set; } |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using System.Linq; |
|||
using Volo.Abp.ObjectMapping; |
|||
|
|||
namespace Volo.Abp.Application.Services.QueryProjection; |
|||
|
|||
public class BookStructProjector : IQueryProjector<Book, BookStructDto> |
|||
{ |
|||
public IQueryable<BookStructDto> ProjectTo(IQueryable<Book> source) |
|||
{ |
|||
return source.Select(book => new BookStructDto |
|||
{ |
|||
Id = book.Id, |
|||
Name = book.Name |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
#nullable enable |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.ObjectMapping; |
|||
|
|||
namespace Volo.Abp.Application.Services.QueryProjection; |
|||
|
|||
public class BookWithoutProjectionAppService : CrudAppService<Book, BookDto, Guid> |
|||
{ |
|||
public const string Marker = "-not-projected"; |
|||
|
|||
protected override IQueryProjector<Book, BookDto>? GetOutputDtoQueryProjector => null; |
|||
|
|||
protected override IQueryProjector<Book, BookDto>? GetListOutputDtoQueryProjector => null; |
|||
|
|||
public BookWithoutProjectionAppService(IRepository<Book, Guid> repository) |
|||
: base(repository) |
|||
{ |
|||
|
|||
} |
|||
|
|||
protected override Task<BookDto> MapToGetOutputDtoAsync(Book entity) |
|||
{ |
|||
return Task.FromResult(new BookDto { Id = entity.Id, Name = entity.Name + Marker }); |
|||
} |
|||
|
|||
protected override Task<List<BookDto>> MapToGetListOutputDtosAsync(List<Book> entities) |
|||
{ |
|||
return Task.FromResult(entities.ConvertAll(entity => new BookDto { Id = entity.Id, Name = entity.Name + Marker })); |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Application.Services.QueryProjection; |
|||
|
|||
public interface IBookNameSuffixProvider |
|||
{ |
|||
Task<string> GetAsync(); |
|||
} |
|||
|
|||
public class BookNameSuffixProvider : IBookNameSuffixProvider, ITransientDependency |
|||
{ |
|||
public async Task<string> GetAsync() |
|||
{ |
|||
await Task.Yield(); |
|||
|
|||
return BookAsyncProjectionAppService.Marker; |
|||
} |
|||
} |
|||
@ -0,0 +1,196 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Shouldly; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Domain.Entities; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.ObjectMapping; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Application.Services.QueryProjection; |
|||
|
|||
public class QueryProjection_Tests : AbpDddApplicationTestBase |
|||
{ |
|||
private readonly Guid _bookId = Guid.NewGuid(); |
|||
|
|||
public QueryProjection_Tests() |
|||
{ |
|||
var repository = GetRequiredService<IRepository<Book, Guid>>(); |
|||
repository.InsertAsync(new Book(_bookId, "Hitchhiker's Guide", 42)).GetAwaiter().GetResult(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Resolve_The_Projector_Independent_From_The_Class_Name() |
|||
{ |
|||
ServiceProvider.GetService<IQueryProjector<Book, BookDto>>() |
|||
.ShouldBeOfType<BookProjector>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Project_On_The_Query_For_CrudAppService() |
|||
{ |
|||
var appService = GetRequiredService<BookAppService>(); |
|||
|
|||
(await appService.GetAsync(_bookId)).Name.ShouldEndWith(BookProjector.Marker); |
|||
(await appService.GetListAsync(new PagedAndSortedResultRequestDto())) |
|||
.Items[0].Name.ShouldEndWith(BookProjector.Marker); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Project_On_The_Query_For_ReadOnlyAppService() |
|||
{ |
|||
var appService = GetRequiredService<BookReadOnlyAppService>(); |
|||
|
|||
(await appService.GetAsync(_bookId)).Name.ShouldEndWith(BookProjector.Marker); |
|||
(await appService.GetListAsync(new PagedAndSortedResultRequestDto())) |
|||
.Items[0].Name.ShouldEndWith(BookProjector.Marker); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Throw_EntityNotFoundException_If_The_Projected_Entity_Does_Not_Exist() |
|||
{ |
|||
var appService = GetRequiredService<BookAppService>(); |
|||
|
|||
await Should.ThrowAsync<EntityNotFoundException>(async () => await appService.GetAsync(Guid.NewGuid())); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Use_The_Object_Mapper_If_No_Projector_Was_Registered() |
|||
{ |
|||
var appService = GetRequiredService<BookLiteAppService>(); |
|||
|
|||
(await appService.GetAsync(_bookId)).Name.ShouldEndWith(BookObjectMapper.Marker); |
|||
(await appService.GetListAsync(new PagedAndSortedResultRequestDto())) |
|||
.Items[0].Name.ShouldEndWith(BookObjectMapper.Marker); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Use_The_Entity_Based_Overrides_If_The_Projection_Was_Disabled() |
|||
{ |
|||
var appService = GetRequiredService<BookWithoutProjectionAppService>(); |
|||
|
|||
(await appService.GetAsync(_bookId)).Name.ShouldEndWith(BookWithoutProjectionAppService.Marker); |
|||
(await appService.GetListAsync(new PagedAndSortedResultRequestDto())) |
|||
.Items[0].Name.ShouldEndWith(BookWithoutProjectionAppService.Marker); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Not_Use_The_Entity_Based_Overrides_While_Projecting() |
|||
{ |
|||
var appService = GetRequiredService<BookCustomizedAppService>(); |
|||
|
|||
var dto = await appService.GetAsync(_bookId); |
|||
|
|||
dto.Name.ShouldEndWith(BookProjector.Marker); |
|||
dto.Name.ShouldNotContain(BookCustomizedAppService.Marker); |
|||
|
|||
var items = (await appService.GetListAsync(new PagedAndSortedResultRequestDto())).Items; |
|||
|
|||
items[0].Name.ShouldEndWith(BookProjector.Marker); |
|||
items[0].Name.ShouldNotContain(BookCustomizedAppService.Marker); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Use_The_Object_Mapper_If_The_Entity_Query_Can_Not_Be_Created() |
|||
{ |
|||
var appService = GetRequiredService<BookAbstractKeyAppService>(); |
|||
|
|||
//GetAsync has no query to project, it falls back to GetEntityByIdAsync
|
|||
(await appService.GetAsync(_bookId)).Name.ShouldEndWith(BookObjectMapper.Marker); |
|||
|
|||
//GetListAsync always has a query, so it is still projected
|
|||
(await appService.GetListAsync(new PagedAndSortedResultRequestDto())) |
|||
.Items[0].Name.ShouldEndWith(BookProjector.Marker); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Use_The_Asynchronously_Created_Projection_Query() |
|||
{ |
|||
var appService = GetRequiredService<BookAsyncProjectionAppService>(); |
|||
|
|||
(await appService.GetAsync(_bookId)).Name.ShouldEndWith(BookAsyncProjectionAppService.Marker); |
|||
(await appService.GetListAsync(new PagedAndSortedResultRequestDto())) |
|||
.Items[0].Name.ShouldEndWith(BookAsyncProjectionAppService.Marker); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Throw_EntityNotFoundException_From_An_Overridden_Projection_Query() |
|||
{ |
|||
var appService = GetRequiredService<BookAsyncProjectionAppService>(); |
|||
|
|||
await Should.ThrowAsync<EntityNotFoundException>(async () => await appService.GetAsync(Guid.NewGuid())); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Throw_EntityNotFoundException_For_A_Value_Type_Dto() |
|||
{ |
|||
var appService = GetRequiredService<BookStructAppService>(); |
|||
|
|||
(await appService.GetAsync(_bookId)).Name.ShouldBe("Hitchhiker's Guide"); |
|||
|
|||
await Should.ThrowAsync<EntityNotFoundException>(async () => await appService.GetAsync(Guid.NewGuid())); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Not_Project_On_The_Create_And_Update_Paths() |
|||
{ |
|||
var appService = GetRequiredService<BookAppService>(); |
|||
|
|||
var created = await appService.CreateAsync(new BookDto { Name = "New Book" }); |
|||
created.Name.ShouldEndWith(BookObjectMapper.Marker); |
|||
|
|||
var updated = await appService.UpdateAsync(created.Id, new BookDto { Name = "Updated Book" }); |
|||
updated.Name.ShouldEndWith(BookObjectMapper.Marker); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Use_A_Different_Projector_For_The_Get_And_The_List() |
|||
{ |
|||
var appService = GetRequiredService<BookDetailAppService>(); |
|||
|
|||
(await appService.GetAsync(_bookId)).Name.ShouldEndWith(BookDetailProjector.Marker); |
|||
(await appService.GetListAsync(new PagedAndSortedResultRequestDto())) |
|||
.Items[0].Name.ShouldEndWith(BookProjector.Marker); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Apply_Paging_And_Sorting_Before_Projecting() |
|||
{ |
|||
var repository = GetRequiredService<IRepository<Book, Guid>>(); |
|||
await repository.InsertAsync(new Book(Guid.NewGuid(), "A Book", 1)); |
|||
await repository.InsertAsync(new Book(Guid.NewGuid(), "Z Book", 2)); |
|||
|
|||
var appService = GetRequiredService<BookAppService>(); |
|||
|
|||
var firstPage = await appService.GetListAsync( |
|||
new PagedAndSortedResultRequestDto { MaxResultCount = 1, Sorting = "Name" }); |
|||
|
|||
firstPage.TotalCount.ShouldBe(3); |
|||
firstPage.Items.Count.ShouldBe(1); |
|||
firstPage.Items[0].Name.ShouldBe("A Book" + BookProjector.Marker); |
|||
|
|||
var secondPage = await appService.GetListAsync( |
|||
new PagedAndSortedResultRequestDto { MaxResultCount = 1, SkipCount = 1, Sorting = "Name" }); |
|||
|
|||
secondPage.Items[0].Name.ShouldBe("Hitchhiker's Guide" + BookProjector.Marker); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Project_A_Single_Entity_From_An_AbstractKey_Application_Service() |
|||
{ |
|||
var appService = GetRequiredService<BookAbstractKeyProjectingAppService>(); |
|||
|
|||
(await appService.GetAsync(_bookId)).Name.ShouldEndWith(BookProjector.Marker); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Check_The_Policies_While_Projecting() |
|||
{ |
|||
var appService = GetRequiredService<BookPolicyCheckedAppService>(); |
|||
|
|||
await Should.ThrowAsync<BookPolicyCheckedException>(async () => await appService.GetAsync(_bookId)); |
|||
await Should.ThrowAsync<BookPolicyCheckedException>(async () => |
|||
await appService.GetListAsync(new PagedAndSortedResultRequestDto())); |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using Volo.Abp.Application.Services; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.TestApp.Domain; |
|||
|
|||
namespace Volo.Abp.EntityFrameworkCore.Applications; |
|||
|
|||
public class EntityWithIntPkProjectionAppService : ReadOnlyAppService<EntityWithIntPk, EntityWithIntPkProjectionDto, int> |
|||
{ |
|||
public EntityWithIntPkProjectionAppService(IReadOnlyRepository<EntityWithIntPk, int> repository) |
|||
: base(repository) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace Volo.Abp.EntityFrameworkCore.Applications; |
|||
|
|||
public class EntityWithIntPkProjectionDto : EntityDto<int> |
|||
{ |
|||
public string Name { get; set; } |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System.Linq; |
|||
using Volo.Abp.ObjectMapping; |
|||
using Volo.Abp.TestApp.Domain; |
|||
|
|||
namespace Volo.Abp.EntityFrameworkCore.Applications; |
|||
|
|||
public class EntityWithIntPkProjector : IQueryProjector<EntityWithIntPk, EntityWithIntPkProjectionDto> |
|||
{ |
|||
public IQueryable<EntityWithIntPkProjectionDto> ProjectTo(IQueryable<EntityWithIntPk> source) |
|||
{ |
|||
return source.Select(entity => new EntityWithIntPkProjectionDto |
|||
{ |
|||
Id = entity.Id, |
|||
Name = entity.Name |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Services; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.TestApp.Domain; |
|||
|
|||
namespace Volo.Abp.EntityFrameworkCore.Applications; |
|||
|
|||
public class PersonProjectionAppService : ReadOnlyAppService<Person, PersonProjectionDto, Guid> |
|||
{ |
|||
public PersonProjectionAppService(IReadOnlyRepository<Person, Guid> repository) |
|||
: base(repository) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace Volo.Abp.EntityFrameworkCore.Applications; |
|||
|
|||
public class PersonProjectionDto : EntityDto<Guid> |
|||
{ |
|||
public string Name { get; set; } |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System.Linq; |
|||
using Volo.Abp.ObjectMapping; |
|||
using Volo.Abp.TestApp.Domain; |
|||
|
|||
namespace Volo.Abp.EntityFrameworkCore.Applications; |
|||
|
|||
public class PersonProjector : IQueryProjector<Person, PersonProjectionDto> |
|||
{ |
|||
public IQueryable<PersonProjectionDto> ProjectTo(IQueryable<Person> source) |
|||
{ |
|||
return source.Select(person => new PersonProjectionDto |
|||
{ |
|||
Id = person.Id, |
|||
Name = person.Name |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
#nullable enable |
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Services; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.TestApp.Domain; |
|||
|
|||
namespace Volo.Abp.EntityFrameworkCore.Applications; |
|||
|
|||
//City is another aggregate root, so Person has no City navigation property to project.
|
|||
public class PersonWithCityAppService : ReadOnlyAppService<Person, PersonWithCityDto, Guid> |
|||
{ |
|||
private readonly IReadOnlyRepository<City, Guid> _cityRepository; |
|||
|
|||
public PersonWithCityAppService( |
|||
IReadOnlyRepository<Person, Guid> repository, |
|||
IReadOnlyRepository<City, Guid> cityRepository) |
|||
: base(repository) |
|||
{ |
|||
_cityRepository = cityRepository; |
|||
} |
|||
|
|||
protected override async Task<IQueryable<PersonWithCityDto>?> CreateGetOutputDtoQueryOrNullAsync(Guid id) |
|||
{ |
|||
var people = await CreateEntityQueryOrNullAsync(id); |
|||
|
|||
return people == null ? null : await JoinCitiesAsync(people); |
|||
} |
|||
|
|||
protected override async Task<IQueryable<PersonWithCityDto>?> CreateGetListOutputDtoQueryOrNullAsync(IQueryable<Person> query) |
|||
{ |
|||
return await JoinCitiesAsync(query); |
|||
} |
|||
|
|||
//left join, an inner join would drop the people without a city and break the total count
|
|||
private async Task<IQueryable<PersonWithCityDto>> JoinCitiesAsync(IQueryable<Person> people) |
|||
{ |
|||
var cities = await _cityRepository.GetQueryableAsync(); |
|||
|
|||
return from person in people |
|||
join city in cities on person.CityId equals city.Id into personCities |
|||
from personCity in personCities.DefaultIfEmpty() |
|||
select new PersonWithCityDto |
|||
{ |
|||
Id = person.Id, |
|||
Name = person.Name, |
|||
CityName = personCity != null ? personCity.Name : null |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
#nullable enable |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace Volo.Abp.EntityFrameworkCore.Applications; |
|||
|
|||
public class PersonWithCityDto : EntityDto<Guid> |
|||
{ |
|||
public string Name { get; set; } |
|||
|
|||
public string? CityName { get; set; } |
|||
} |
|||
@ -0,0 +1,197 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Shouldly; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Domain.Entities; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.ObjectMapping; |
|||
using Volo.Abp.TestApp; |
|||
using Volo.Abp.TestApp.Application; |
|||
using Volo.Abp.Threading; |
|||
using Volo.Abp.TestApp.Domain; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.EntityFrameworkCore.Applications; |
|||
|
|||
public class QueryProjection_Tests : EntityFrameworkCoreTestBase |
|||
{ |
|||
private readonly PersonProjectionAppService _personProjectionAppService; |
|||
|
|||
public QueryProjection_Tests() |
|||
{ |
|||
_personProjectionAppService = GetRequiredService<PersonProjectionAppService>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Get_A_Projected_Entity() |
|||
{ |
|||
var dto = await _personProjectionAppService.GetAsync(TestDataBuilder.UserDouglasId); |
|||
|
|||
dto.Id.ShouldBe(TestDataBuilder.UserDouglasId); |
|||
dto.Name.ShouldBe("Douglas"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Throw_EntityNotFoundException_For_A_Missing_Entity() |
|||
{ |
|||
(await Should.ThrowAsync<EntityNotFoundException<Person>>( |
|||
async () => await _personProjectionAppService.GetAsync(Guid.NewGuid())) |
|||
).EntityType.ShouldBe(typeof(Person)); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Get_A_Projected_List() |
|||
{ |
|||
var result = await _personProjectionAppService.GetListAsync(new PagedAndSortedResultRequestDto()); |
|||
|
|||
result.TotalCount.ShouldBeGreaterThan(0); |
|||
result.Items.Count.ShouldBe((int)result.TotalCount); |
|||
result.Items.ShouldContain(x => x.Name == "Douglas"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Get_A_Projected_Entity_With_A_Non_Guid_Key() |
|||
{ |
|||
var appService = GetRequiredService<EntityWithIntPkProjectionAppService>(); |
|||
var entity = await WithUnitOfWorkAsync( |
|||
async () => await GetRequiredService<IRepository<EntityWithIntPk, int>>().FirstAsync()); |
|||
|
|||
(await appService.GetAsync(entity.Id)).Name.ShouldBe(entity.Name); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Apply_The_Soft_Delete_Filter_While_Projecting() |
|||
{ |
|||
var result = await _personProjectionAppService.GetListAsync(new PagedAndSortedResultRequestDto()); |
|||
|
|||
result.Items.ShouldNotContain(x => x.Id == TestDataBuilder.UserJohnDeletedId); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Only_Select_The_Projected_Columns() |
|||
{ |
|||
await WithUnitOfWorkAsync(async () => |
|||
{ |
|||
var repository = GetRequiredService<IReadOnlyRepository<Person, Guid>>(); |
|||
var projector = GetRequiredService<IQueryProjector<Person, PersonProjectionDto>>(); |
|||
|
|||
var sql = projector.ProjectTo(await repository.GetQueryableAsync()).ToQueryString(); |
|||
|
|||
sql.ShouldContain("\"Name\""); |
|||
sql.ShouldNotContain("\"Birthday\""); |
|||
sql.ShouldNotContain("\"ExtraProperties\""); |
|||
|
|||
//the data filters are still a part of the query
|
|||
sql.ShouldContain("Is_Deleted"); |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Join_Another_Aggregate_While_Projecting() |
|||
{ |
|||
var appService = GetRequiredService<PersonWithCityAppService>(); |
|||
|
|||
var dto = await appService.GetAsync(TestDataBuilder.UserDouglasId); |
|||
dto.CityName.ShouldBe("London"); |
|||
|
|||
var result = await appService.GetListAsync(new PagedAndSortedResultRequestDto()); |
|||
result.Items.ShouldContain(x => x.CityName == "London"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Keep_The_Total_Count_While_Joining_Another_Aggregate() |
|||
{ |
|||
await WithUnitOfWorkAsync(async () => |
|||
{ |
|||
await GetRequiredService<IRepository<Person, Guid>>() |
|||
.InsertAsync(new Person(Guid.NewGuid(), "PersonWithoutCity", 30), autoSave: true); |
|||
}); |
|||
|
|||
var result = await GetRequiredService<PersonWithCityAppService>() |
|||
.GetListAsync(new PagedAndSortedResultRequestDto()); |
|||
|
|||
result.Items.Count.ShouldBe((int)result.TotalCount); |
|||
result.Items.ShouldContain(x => x.Name == "PersonWithoutCity" && x.CityName == null); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Execute_The_Projected_Query_From_The_Application_Service() |
|||
{ |
|||
System.Collections.Concurrent.ConcurrentQueue<string> commands; |
|||
using (SqlCommandCapture.Begin(out commands)) |
|||
{ |
|||
await GetRequiredService<PersonProjectionAppService>() |
|||
.GetListAsync(new PagedAndSortedResultRequestDto()); |
|||
} |
|||
|
|||
//the application service must run the projection itself, not materialize the entities first
|
|||
var selects = commands.Where(x => x.Contains("FROM \"People\"") && !x.Contains("COUNT")).ToList(); |
|||
|
|||
var select = selects.ShouldHaveSingleItem(); |
|||
select.ShouldContain("\"Name\""); |
|||
select.ShouldNotContain("\"Birthday\""); |
|||
select.ShouldNotContain("\"ExtraProperties\""); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Apply_The_Multi_Tenancy_Filter_While_Projecting() |
|||
{ |
|||
var result = await GetRequiredService<PersonProjectionAppService>() |
|||
.GetListAsync(new PagedAndSortedResultRequestDto()); |
|||
|
|||
result.Items.ShouldNotContain(x => x.Name.StartsWith(TestDataBuilder.TenantId1.ToString())); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Use_The_Ambient_Cancellation_Token_While_Projecting() |
|||
{ |
|||
var cancellationTokenProvider = GetRequiredService<ICancellationTokenProvider>(); |
|||
var appService = GetRequiredService<PersonProjectionAppService>(); |
|||
|
|||
//the entity path passes it through Repository.GetAsync, so the projected path must not regress
|
|||
using (cancellationTokenProvider.Use(new CancellationToken(canceled: true))) |
|||
{ |
|||
await Should.ThrowAsync<OperationCanceledException>(async () => |
|||
await appService.GetAsync(TestDataBuilder.UserDouglasId)); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Execute_The_Projected_Query_From_The_Application_Service_For_A_Single_Entity() |
|||
{ |
|||
System.Collections.Concurrent.ConcurrentQueue<string> commands; |
|||
using (SqlCommandCapture.Begin(out commands)) |
|||
{ |
|||
await GetRequiredService<PersonProjectionAppService>().GetAsync(TestDataBuilder.UserDouglasId); |
|||
} |
|||
|
|||
var selects = commands.Where(x => x.Contains("FROM \"People\"")).ToList(); |
|||
|
|||
var select = selects.ShouldHaveSingleItem(); |
|||
select.ShouldContain("\"Name\""); |
|||
select.ShouldNotContain("\"Birthday\""); |
|||
select.ShouldNotContain("\"ExtraProperties\""); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Apply_The_Data_Filters_Of_The_Joined_Aggregate() |
|||
{ |
|||
var cityRepository = GetRequiredService<IRepository<City, Guid>>(); |
|||
|
|||
await WithUnitOfWorkAsync(async () => |
|||
{ |
|||
var london = await cityRepository.GetAsync(TestDataBuilder.LondonCityId); |
|||
await cityRepository.DeleteAsync(london, autoSave: true); |
|||
}); |
|||
|
|||
var result = await GetRequiredService<PersonWithCityAppService>() |
|||
.GetListAsync(new PagedAndSortedResultRequestDto()); |
|||
|
|||
//the soft deleted city is filtered out, the left join still keeps the person
|
|||
result.Items.ShouldContain(x => x.Name == "Douglas" && x.CityName == null); |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
using System; |
|||
using System.Collections.Concurrent; |
|||
using System.Collections.Generic; |
|||
using System.Data.Common; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.EntityFrameworkCore.Diagnostics; |
|||
|
|||
namespace Volo.Abp.EntityFrameworkCore.Applications; |
|||
|
|||
public class SqlCommandCapture : DbCommandInterceptor |
|||
{ |
|||
private static readonly AsyncLocal<ConcurrentQueue<string>> Commands = new(); |
|||
|
|||
public static IDisposable Begin(out ConcurrentQueue<string> commands) |
|||
{ |
|||
commands = new ConcurrentQueue<string>(); |
|||
Commands.Value = commands; |
|||
return new DisposeAction(() => Commands.Value = null); |
|||
} |
|||
|
|||
public override InterceptionResult<DbDataReader> ReaderExecuting( |
|||
DbCommand command, |
|||
CommandEventData eventData, |
|||
InterceptionResult<DbDataReader> result) |
|||
{ |
|||
Commands.Value?.Enqueue(command.CommandText); |
|||
return base.ReaderExecuting(command, eventData, result); |
|||
} |
|||
|
|||
public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync( |
|||
DbCommand command, |
|||
CommandEventData eventData, |
|||
InterceptionResult<DbDataReader> result, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
Commands.Value?.Enqueue(command.CommandText); |
|||
return base.ReaderExecutingAsync(command, eventData, result, cancellationToken); |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Shouldly; |
|||
using Volo.Abp.Mapperly.SampleClasses; |
|||
using Volo.Abp.ObjectMapping; |
|||
using Volo.Abp.Testing; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Mapperly; |
|||
|
|||
public class AbpMapperlyQueryProjection_Tests : AbpIntegratedTest<MapperlyTestModule> |
|||
{ |
|||
[Fact] |
|||
public void Should_Project_A_Queryable() |
|||
{ |
|||
var queryProjector = ServiceProvider.GetRequiredService<IQueryProjector<MyEntity, MyEntityDto>>(); |
|||
|
|||
var entities = new List<MyEntity> |
|||
{ |
|||
new MyEntity { Id = Guid.NewGuid(), Number = 42 } |
|||
}.AsQueryable(); |
|||
|
|||
var dtos = queryProjector.ProjectTo(entities).ToList(); |
|||
|
|||
dtos.Count.ShouldBe(1); |
|||
dtos[0].Id.ShouldBe(entities.First().Id); |
|||
dtos[0].Number.ShouldBe(42); |
|||
} |
|||
|
|||
|
|||
[Fact] |
|||
public void Should_Register_A_Projector_Only_Once() |
|||
{ |
|||
//MyEntityQueryProjector also matches the class name convention of ExposeServicesAttribute
|
|||
ServiceProvider.GetServices<IQueryProjector<MyEntity, MyEntityDto>>().ShouldHaveSingleItem(); |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System.Linq; |
|||
using Riok.Mapperly.Abstractions; |
|||
using Volo.Abp.ObjectMapping; |
|||
|
|||
namespace Volo.Abp.Mapperly.SampleClasses; |
|||
|
|||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)] |
|||
public partial class MyEntityQueryProjector : IQueryProjector<MyEntity, MyEntityDto> |
|||
{ |
|||
public partial IQueryable<MyEntityDto> ProjectTo(IQueryable<MyEntity> source); |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Services; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.TestApp.Domain; |
|||
|
|||
namespace Volo.Abp.MongoDB.Applications; |
|||
|
|||
public class PersonProjectionAppService : ReadOnlyAppService<Person, PersonProjectionDto, Guid> |
|||
{ |
|||
public PersonProjectionAppService(IReadOnlyRepository<Person, Guid> repository) |
|||
: base(repository) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace Volo.Abp.MongoDB.Applications; |
|||
|
|||
public class PersonProjectionDto : EntityDto<Guid> |
|||
{ |
|||
public string Name { get; set; } |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System.Linq; |
|||
using Volo.Abp.ObjectMapping; |
|||
using Volo.Abp.TestApp.Domain; |
|||
|
|||
namespace Volo.Abp.MongoDB.Applications; |
|||
|
|||
public class PersonProjector : IQueryProjector<Person, PersonProjectionDto> |
|||
{ |
|||
public IQueryable<PersonProjectionDto> ProjectTo(IQueryable<Person> source) |
|||
{ |
|||
return source.Select(person => new PersonProjectionDto |
|||
{ |
|||
Id = person.Id, |
|||
Name = person.Name |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Shouldly; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Domain.Entities; |
|||
using Volo.Abp.TestApp; |
|||
using Volo.Abp.TestApp.Domain; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.MongoDB.Applications; |
|||
|
|||
[Collection(MongoTestCollection.Name)] |
|||
public class QueryProjection_Tests : MongoDbTestBase |
|||
{ |
|||
private readonly PersonProjectionAppService _personProjectionAppService; |
|||
|
|||
public QueryProjection_Tests() |
|||
{ |
|||
_personProjectionAppService = GetRequiredService<PersonProjectionAppService>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Get_A_Projected_Entity() |
|||
{ |
|||
var dto = await _personProjectionAppService.GetAsync(TestDataBuilder.UserDouglasId); |
|||
|
|||
dto.Id.ShouldBe(TestDataBuilder.UserDouglasId); |
|||
dto.Name.ShouldBe("Douglas"); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Throw_EntityNotFoundException_For_A_Missing_Entity() |
|||
{ |
|||
await Should.ThrowAsync<EntityNotFoundException<Person>>( |
|||
async () => await _personProjectionAppService.GetAsync(Guid.NewGuid())); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Get_A_Projected_List() |
|||
{ |
|||
var result = await _personProjectionAppService.GetListAsync(new PagedAndSortedResultRequestDto()); |
|||
|
|||
result.TotalCount.ShouldBeGreaterThan(0); |
|||
result.Items.ShouldContain(x => x.Name == "Douglas"); |
|||
} |
|||
} |
|||
@ -1 +1 @@ |
|||
{"version":3,"sourceRoot":"","sources":["vs.scss"],"names":[],"mappings":"AAGA;EACI;EACA;;AAEA;EACI;EACA;;AAIJ;EACI;;AAGJ;EACI;EACA;;AAGJ;EACI;;AAGJ;EACI;;AAGJ;EACI;EACA;EACA;;AAIA;EACI;;AAGJ;AAAA;AAAA;AAAA;EAII;;AAIR;AAAA;EAEI;;AAGJ;EACI;;AAEA;EACI;;AAEJ;EACI;;AAIR;AAAA;EAEI;EACA;EACA;;AAGJ;EACI;;AAKA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EAEI;EACA;;AAIJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EAEI;EACA;EACA;EACA;EACA;;AAIJ;EACI;;AAGJ;EACI;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEA;EACI;;AAIR;EACI;EACA;EACA;EACA;;AAEA;EACI;;AAIR;EA5GJ;IA6GQ;IACA;IACA;;EAEA;IACI;IACA;IACA;IACA;IACA;;EAGJ;IACI;IACA;IACA;IACA;;;AAOR;EAEI;;AAGJ;EACI;IAEI;;;;AAOpB;EACI;;AAEA;EACI;EACA;EACA,KAtOK;EAuOL,QAvOK;EAwOL;EACA;EACA;EACA;EACA;EACA;EACA;;AAIQ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;;AAMhB;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;;AAGJ;EACI;EACA;;AAEJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAIR;AAAA;EAEI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;AAAA;AAAA;EAII;;AAIR;EACI;EACA;;AAEJ;EACI;EACA;;AAEA;EACI;;AAIR;EACI;;AAEA;EAII;;AAIR;EACI;EACA;EACA;;AAIR;EACI;;AAIR;EAEI;EACA;;AAEA;EACI;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;;AAKZ;EACI;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGI;EACI;;AAIR;EACI;;AAIR;EACI;;AAGI;EACI;;AAGJ;EACI;;AAGI;EACI;;AAQhB;EACI;EACA;;AAIA;EACI;EACA;;AAMA;EACI;;AAOpB;EACI;;AAKhB;EACI;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AACA;EACI;EACA;EACA;EACA;EACA;;AAMA;EACI;EACA;EACA;EACA;EACA;;AACA;EACI;;AAEJ;EACI;EACA;;AAMhB;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAEJ;EACI;;AAGJ;EACI;EACA;;AAKZ;EACI;EACA;;AAEA;EACI;;AAEA;EACI;;AAGJ;EACI;EACA;;AAIR;EACI;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;;AAKJ;EACI;EACA;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAMA;EACI;;AAQhB;EACI;;AAGJ;EACI;EACA;;AAEA;EACI;EACA;EACA;;AAGJ;EACI;EACA;EACA;;AAGJ;AAAA;AAAA;AAAA;EAII;EACA;EACA;;AAGJ;AAAA;AAAA;AAAA;AAAA;AAAA;EAMI;EACA;;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAIA;AAAA;AAAA;AAAA;AAAA;AAAA;EACI;;AAKZ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;;AAGI;EACI;;AAIR;EACI;;AAGJ;AAAA;EAEI;EACA;;AAGJ;EACI;EACA;;AAGJ;EACI;;AAGJ;EACI;;AAKZ;EACI;EACA;EACA;EACA;;AACA;EACI;EACA;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AACA;EACI;;AAKZ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAMR;EACI;EACA;;AAEA;EACI;;AAIR;EACI;EACA;EACA;;AAKZ;EACI;EACA;;AAEA;EACI;EACA;EACA,KA90BC;EA+0BD;EACA;EACA;;AAGI;EACI;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;;AAMA;EACI;;AAMA;EACI;;AAQxB;EACI;;AAIR;EACI;;AAGJ;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAIR;EACI;EACA;EACA;EACA;EACA,eA15BC;EA25BD;;AAEA;EACI;EACA;EACA;;AACA;EACI;;AAGR;EACI;EACA,SAx6BH;;AA06BD;EACI;EACA;EACA;;AAGJ;EACI;;;AAMhB;EACI;IACI;;EAEA;IACI;;EAGJ;IACI;;EAGJ;AAAA;IAEI;IACA;IACA;IACA;IACA;IACA;IACA;;EAQR;IACI;;EAEA;IACI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAGI;IACI;IACA;IACA;IACA;;EAEA;IACI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACI;;EAIR;IACI;;EAGJ;IACI;IACA;;EAGJ;IACI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACI;IACA;IACA;;EAEA;IACI;;EAKZ;IACI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAIR;IACI;IACA;IACA;;EAIR;IACI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACI;;EAMZ;IACI;IACA;IACA;IACA;IACA;;EAEA;IACI;;EAGJ;IACI;;EAIA;IACI;;EAEJ;IACI;;EAGJ;IACI;IACA;IACA;IACA;;EAGJ;IACI;IACA;IACA;IACA;;EAKZ;IACI;IACA;IACA;;EAEA;IACI;IACA;;EAIR;IACI;;EAEA;IACI;;;AAMhB;EAIgB;IACI;;EAEJ;IACI;;EAMI;IACI;IACA;;;AAS5B;EAEQ;IACI;IACA;;EAEJ;IAII;;EAHA;IACI;;;AAOhB;EAEQ;IACI;IACA;IACA;IACA;;EAEA;IACI;IACA;IACA","file":"vs.css"} |
|||
{"version":3,"sourceRoot":"","sources":["vs.scss"],"names":[],"mappings":"AAGA;EACI;EACA;;AAEA;EACI;EACA;;AAIJ;EACI;;AAGJ;EACI;EACA;;AAGJ;EACI;;AAGJ;EACI;;AAGJ;EACI;EACA;EACA;;AAIA;EACI;;AAGJ;AAAA;AAAA;AAAA;EAII;;AAIR;AAAA;EAEI;;AAGJ;EACI;;AAEA;EACI;;AAEJ;EACI;;AAMR;EACI;;AAGJ;EACI;;AAMA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EACI;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EACI;;AAGJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;;AAIJ;EACI;EACA;;AAMZ;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;;AAGA;EACI;EACA;;AAOZ;EAGI;EACA;EACA;;AAGJ;EACI;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEA;EACI;;AAIR;EA5HJ;IA6HQ;IACA;IACA;;EAEA;IACI;IACA;IACA;IACA;;EAEA;IACI;;;AAOhB;EACI;;AAEA;EAHJ;IAIQ;;;;AAMhB;EACI;;AAEA;EACI;EACA;EACA,KA5OK;EA6OL,QA7OK;EA8OL;EACA;EACA;EACA;EACA;EACA;EACA;;AAIQ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;;AAMhB;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;;AAGJ;EACI;EACA;;AAEJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAIR;AAAA;EAEI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;AAAA;AAAA;EAII;;AAIR;EACI;EACA;;AAEJ;EACI;EACA;;AAEA;EACI;;AAIR;EACI;;AAEA;EAII;;AAIR;EACI;EACA;EACA;;AAIR;EACI;;AAIR;EAEI;EACA;;AAEA;EACI;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;;AAKZ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGI;EACI;;AAIR;EACI;;AAIR;EACI;;AAGI;EACI;;AAGJ;EACI;;AAGI;EACI;;AAQhB;EACI;EACA;;AAIA;EACI;EACA;;AAMA;EACI;;AAUhC;EACI;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AACA;EACI;EACA;EACA;EACA;EACA;EACA;;AAMA;EACI;EACA;EACA;EACA;EACA;;AACA;EACI;;AAEJ;EACI;EACA;;AAMhB;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAEJ;EACI;;AAGJ;EACI;EACA;;AAKZ;EACI;EACA;;AAEA;EACI;;AAEA;EACI;;AAGJ;EACI;EACA;;AAIR;EACI;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;;AAKJ;EACI;EACA;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAMA;EACI;;AAQhB;EACI;;AAGJ;EACI;EACA;;AAEA;EACI;EACA;EACA;;AAGJ;EACI;EACA;EACA;;AAGJ;AAAA;AAAA;AAAA;EAII;EACA;EACA;;AAGJ;AAAA;AAAA;AAAA;AAAA;AAAA;EAMI;EACA;;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAIA;AAAA;AAAA;AAAA;AAAA;AAAA;EACI;;AAKZ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;;AAGI;EACI;;AAIR;EACI;;AAGJ;AAAA;EAEI;EACA;;AAGJ;EACI;EACA;;AAGJ;EACI;;AAGJ;EACI;;AAKZ;EACI;EACA;EACA;EACA;;AACA;EACI;EACA;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AACA;EACI;;AAKZ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAMR;EACI;EACA;;AAEA;EACI;;AAIR;EACI;EACA;EACA;;AAKZ;EACI;EACA;;AAEA;EACI;EACA;EACA,KAp1BC;EAq1BD;EACA;EACA;;AAGI;EACI;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;;AAMA;EACI;;AAMA;EACI;;AAQxB;EACI;;AAIR;EACI;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAIR;EACI;EACA;EACA;EACA;EACA,eAl6BC;EAm6BD;;AAEA;EACI;EACA;EACA;;AACA;EACI;;AAGR;EACI;EACA,SAh7BH;;AAk7BD;EACI;EACA;EACA;;AAGJ;EACI;;;AAMhB;EACI;IACI;;EAEA;IACI;;EAGJ;IACI;;EAGJ;AAAA;IAEI;IACA;IACA;IACA;IACA;IACA;IACA;;EAQR;IACI;;EAEA;IACI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAGI;IACI;IACA;IACA;IACA;;EAEA;IACI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACI;;EAIR;IACI;;EAGJ;IACI;IACA;;EAGJ;IACI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACI;IACA;IACA;;EAEA;IACI;;EAKZ;IACI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAIR;IACI;IACA;IACA;;EAIR;IACI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACI;;EAMZ;IACI;IACA;IACA;IACA;IACA;;EAEA;IACI;;EAGJ;IACI;;EAIA;IACI;;EAEJ;IACI;;EAGJ;IACI;IACA;IACA;IACA;;EAGJ;IACI;IACA;IACA;IACA;;EAKZ;IACI;IACA;IACA;;EAEA;IACI;IACA;;EAIR;IACI;;EAEA;IACI;;;AAMhB;EAIgB;IACI;;EAEJ;IACI;;EAMI;IACI;IACA;;;AAS5B;EAEQ;IACI;IACA;;EAEJ;IAII;;EAHA;IACI;;;AAOhB;EAEQ;IACI;IACA;IACA;IACA;;EAEA;IACI;IACA;IACA","file":"vs.css"} |
|||
File diff suppressed because it is too large
@ -1,6 +1,6 @@ |
|||
module.exports = { |
|||
mappings: { |
|||
"@node_modules/@abp/bootstrap-daterangepicker/src/daterangepicker.js": "@libs/bootstrap-daterangepicker/", |
|||
"@node_modules/bootstrap-daterangepicker/daterangepicker.css": "@libs/bootstrap-daterangepicker/", |
|||
"@node_modules/@abp/bootstrap-daterangepicker/src/daterangepicker.css": "@libs/bootstrap-daterangepicker/", |
|||
} |
|||
} |
|||
@ -0,0 +1,410 @@ |
|||
.daterangepicker { |
|||
position: absolute; |
|||
color: inherit; |
|||
background-color: #fff; |
|||
border-radius: 4px; |
|||
border: 1px solid #ddd; |
|||
width: 278px; |
|||
max-width: none; |
|||
padding: 0; |
|||
margin-top: 7px; |
|||
top: 100px; |
|||
left: 20px; |
|||
z-index: 3001; |
|||
display: none; |
|||
font-family: arial; |
|||
font-size: 15px; |
|||
line-height: 1em; |
|||
} |
|||
|
|||
.daterangepicker:before, .daterangepicker:after { |
|||
position: absolute; |
|||
display: inline-block; |
|||
border-bottom-color: rgba(0, 0, 0, 0.2); |
|||
content: ''; |
|||
} |
|||
|
|||
.daterangepicker:before { |
|||
top: -7px; |
|||
border-right: 7px solid transparent; |
|||
border-left: 7px solid transparent; |
|||
border-bottom: 7px solid #ccc; |
|||
} |
|||
|
|||
.daterangepicker:after { |
|||
top: -6px; |
|||
border-right: 6px solid transparent; |
|||
border-bottom: 6px solid #fff; |
|||
border-left: 6px solid transparent; |
|||
} |
|||
|
|||
.daterangepicker.opensleft:before { |
|||
right: 9px; |
|||
} |
|||
|
|||
.daterangepicker.opensleft:after { |
|||
right: 10px; |
|||
} |
|||
|
|||
.daterangepicker.openscenter:before { |
|||
left: 0; |
|||
right: 0; |
|||
width: 0; |
|||
margin-left: auto; |
|||
margin-right: auto; |
|||
} |
|||
|
|||
.daterangepicker.openscenter:after { |
|||
left: 0; |
|||
right: 0; |
|||
width: 0; |
|||
margin-left: auto; |
|||
margin-right: auto; |
|||
} |
|||
|
|||
.daterangepicker.opensright:before { |
|||
left: 9px; |
|||
} |
|||
|
|||
.daterangepicker.opensright:after { |
|||
left: 10px; |
|||
} |
|||
|
|||
.daterangepicker.drop-up { |
|||
margin-top: -7px; |
|||
} |
|||
|
|||
.daterangepicker.drop-up:before { |
|||
top: initial; |
|||
bottom: -7px; |
|||
border-bottom: initial; |
|||
border-top: 7px solid #ccc; |
|||
} |
|||
|
|||
.daterangepicker.drop-up:after { |
|||
top: initial; |
|||
bottom: -6px; |
|||
border-bottom: initial; |
|||
border-top: 6px solid #fff; |
|||
} |
|||
|
|||
.daterangepicker.single .daterangepicker .ranges, .daterangepicker.single .drp-calendar { |
|||
float: none; |
|||
} |
|||
|
|||
.daterangepicker.single .drp-selected { |
|||
display: none; |
|||
} |
|||
|
|||
.daterangepicker.show-calendar .drp-calendar { |
|||
display: block; |
|||
} |
|||
|
|||
.daterangepicker.show-calendar .drp-buttons { |
|||
display: block; |
|||
} |
|||
|
|||
.daterangepicker.auto-apply .drp-buttons { |
|||
display: none; |
|||
} |
|||
|
|||
.daterangepicker .drp-calendar { |
|||
display: none; |
|||
max-width: 270px; |
|||
} |
|||
|
|||
.daterangepicker .drp-calendar.left { |
|||
padding: 8px 0 8px 8px; |
|||
} |
|||
|
|||
.daterangepicker .drp-calendar.right { |
|||
padding: 8px; |
|||
} |
|||
|
|||
.daterangepicker .drp-calendar.single .calendar-table { |
|||
border: none; |
|||
} |
|||
|
|||
.daterangepicker .calendar-table .next span, .daterangepicker .calendar-table .prev span { |
|||
color: #fff; |
|||
border: solid black; |
|||
border-width: 0 2px 2px 0; |
|||
border-radius: 0; |
|||
display: inline-block; |
|||
padding: 3px; |
|||
} |
|||
|
|||
.daterangepicker .calendar-table .next span { |
|||
transform: rotate(-45deg); |
|||
-webkit-transform: rotate(-45deg); |
|||
} |
|||
|
|||
.daterangepicker .calendar-table .prev span { |
|||
transform: rotate(135deg); |
|||
-webkit-transform: rotate(135deg); |
|||
} |
|||
|
|||
.daterangepicker .calendar-table th, .daterangepicker .calendar-table td { |
|||
white-space: nowrap; |
|||
text-align: center; |
|||
vertical-align: middle; |
|||
min-width: 32px; |
|||
width: 32px; |
|||
height: 24px; |
|||
line-height: 24px; |
|||
font-size: 12px; |
|||
border-radius: 4px; |
|||
border: 1px solid transparent; |
|||
white-space: nowrap; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.daterangepicker .calendar-table { |
|||
border: 1px solid #fff; |
|||
border-radius: 4px; |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.daterangepicker .calendar-table table { |
|||
width: 100%; |
|||
margin: 0; |
|||
border-spacing: 0; |
|||
border-collapse: collapse; |
|||
} |
|||
|
|||
.daterangepicker td.available:hover, .daterangepicker th.available:hover { |
|||
background-color: #eee; |
|||
border-color: transparent; |
|||
color: inherit; |
|||
} |
|||
|
|||
.daterangepicker td.week, .daterangepicker th.week { |
|||
font-size: 80%; |
|||
color: #ccc; |
|||
} |
|||
|
|||
.daterangepicker td.off, .daterangepicker td.off.in-range, .daterangepicker td.off.start-date, .daterangepicker td.off.end-date { |
|||
background-color: #fff; |
|||
border-color: transparent; |
|||
color: #999; |
|||
} |
|||
|
|||
.daterangepicker td.in-range { |
|||
background-color: #ebf4f8; |
|||
border-color: transparent; |
|||
color: #000; |
|||
border-radius: 0; |
|||
} |
|||
|
|||
.daterangepicker td.start-date { |
|||
border-radius: 4px 0 0 4px; |
|||
} |
|||
|
|||
.daterangepicker td.end-date { |
|||
border-radius: 0 4px 4px 0; |
|||
} |
|||
|
|||
.daterangepicker td.start-date.end-date { |
|||
border-radius: 4px; |
|||
} |
|||
|
|||
.daterangepicker td.active, .daterangepicker td.active:hover { |
|||
background-color: #357ebd; |
|||
border-color: transparent; |
|||
color: #fff; |
|||
} |
|||
|
|||
.daterangepicker th.month { |
|||
width: auto; |
|||
} |
|||
|
|||
.daterangepicker td.disabled, .daterangepicker option.disabled { |
|||
color: #999; |
|||
cursor: not-allowed; |
|||
text-decoration: line-through; |
|||
} |
|||
|
|||
.daterangepicker select.monthselect, .daterangepicker select.yearselect { |
|||
font-size: 12px; |
|||
padding: 1px; |
|||
height: auto; |
|||
margin: 0; |
|||
cursor: default; |
|||
} |
|||
|
|||
.daterangepicker select.monthselect { |
|||
margin-right: 2%; |
|||
width: 56%; |
|||
} |
|||
|
|||
.daterangepicker select.yearselect { |
|||
width: 40%; |
|||
} |
|||
|
|||
.daterangepicker select.hourselect, .daterangepicker select.minuteselect, .daterangepicker select.secondselect, .daterangepicker select.ampmselect { |
|||
width: 50px; |
|||
margin: 0 auto; |
|||
background: #eee; |
|||
border: 1px solid #eee; |
|||
padding: 2px; |
|||
outline: 0; |
|||
font-size: 12px; |
|||
} |
|||
|
|||
.daterangepicker .calendar-time { |
|||
text-align: center; |
|||
margin: 4px auto 0 auto; |
|||
line-height: 30px; |
|||
position: relative; |
|||
} |
|||
|
|||
.daterangepicker .calendar-time select.disabled { |
|||
color: #ccc; |
|||
cursor: not-allowed; |
|||
} |
|||
|
|||
.daterangepicker .drp-buttons { |
|||
clear: both; |
|||
text-align: right; |
|||
padding: 8px; |
|||
border-top: 1px solid #ddd; |
|||
display: none; |
|||
line-height: 12px; |
|||
vertical-align: middle; |
|||
} |
|||
|
|||
.daterangepicker .drp-selected { |
|||
display: inline-block; |
|||
font-size: 12px; |
|||
padding-right: 8px; |
|||
} |
|||
|
|||
.daterangepicker .drp-buttons .btn { |
|||
margin-left: 8px; |
|||
font-size: 12px; |
|||
font-weight: bold; |
|||
padding: 4px 8px; |
|||
} |
|||
|
|||
.daterangepicker.show-ranges.single.rtl .drp-calendar.left { |
|||
border-right: 1px solid #ddd; |
|||
} |
|||
|
|||
.daterangepicker.show-ranges.single.ltr .drp-calendar.left { |
|||
border-left: 1px solid #ddd; |
|||
} |
|||
|
|||
.daterangepicker.show-ranges.rtl .drp-calendar.right { |
|||
border-right: 1px solid #ddd; |
|||
} |
|||
|
|||
.daterangepicker.show-ranges.ltr .drp-calendar.left { |
|||
border-left: 1px solid #ddd; |
|||
} |
|||
|
|||
.daterangepicker .ranges { |
|||
float: none; |
|||
text-align: left; |
|||
margin: 0; |
|||
} |
|||
|
|||
.daterangepicker.show-calendar .ranges { |
|||
margin-top: 8px; |
|||
} |
|||
|
|||
.daterangepicker .ranges ul { |
|||
list-style: none; |
|||
margin: 0 auto; |
|||
padding: 0; |
|||
width: 100%; |
|||
} |
|||
|
|||
.daterangepicker .ranges li { |
|||
font-size: 12px; |
|||
padding: 8px 12px; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.daterangepicker .ranges li:hover { |
|||
background-color: #eee; |
|||
} |
|||
|
|||
.daterangepicker .ranges li.active { |
|||
background-color: #08c; |
|||
color: #fff; |
|||
} |
|||
|
|||
/* Larger Screen Styling */ |
|||
@media (min-width: 564px) { |
|||
.daterangepicker { |
|||
width: auto; |
|||
} |
|||
|
|||
.daterangepicker .ranges ul { |
|||
width: 140px; |
|||
} |
|||
|
|||
.daterangepicker.single .ranges ul { |
|||
width: 100%; |
|||
} |
|||
|
|||
.daterangepicker.single .drp-calendar.left { |
|||
clear: none; |
|||
} |
|||
|
|||
.daterangepicker.single .ranges, .daterangepicker.single .drp-calendar { |
|||
float: left; |
|||
} |
|||
|
|||
.daterangepicker { |
|||
direction: ltr; |
|||
text-align: left; |
|||
} |
|||
|
|||
.daterangepicker .drp-calendar.left { |
|||
clear: left; |
|||
margin-right: 0; |
|||
} |
|||
|
|||
.daterangepicker .drp-calendar.left .calendar-table { |
|||
border-right: none; |
|||
border-top-right-radius: 0; |
|||
border-bottom-right-radius: 0; |
|||
} |
|||
|
|||
.daterangepicker .drp-calendar.right { |
|||
margin-left: 0; |
|||
} |
|||
|
|||
.daterangepicker .drp-calendar.right .calendar-table { |
|||
border-left: none; |
|||
border-top-left-radius: 0; |
|||
border-bottom-left-radius: 0; |
|||
} |
|||
|
|||
.daterangepicker .drp-calendar.left .calendar-table { |
|||
padding-right: 8px; |
|||
} |
|||
|
|||
.daterangepicker .ranges, .daterangepicker .drp-calendar { |
|||
float: left; |
|||
} |
|||
} |
|||
|
|||
@media (min-width: 730px) { |
|||
.daterangepicker .ranges { |
|||
width: auto; |
|||
} |
|||
|
|||
.daterangepicker .ranges { |
|||
float: left; |
|||
} |
|||
|
|||
.daterangepicker.rtl .ranges { |
|||
float: right; |
|||
} |
|||
|
|||
.daterangepicker .drp-calendar.left { |
|||
clear: none !important; |
|||
} |
|||
} |
|||
@ -1,5 +0,0 @@ |
|||
module.exports = { |
|||
mappings: { |
|||
"@node_modules/@abp/jquery-form/src/jquery.form.min.js": "@libs/jquery-form/" |
|||
} |
|||
} |
|||
@ -1,32 +0,0 @@ |
|||
{ |
|||
"version": "10.7.0-rc.3", |
|||
"name": "@abp/jquery-form", |
|||
"repository": { |
|||
"type": "git", |
|||
"url": "https://github.com/abpframework/abp.git", |
|||
"directory": "npm/packs/jquery-form" |
|||
}, |
|||
"publishConfig": { |
|||
"access": "public" |
|||
}, |
|||
"dependencies": { |
|||
"@abp/jquery": "~10.7.0-rc.3", |
|||
"jquery-form": "^4.3.0" |
|||
}, |
|||
"gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", |
|||
"homepage": "https://abp.io", |
|||
"license": "LGPL-3.0", |
|||
"keywords": [ |
|||
"aspnetcore", |
|||
"boilerplate", |
|||
"framework", |
|||
"web", |
|||
"best-practices", |
|||
"angular", |
|||
"maui", |
|||
"blazor", |
|||
"mvc", |
|||
"csharp", |
|||
"webapp" |
|||
] |
|||
} |
|||
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@ |
|||
module.exports = { |
|||
mappings: { |
|||
"@node_modules/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.js": "@libs/jquery-validation-unobtrusive/" |
|||
"@node_modules/@abp/jquery-validation-unobtrusive/src/jquery.validate.unobtrusive.js": "@libs/jquery-validation-unobtrusive/" |
|||
} |
|||
} |
|||
@ -0,0 +1,436 @@ |
|||
/** |
|||
* @license |
|||
* Unobtrusive validation support library for jQuery and jQuery Validate |
|||
* Copyright (c) .NET Foundation. All rights reserved. |
|||
* Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. |
|||
* @version v4.0.0 |
|||
* Patched copy: uses JSON.parse, typeof and Function.prototype.bind instead of the jQuery APIs removed or deprecated in jQuery 4. |
|||
*/ |
|||
|
|||
/*jslint white: true, browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: false */ |
|||
/*global document: false, jQuery: false */ |
|||
|
|||
(function (factory) { |
|||
if (typeof define === 'function' && define.amd) { |
|||
// AMD. Register as an anonymous module.
|
|||
define("jquery.validate.unobtrusive", ['jquery-validation'], factory); |
|||
} else if (typeof module === 'object' && module.exports) { |
|||
// CommonJS-like environments that support module.exports
|
|||
module.exports = factory(require('jquery-validation')); |
|||
} else { |
|||
// Browser global
|
|||
jQuery.validator.unobtrusive = factory(jQuery); |
|||
} |
|||
}(function ($) { |
|||
var $jQval = $.validator, |
|||
adapters, |
|||
data_validation = "unobtrusiveValidation"; |
|||
|
|||
function setValidationValues(options, ruleName, value) { |
|||
options.rules[ruleName] = value; |
|||
if (options.message) { |
|||
options.messages[ruleName] = options.message; |
|||
} |
|||
} |
|||
|
|||
function splitAndTrim(value) { |
|||
return value.replace(/^\s+|\s+$/g, "").split(/\s*,\s*/g); |
|||
} |
|||
|
|||
function escapeAttributeValue(value) { |
|||
// As mentioned on http://api.jquery.com/category/selectors/
|
|||
return value.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g, "\\$1"); |
|||
} |
|||
|
|||
function getModelPrefix(fieldName) { |
|||
return fieldName.substr(0, fieldName.lastIndexOf(".") + 1); |
|||
} |
|||
|
|||
function appendModelPrefix(value, prefix) { |
|||
if (value.indexOf("*.") === 0) { |
|||
value = value.replace("*.", prefix); |
|||
} |
|||
return value; |
|||
} |
|||
|
|||
function onError(error, inputElement) { // 'this' is the form element
|
|||
var container = $(this).find("[data-valmsg-for='" + escapeAttributeValue(inputElement[0].name) + "']"), |
|||
replaceAttrValue = container.attr("data-valmsg-replace"), |
|||
replace = replaceAttrValue ? JSON.parse(replaceAttrValue) !== false : null; |
|||
|
|||
container.removeClass("field-validation-valid").addClass("field-validation-error"); |
|||
error.data("unobtrusiveContainer", container); |
|||
|
|||
if (replace) { |
|||
container.empty(); |
|||
error.removeClass("input-validation-error").appendTo(container); |
|||
} |
|||
else { |
|||
error.hide(); |
|||
} |
|||
} |
|||
|
|||
function onErrors(event, validator) { // 'this' is the form element
|
|||
var container = $(this).find("[data-valmsg-summary=true]"), |
|||
list = container.find("ul"); |
|||
|
|||
if (list && list.length && validator.errorList.length) { |
|||
list.empty(); |
|||
container.addClass("validation-summary-errors").removeClass("validation-summary-valid"); |
|||
|
|||
$.each(validator.errorList, function () { |
|||
$("<li />").html(this.message).appendTo(list); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
function onSuccess(error) { // 'this' is the form element
|
|||
var container = error.data("unobtrusiveContainer"); |
|||
|
|||
if (container) { |
|||
var replaceAttrValue = container.attr("data-valmsg-replace"), |
|||
replace = replaceAttrValue ? JSON.parse(replaceAttrValue) : null; |
|||
|
|||
container.addClass("field-validation-valid").removeClass("field-validation-error"); |
|||
error.removeData("unobtrusiveContainer"); |
|||
|
|||
if (replace) { |
|||
container.empty(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
function onReset(event) { // 'this' is the form element
|
|||
var $form = $(this), |
|||
key = '__jquery_unobtrusive_validation_form_reset'; |
|||
if ($form.data(key)) { |
|||
return; |
|||
} |
|||
// Set a flag that indicates we're currently resetting the form.
|
|||
$form.data(key, true); |
|||
try { |
|||
$form.data("validator").resetForm(); |
|||
} finally { |
|||
$form.removeData(key); |
|||
} |
|||
|
|||
$form.find(".validation-summary-errors") |
|||
.addClass("validation-summary-valid") |
|||
.removeClass("validation-summary-errors"); |
|||
$form.find(".field-validation-error") |
|||
.addClass("field-validation-valid") |
|||
.removeClass("field-validation-error") |
|||
.removeData("unobtrusiveContainer") |
|||
.find(">*") // If we were using valmsg-replace, get the underlying error
|
|||
.removeData("unobtrusiveContainer"); |
|||
} |
|||
|
|||
function validationInfo(form) { |
|||
var $form = $(form), |
|||
result = $form.data(data_validation), |
|||
onResetProxy = onReset.bind(form), |
|||
defaultOptions = $jQval.unobtrusive.options || {}, |
|||
execInContext = function (name, args) { |
|||
var func = defaultOptions[name]; |
|||
func && typeof func === "function" && func.apply(form, args); |
|||
}; |
|||
|
|||
if (!result) { |
|||
result = { |
|||
options: { // options structure passed to jQuery Validate's validate() method
|
|||
errorClass: defaultOptions.errorClass || "input-validation-error", |
|||
errorElement: defaultOptions.errorElement || "span", |
|||
errorPlacement: function () { |
|||
onError.apply(form, arguments); |
|||
execInContext("errorPlacement", arguments); |
|||
}, |
|||
invalidHandler: function () { |
|||
onErrors.apply(form, arguments); |
|||
execInContext("invalidHandler", arguments); |
|||
}, |
|||
messages: {}, |
|||
rules: {}, |
|||
success: function () { |
|||
onSuccess.apply(form, arguments); |
|||
execInContext("success", arguments); |
|||
} |
|||
}, |
|||
attachValidation: function () { |
|||
$form |
|||
.off("reset." + data_validation) |
|||
.on("reset." + data_validation, onResetProxy) |
|||
.validate(this.options); |
|||
}, |
|||
validate: function () { // a validation function that is called by unobtrusive Ajax
|
|||
$form.validate(); |
|||
return $form.valid(); |
|||
} |
|||
}; |
|||
$form.data(data_validation, result); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
$jQval.unobtrusive = { |
|||
adapters: [], |
|||
|
|||
parseElement: function (element, skipAttach) { |
|||
/// <summary>
|
|||
/// Parses a single HTML element for unobtrusive validation attributes.
|
|||
/// </summary>
|
|||
/// <param name="element" domElement="true">The HTML element to be parsed.</param>
|
|||
/// <param name="skipAttach" type="Boolean">[Optional] true to skip attaching the
|
|||
/// validation to the form. If parsing just this single element, you should specify true.
|
|||
/// If parsing several elements, you should specify false, and manually attach the validation
|
|||
/// to the form when you are finished. The default is false.</param>
|
|||
var $element = $(element), |
|||
form = $element.parents("form")[0], |
|||
valInfo, rules, messages; |
|||
|
|||
if (!form) { // Cannot do client-side validation without a form
|
|||
return; |
|||
} |
|||
|
|||
valInfo = validationInfo(form); |
|||
valInfo.options.rules[element.name] = rules = {}; |
|||
valInfo.options.messages[element.name] = messages = {}; |
|||
|
|||
$.each(this.adapters, function () { |
|||
var prefix = "data-val-" + this.name, |
|||
message = $element.attr(prefix), |
|||
paramValues = {}; |
|||
|
|||
if (message !== undefined) { // Compare against undefined, because an empty message is legal (and falsy)
|
|||
prefix += "-"; |
|||
|
|||
$.each(this.params, function () { |
|||
paramValues[this] = $element.attr(prefix + this); |
|||
}); |
|||
|
|||
this.adapt({ |
|||
element: element, |
|||
form: form, |
|||
message: message, |
|||
params: paramValues, |
|||
rules: rules, |
|||
messages: messages |
|||
}); |
|||
} |
|||
}); |
|||
|
|||
$.extend(rules, { "__dummy__": true }); |
|||
|
|||
if (!skipAttach) { |
|||
valInfo.attachValidation(); |
|||
} |
|||
}, |
|||
|
|||
parse: function (selector) { |
|||
/// <summary>
|
|||
/// Parses all the HTML elements in the specified selector. It looks for input elements decorated
|
|||
/// with the [data-val=true] attribute value and enables validation according to the data-val-*
|
|||
/// attribute values.
|
|||
/// </summary>
|
|||
/// <param name="selector" type="String">Any valid jQuery selector.</param>
|
|||
|
|||
// $forms includes all forms in selector's DOM hierarchy (parent, children and self) that have at least one
|
|||
// element with data-val=true
|
|||
var $selector = $(selector), |
|||
$forms = $selector.parents() |
|||
.addBack() |
|||
.filter("form") |
|||
.add($selector.find("form")) |
|||
.has("[data-val=true]"); |
|||
|
|||
$selector.find("[data-val=true]").each(function () { |
|||
$jQval.unobtrusive.parseElement(this, true); |
|||
}); |
|||
|
|||
$forms.each(function () { |
|||
var info = validationInfo(this); |
|||
if (info) { |
|||
info.attachValidation(); |
|||
} |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
adapters = $jQval.unobtrusive.adapters; |
|||
|
|||
adapters.add = function (adapterName, params, fn) { |
|||
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation.</summary>
|
|||
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
|
|||
/// in the data-val-nnnn HTML attribute (where nnnn is the adapter name).</param>
|
|||
/// <param name="params" type="Array" optional="true">[Optional] An array of parameter names (strings) that will
|
|||
/// be extracted from the data-val-nnnn-mmmm HTML attributes (where nnnn is the adapter name, and
|
|||
/// mmmm is the parameter name).</param>
|
|||
/// <param name="fn" type="Function">The function to call, which adapts the values from the HTML
|
|||
/// attributes into jQuery Validate rules and/or messages.</param>
|
|||
/// <returns type="jQuery.validator.unobtrusive.adapters" />
|
|||
if (!fn) { // Called with no params, just a function
|
|||
fn = params; |
|||
params = []; |
|||
} |
|||
this.push({ name: adapterName, params: params, adapt: fn }); |
|||
return this; |
|||
}; |
|||
|
|||
adapters.addBool = function (adapterName, ruleName) { |
|||
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where
|
|||
/// the jQuery Validate validation rule has no parameter values.</summary>
|
|||
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
|
|||
/// in the data-val-nnnn HTML attribute (where nnnn is the adapter name).</param>
|
|||
/// <param name="ruleName" type="String" optional="true">[Optional] The name of the jQuery Validate rule. If not provided, the value
|
|||
/// of adapterName will be used instead.</param>
|
|||
/// <returns type="jQuery.validator.unobtrusive.adapters" />
|
|||
return this.add(adapterName, function (options) { |
|||
setValidationValues(options, ruleName || adapterName, true); |
|||
}); |
|||
}; |
|||
|
|||
adapters.addMinMax = function (adapterName, minRuleName, maxRuleName, minMaxRuleName, minAttribute, maxAttribute) { |
|||
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where
|
|||
/// the jQuery Validate validation has three potential rules (one for min-only, one for max-only, and
|
|||
/// one for min-and-max). The HTML parameters are expected to be named -min and -max.</summary>
|
|||
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
|
|||
/// in the data-val-nnnn HTML attribute (where nnnn is the adapter name).</param>
|
|||
/// <param name="minRuleName" type="String">The name of the jQuery Validate rule to be used when you only
|
|||
/// have a minimum value.</param>
|
|||
/// <param name="maxRuleName" type="String">The name of the jQuery Validate rule to be used when you only
|
|||
/// have a maximum value.</param>
|
|||
/// <param name="minMaxRuleName" type="String">The name of the jQuery Validate rule to be used when you
|
|||
/// have both a minimum and maximum value.</param>
|
|||
/// <param name="minAttribute" type="String" optional="true">[Optional] The name of the HTML attribute that
|
|||
/// contains the minimum value. The default is "min".</param>
|
|||
/// <param name="maxAttribute" type="String" optional="true">[Optional] The name of the HTML attribute that
|
|||
/// contains the maximum value. The default is "max".</param>
|
|||
/// <returns type="jQuery.validator.unobtrusive.adapters" />
|
|||
return this.add(adapterName, [minAttribute || "min", maxAttribute || "max"], function (options) { |
|||
var min = options.params.min, |
|||
max = options.params.max; |
|||
|
|||
if (min && max) { |
|||
setValidationValues(options, minMaxRuleName, [min, max]); |
|||
} |
|||
else if (min) { |
|||
setValidationValues(options, minRuleName, min); |
|||
} |
|||
else if (max) { |
|||
setValidationValues(options, maxRuleName, max); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
adapters.addSingleVal = function (adapterName, attribute, ruleName) { |
|||
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where
|
|||
/// the jQuery Validate validation rule has a single value.</summary>
|
|||
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
|
|||
/// in the data-val-nnnn HTML attribute(where nnnn is the adapter name).</param>
|
|||
/// <param name="attribute" type="String">[Optional] The name of the HTML attribute that contains the value.
|
|||
/// The default is "val".</param>
|
|||
/// <param name="ruleName" type="String" optional="true">[Optional] The name of the jQuery Validate rule. If not provided, the value
|
|||
/// of adapterName will be used instead.</param>
|
|||
/// <returns type="jQuery.validator.unobtrusive.adapters" />
|
|||
return this.add(adapterName, [attribute || "val"], function (options) { |
|||
setValidationValues(options, ruleName || adapterName, options.params[attribute]); |
|||
}); |
|||
}; |
|||
|
|||
$jQval.addMethod("__dummy__", function (value, element, params) { |
|||
return true; |
|||
}); |
|||
|
|||
$jQval.addMethod("regex", function (value, element, params) { |
|||
var match; |
|||
if (this.optional(element)) { |
|||
return true; |
|||
} |
|||
|
|||
match = new RegExp(params).exec(value); |
|||
return (match && (match.index === 0) && (match[0].length === value.length)); |
|||
}); |
|||
|
|||
$jQval.addMethod("nonalphamin", function (value, element, nonalphamin) { |
|||
var match; |
|||
if (nonalphamin) { |
|||
match = value.match(/\W/g); |
|||
match = match && match.length >= nonalphamin; |
|||
} |
|||
return match; |
|||
}); |
|||
|
|||
if ($jQval.methods.extension) { |
|||
adapters.addSingleVal("accept", "mimtype"); |
|||
adapters.addSingleVal("extension", "extension"); |
|||
} else { |
|||
// for backward compatibility, when the 'extension' validation method does not exist, such as with versions
|
|||
// of JQuery Validation plugin prior to 1.10, we should use the 'accept' method for
|
|||
// validating the extension, and ignore mime-type validations as they are not supported.
|
|||
adapters.addSingleVal("extension", "extension", "accept"); |
|||
} |
|||
|
|||
adapters.addSingleVal("regex", "pattern"); |
|||
adapters.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url"); |
|||
adapters.addMinMax("length", "minlength", "maxlength", "rangelength").addMinMax("range", "min", "max", "range"); |
|||
adapters.addMinMax("minlength", "minlength").addMinMax("maxlength", "minlength", "maxlength"); |
|||
adapters.add("equalto", ["other"], function (options) { |
|||
var prefix = getModelPrefix(options.element.name), |
|||
other = options.params.other, |
|||
fullOtherName = appendModelPrefix(other, prefix), |
|||
element = $(options.form).find(":input").filter("[name='" + escapeAttributeValue(fullOtherName) + "']")[0]; |
|||
|
|||
setValidationValues(options, "equalTo", element); |
|||
}); |
|||
adapters.add("required", function (options) { |
|||
// jQuery Validate equates "required" with "mandatory" for checkbox elements
|
|||
if (options.element.tagName.toUpperCase() !== "INPUT" || options.element.type.toUpperCase() !== "CHECKBOX") { |
|||
setValidationValues(options, "required", true); |
|||
} |
|||
}); |
|||
adapters.add("remote", ["url", "type", "additionalfields"], function (options) { |
|||
var value = { |
|||
url: options.params.url, |
|||
type: options.params.type || "GET", |
|||
data: {} |
|||
}, |
|||
prefix = getModelPrefix(options.element.name); |
|||
|
|||
$.each(splitAndTrim(options.params.additionalfields || options.element.name), function (i, fieldName) { |
|||
var paramName = appendModelPrefix(fieldName, prefix); |
|||
value.data[paramName] = function () { |
|||
var field = $(options.form).find(":input").filter("[name='" + escapeAttributeValue(paramName) + "']"); |
|||
// For checkboxes and radio buttons, only pick up values from checked fields.
|
|||
if (field.is(":checkbox")) { |
|||
return field.filter(":checked").val() || field.filter(":hidden").val() || ''; |
|||
} |
|||
else if (field.is(":radio")) { |
|||
return field.filter(":checked").val() || ''; |
|||
} |
|||
return field.val(); |
|||
}; |
|||
}); |
|||
|
|||
setValidationValues(options, "remote", value); |
|||
}); |
|||
adapters.add("password", ["min", "nonalphamin", "regex"], function (options) { |
|||
if (options.params.min) { |
|||
setValidationValues(options, "minlength", options.params.min); |
|||
} |
|||
if (options.params.nonalphamin) { |
|||
setValidationValues(options, "nonalphamin", options.params.nonalphamin); |
|||
} |
|||
if (options.params.regex) { |
|||
setValidationValues(options, "regex", options.params.regex); |
|||
} |
|||
}); |
|||
adapters.add("fileextensions", ["extensions"], function (options) { |
|||
setValidationValues(options, "extension", options.params.extensions); |
|||
}); |
|||
|
|||
$(function () { |
|||
$jQval.unobtrusive.parse(document); |
|||
}); |
|||
|
|||
return $jQval.unobtrusive; |
|||
})); |
|||
@ -1,129 +0,0 @@ |
|||
## ℹ️ Description |
|||
|
|||
ABP Framework is a complete open-source infrastructure to create modern web applications by following the best practices and conventions of software development. This package is a part of the [ABP Framework](https://abp.io) and contains client-side files. |
|||
For more information, check out the below links: |
|||
|
|||
🔗Official Website: https://abp.io |
|||
|
|||
🔗Commercial Website: https://commercial.abp.io |
|||
|
|||
🔗Commercial Demo: https://commercial.abp.io/demo |
|||
|
|||
🔗GitHub Repository: https://github.com/abpframework/abp |
|||
|
|||
🔗Official Theme: https://www.LeptonTheme.com |
|||
|
|||
🔗Documentation: https://docs.abp.io |
|||
|
|||
🔗Community: https://community.abp.io |
|||
|
|||
🔗Blog: https://blog.abp.io |
|||
|
|||
🔗Books: https://abp.io/books |
|||
|
|||
🔗Twitter: https://twitter.com/abpframework |
|||
|
|||
🔗Discord: https://community.abp.io/discord |
|||
|
|||
🔗Stackoverflow: https://stackoverflow.com/questions/tagged/abp |
|||
|
|||
🔗YouTube: https://www.youtube.com/@Volosoft |
|||
|
|||
|
|||
## 🤔 Why ABP Platform? |
|||
|
|||
Why should you use the ABP.IO Platform instead of creating a new solution from scratch? |
|||
|
|||
You can find the answer here 👉🏻 [Why ABP Platform?](https://docs.abp.io/en/commercial/latest/why-abp-io-platform) |
|||
|
|||
## 🚀 Key Features of the ABP Framework |
|||
|
|||
🟡 Modularity |
|||
|
|||
🟡 Multi-Tenancy |
|||
|
|||
🟡 Bootstrap Tag Helpers |
|||
|
|||
🟡 Dynamic Forms |
|||
|
|||
🟡 Authentication |
|||
|
|||
🟡 Authorization |
|||
|
|||
🟡 Distributed Event Bus |
|||
|
|||
🟡 BLOB Storing |
|||
|
|||
🟡 Text Templating |
|||
|
|||
🟡 Tooling: ABP CLI |
|||
|
|||
🟡 Cross-Cutting Concerns |
|||
|
|||
🟡 Bundling & Minification |
|||
|
|||
🟡 Virtual File System |
|||
|
|||
🟡 Theming |
|||
|
|||
🟡 Background Jobs |
|||
|
|||
🟡 DDD Infrastructure |
|||
|
|||
🟡 Auto REST APIs |
|||
|
|||
🟡 Dynamic Client Proxies |
|||
|
|||
🟡 Multiple Database Providers |
|||
|
|||
🟡 Data filtering |
|||
|
|||
🟡 Test Infrastructure |
|||
|
|||
🟡 Audit Logging |
|||
|
|||
🟡 Object to Object Mapping |
|||
|
|||
🟡 Email & SMS Abstractions |
|||
|
|||
🟡 Localization |
|||
|
|||
🟡 Setting Management |
|||
|
|||
🟡 Extension Methods |
|||
|
|||
🟡 Aspect Oriented Programming |
|||
|
|||
🟡 Dependency Injection |
|||
|
|||
|
|||
## 🧐 How It Works? |
|||
|
|||
The following page explains how you use the ABP.IO Platform as a .NET developer 👉 [How it works?](https://commercial.abp.io/how-it-works) |
|||
|
|||
|
|||
### 📘 Supported Database Providers |
|||
|
|||
🔵 Entity Framework Core |
|||
|
|||
🔵 MongoDB |
|||
|
|||
🔵 Dapper |
|||
|
|||
|
|||
### 🎴 Supported UI Frameworks |
|||
|
|||
🔵 Angular |
|||
|
|||
🔵 Razor Pages |
|||
|
|||
🔵 Blazor Web Assembly |
|||
|
|||
🔵 Blazor Server |
|||
|
|||
🔵 MAUI with Blazor Hybrid |
|||
|
|||
|
|||
## 📫 Bug & Support |
|||
|
|||
Support for open-source ABP Framework client-side packages is available at [GitHub Issues](https://github.com/abpframework/abp/issues), and the commercial support is available at [support.abp.io](https://support.abp.io). |
|||
@ -1,5 +0,0 @@ |
|||
module.exports = { |
|||
mappings: { |
|||
"@node_modules/malihu-custom-scrollbar-plugin/*.*": "@libs/malihu-custom-scrollbar-plugin/" |
|||
} |
|||
} |
|||
@ -1,32 +0,0 @@ |
|||
{ |
|||
"version": "10.7.0-rc.3", |
|||
"name": "@abp/malihu-custom-scrollbar-plugin", |
|||
"repository": { |
|||
"type": "git", |
|||
"url": "https://github.com/abpframework/abp.git", |
|||
"directory": "npm/packs/malihu-custom-scrollbar-plugin" |
|||
}, |
|||
"publishConfig": { |
|||
"access": "public" |
|||
}, |
|||
"dependencies": { |
|||
"@abp/core": "~10.7.0-rc.3", |
|||
"malihu-custom-scrollbar-plugin": "^3.1.5" |
|||
}, |
|||
"gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", |
|||
"homepage": "https://abp.io", |
|||
"license": "LGPL-3.0", |
|||
"keywords": [ |
|||
"aspnetcore", |
|||
"boilerplate", |
|||
"framework", |
|||
"web", |
|||
"best-practices", |
|||
"angular", |
|||
"maui", |
|||
"blazor", |
|||
"mvc", |
|||
"csharp", |
|||
"webapp" |
|||
] |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
module.exports = { |
|||
mappings: { |
|||
"@node_modules/owl.carousel/dist/**/*.*": "@libs/owl.carousel/" |
|||
} |
|||
} |
|||
@ -1,20 +1,19 @@ |
|||
{ |
|||
"version": "10.7.0-rc.3", |
|||
"name": "@abp/toastr", |
|||
"repository": { |
|||
"type": "git", |
|||
"url": "https://github.com/abpframework/abp.git", |
|||
"directory": "npm/packs/toastr" |
|||
}, |
|||
"name": "@abp/owl.carousel", |
|||
"publishConfig": { |
|||
"access": "public" |
|||
}, |
|||
"dependencies": { |
|||
"@abp/jquery": "~10.7.0-rc.3", |
|||
"toastr": "^2.1.4" |
|||
"@abp/core": "~10.7.0-rc.3", |
|||
"owl.carousel": "^2.3.4" |
|||
}, |
|||
"gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", |
|||
"homepage": "https://abp.io", |
|||
"repository": { |
|||
"type": "git", |
|||
"url": "https://github.com/abpframework/abp.git" |
|||
}, |
|||
"license": "LGPL-3.0", |
|||
"keywords": [ |
|||
"aspnetcore", |
|||
@ -1,6 +1,6 @@ |
|||
module.exports = { |
|||
mappings: { |
|||
"@node_modules/timeago/jquery.timeago.js": "@libs/timeago/", |
|||
"@node_modules/timeago/locales/*.*": "@libs/timeago/locales/" |
|||
"@node_modules/timeago.js/dist/*.js": "@libs/timeago/", |
|||
"@node_modules/@abp/timeago/src/*.*": "@libs/abp/timeago/" |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,164 @@ |
|||
var abp = abp || {}; |
|||
(function ($) { |
|||
|
|||
if (typeof timeago === 'undefined') { |
|||
throw "abp/timeago library requires the timeago.js library included to the page!"; |
|||
} |
|||
|
|||
abp.timeago = abp.timeago || {}; |
|||
|
|||
// ABP culture name -> timeago.js locale name (only the ones that don't match the two-letter language code)
|
|||
abp.timeago.localeMap = { |
|||
'en': 'en_US', |
|||
'zh': 'zh_CN', |
|||
'zh-Hans': 'zh_CN', |
|||
'zh-Hant': 'zh_TW', |
|||
'zh-CN': 'zh_CN', |
|||
'zh-TW': 'zh_TW', |
|||
'pt': 'pt_BR', |
|||
'nb': 'nb_NO', |
|||
'no': 'nb_NO', |
|||
'nn': 'nn_NO', |
|||
'hi': 'hi_IN', |
|||
'id': 'id_ID', |
|||
'bn': 'bn_IN' |
|||
}; |
|||
|
|||
abp.timeago.getLocale = function (cultureName) { |
|||
cultureName = cultureName || (abp.localization && abp.localization.currentCulture && abp.localization.currentCulture.cultureName) || 'en'; |
|||
|
|||
if (abp.timeago.localeMap[cultureName]) { |
|||
return abp.timeago.localeMap[cultureName]; |
|||
} |
|||
|
|||
var language = cultureName.split('-')[0]; |
|||
return abp.timeago.localeMap[language] || language; |
|||
}; |
|||
|
|||
abp.timeago.format = function (date, options) { |
|||
return timeago.format(date, abp.timeago.getLocale(), options); |
|||
}; |
|||
|
|||
var toNodeList = function (nodes) { |
|||
if (!nodes) { |
|||
return []; |
|||
} |
|||
|
|||
return Array.prototype.filter.call(nodes.nodeType ? [nodes] : nodes, function (node) { |
|||
return node && node.getAttribute; |
|||
}); |
|||
}; |
|||
|
|||
// <time> elements carry the date in the "datetime" attribute, other elements may carry it in "title"
|
|||
var getDateAttribute = function (node) { |
|||
var datetime = node.getAttribute('datetime'); |
|||
if (datetime || node.tagName === 'TIME') { |
|||
return datetime; |
|||
} |
|||
|
|||
return node.getAttribute('title'); |
|||
}; |
|||
|
|||
abp.timeago.render = function (nodes, options) { |
|||
var nodeList = toNodeList(nodes).filter(function (node) { |
|||
var datetime = getDateAttribute(node); |
|||
if (!datetime) { |
|||
return false; |
|||
} |
|||
|
|||
node.setAttribute('datetime', datetime); |
|||
return true; |
|||
}); |
|||
|
|||
if (!nodeList.length) { |
|||
return nodeList; |
|||
} |
|||
|
|||
return timeago.render(nodeList, abp.timeago.getLocale(), options); |
|||
}; |
|||
|
|||
abp.timeago.cancel = function (nodes) { |
|||
if (nodes === undefined) { |
|||
timeago.cancel(); |
|||
return; |
|||
} |
|||
|
|||
toNodeList(nodes).forEach(function (node) { |
|||
timeago.cancel(node); |
|||
}); |
|||
}; |
|||
|
|||
// Locales that ship with ABP's default languages but are missing in timeago.full.min.js
|
|||
var slavicIndex = function (number, index) { |
|||
return (index % 2 === 1 && number >= 5) ? 1 : 0; |
|||
}; |
|||
|
|||
timeago.register('cs', function (number, index) { |
|||
return [ |
|||
[['právě teď', 'právě teď']], |
|||
[['před %s vteřinami', 'za %s vteřiny'], ['před %s vteřinami', 'za %s vteřin']], |
|||
[['před minutou', 'za minutu']], |
|||
[['před %s minutami', 'za %s minuty'], ['před %s minutami', 'za %s minut']], |
|||
[['před hodinou', 'za hodinu']], |
|||
[['před %s hodinami', 'za %s hodiny'], ['před %s hodinami', 'za %s hodin']], |
|||
[['včera', 'zítra']], |
|||
[['před %s dny', 'za %s dny'], ['před %s dny', 'za %s dnů']], |
|||
[['minulý týden', 'příští týden']], |
|||
[['před %s týdny', 'za %s týdny'], ['před %s týdny', 'za %s týdnů']], |
|||
[['minulý měsíc', 'příští měsíc']], |
|||
[['před %s měsíci', 'za %s měsíce'], ['před %s měsíci', 'za %s měsíců']], |
|||
[['před rokem', 'příští rok']], |
|||
[['před %s lety', 'za %s roky'], ['před %s lety', 'za %s let']] |
|||
][index][slavicIndex(number, index)]; |
|||
}); |
|||
|
|||
timeago.register('sk', function (number, index) { |
|||
return [ |
|||
[['práve teraz', 'práve teraz']], |
|||
[['pred %s sekundami', 'o %s sekundy'], ['pred %s sekundami', 'o %s sekúnd']], |
|||
[['pred minútou', 'o minútu']], |
|||
[['pred %s minútami', 'o %s minúty'], ['pred %s minútami', 'o %s minút']], |
|||
[['pred hodinou', 'o hodinu']], |
|||
[['pred %s hodinami', 'o %s hodiny'], ['pred %s hodinami', 'o %s hodín']], |
|||
[['pred %s dňom', 'o %s deň']], |
|||
[['pred %s dňami', 'o %s dni'], ['pred %s dňami', 'o %s dní']], |
|||
[['pred %s týždňom', 'o %s týždeň']], |
|||
[['pred %s týždňami', 'o %s týždne'], ['pred %s týždňami', 'o %s týždňov']], |
|||
[['pred %s mesiacom', 'o %s mesiac']], |
|||
[['pred %s mesiacmi', 'o %s mesiace'], ['pred %s mesiacmi', 'o %s mesiacov']], |
|||
[['pred %s rokom', 'o %s rok']], |
|||
[['pred %s rokmi', 'o %s roky'], ['pred %s rokmi', 'o %s rokov']] |
|||
][index][slavicIndex(number, index)]; |
|||
}); |
|||
|
|||
if (!$) { |
|||
return; |
|||
} |
|||
|
|||
$.timeago = function (date) { |
|||
if (date && date.jquery) { |
|||
date = date[0]; |
|||
} |
|||
|
|||
if (date && date.nodeType === 1) { |
|||
date = getDateAttribute(date); |
|||
} |
|||
|
|||
return abp.timeago.format(date); |
|||
}; |
|||
|
|||
$.fn.timeago = function (action, options) { |
|||
if (action === 'dispose') { |
|||
abp.timeago.cancel(this.toArray()); |
|||
return this; |
|||
} |
|||
|
|||
if (action === 'update' && options !== undefined && options !== null) { |
|||
this.attr('datetime', options instanceof Date ? options.toISOString() : options); |
|||
} |
|||
|
|||
abp.timeago.render(this.toArray()); |
|||
return this; |
|||
}; |
|||
|
|||
})(window.jQuery); |
|||
@ -1,129 +0,0 @@ |
|||
## ℹ️ Description |
|||
|
|||
ABP Framework is a complete open-source infrastructure to create modern web applications by following the best practices and conventions of software development. This package is a part of the [ABP Framework](https://abp.io) and contains client-side files. |
|||
For more information, check out the below links: |
|||
|
|||
🔗Official Website: https://abp.io |
|||
|
|||
🔗Commercial Website: https://commercial.abp.io |
|||
|
|||
🔗Commercial Demo: https://commercial.abp.io/demo |
|||
|
|||
🔗GitHub Repository: https://github.com/abpframework/abp |
|||
|
|||
🔗Official Theme: https://www.LeptonTheme.com |
|||
|
|||
🔗Documentation: https://docs.abp.io |
|||
|
|||
🔗Community: https://community.abp.io |
|||
|
|||
🔗Blog: https://blog.abp.io |
|||
|
|||
🔗Books: https://abp.io/books |
|||
|
|||
🔗Twitter: https://twitter.com/abpframework |
|||
|
|||
🔗Discord: https://community.abp.io/discord |
|||
|
|||
🔗Stackoverflow: https://stackoverflow.com/questions/tagged/abp |
|||
|
|||
🔗YouTube: https://www.youtube.com/@Volosoft |
|||
|
|||
|
|||
## 🤔 Why ABP Platform? |
|||
|
|||
Why should you use the ABP.IO Platform instead of creating a new solution from scratch? |
|||
|
|||
You can find the answer here 👉🏻 [Why ABP Platform?](https://docs.abp.io/en/commercial/latest/why-abp-io-platform) |
|||
|
|||
## 🚀 Key Features of the ABP Framework |
|||
|
|||
🟡 Modularity |
|||
|
|||
🟡 Multi-Tenancy |
|||
|
|||
🟡 Bootstrap Tag Helpers |
|||
|
|||
🟡 Dynamic Forms |
|||
|
|||
🟡 Authentication |
|||
|
|||
🟡 Authorization |
|||
|
|||
🟡 Distributed Event Bus |
|||
|
|||
🟡 BLOB Storing |
|||
|
|||
🟡 Text Templating |
|||
|
|||
🟡 Tooling: ABP CLI |
|||
|
|||
🟡 Cross-Cutting Concerns |
|||
|
|||
🟡 Bundling & Minification |
|||
|
|||
🟡 Virtual File System |
|||
|
|||
🟡 Theming |
|||
|
|||
🟡 Background Jobs |
|||
|
|||
🟡 DDD Infrastructure |
|||
|
|||
🟡 Auto REST APIs |
|||
|
|||
🟡 Dynamic Client Proxies |
|||
|
|||
🟡 Multiple Database Providers |
|||
|
|||
🟡 Data filtering |
|||
|
|||
🟡 Test Infrastructure |
|||
|
|||
🟡 Audit Logging |
|||
|
|||
🟡 Object to Object Mapping |
|||
|
|||
🟡 Email & SMS Abstractions |
|||
|
|||
🟡 Localization |
|||
|
|||
🟡 Setting Management |
|||
|
|||
🟡 Extension Methods |
|||
|
|||
🟡 Aspect Oriented Programming |
|||
|
|||
🟡 Dependency Injection |
|||
|
|||
|
|||
## 🧐 How It Works? |
|||
|
|||
The following page explains how you use the ABP.IO Platform as a .NET developer 👉 [How it works?](https://commercial.abp.io/how-it-works) |
|||
|
|||
|
|||
### 📘 Supported Database Providers |
|||
|
|||
🔵 Entity Framework Core |
|||
|
|||
🔵 MongoDB |
|||
|
|||
🔵 Dapper |
|||
|
|||
|
|||
### 🎴 Supported UI Frameworks |
|||
|
|||
🔵 Angular |
|||
|
|||
🔵 Razor Pages |
|||
|
|||
🔵 Blazor Web Assembly |
|||
|
|||
🔵 Blazor Server |
|||
|
|||
🔵 MAUI with Blazor Hybrid |
|||
|
|||
|
|||
## 📫 Bug & Support |
|||
|
|||
Support for open-source ABP Framework client-side packages is available at [GitHub Issues](https://github.com/abpframework/abp/issues), and the commercial support is available at [support.abp.io](https://support.abp.io). |
|||
@ -1,5 +0,0 @@ |
|||
module.exports = { |
|||
mappings: { |
|||
"@node_modules/toastr/build/*.*": "@libs/toastr/" |
|||
} |
|||
} |
|||
Loading…
Reference in new issue