Browse Source

Merge branch 'dev' into issue-18042

pull/18051/head
maliming 2 years ago
parent
commit
a549410a22
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json
  2. 5
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
  3. 30
      docs/en/Community-Articles/2023-10-23-NET-8-Feature-containers/POST.md
  4. 53
      docs/en/Community-Articles/2023-10-23-NET-8-Feature-raw-sql-queries-for-unmapped-types/POST.md
  5. 189
      docs/en/Community-Articles/2023-11-05-EF-Core-8-Complex-Types/POST.MD
  6. 11
      docs/en/Modules/Cms-Kit/Blogging.md
  7. 1
      docs/en/Modules/Cms-Kit/Comments.md
  8. 3
      docs/en/Modules/Cms-Kit/Ratings.md
  9. 24
      modules/cms-kit/README.md
  10. 15
      modules/cms-kit/host/Volo.CmsKit.Web.Unified/Pages/Index.cshtml
  11. 4
      modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi.Client/ClientProxies/cms-kit-admin-generate-proxy.json
  12. 3
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Blogs/CreateModal.cshtml
  13. 3
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Blogs/UpdateModal.cshtml
  14. 2
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/Create.cshtml
  15. 2
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/Update.cshtml
  16. 28
      modules/cms-kit/src/Volo.CmsKit.Common.HttpApi.Client/ClientProxies/Volo/CmsKit/Blogs/BlogFeatureClientProxy.Generated.cs
  17. 7
      modules/cms-kit/src/Volo.CmsKit.Common.HttpApi.Client/ClientProxies/Volo/CmsKit/Blogs/BlogFeatureClientProxy.cs
  18. 28
      modules/cms-kit/src/Volo.CmsKit.Common.HttpApi.Client/ClientProxies/Volo/CmsKit/MediaDescriptors/MediaDescriptorClientProxy.Generated.cs
  19. 7
      modules/cms-kit/src/Volo.CmsKit.Common.HttpApi.Client/ClientProxies/Volo/CmsKit/MediaDescriptors/MediaDescriptorClientProxy.cs
  20. 54
      modules/cms-kit/src/Volo.CmsKit.Common.HttpApi.Client/ClientProxies/cms-kit-common-generate-proxy.json
  21. 6
      modules/cms-kit/src/Volo.CmsKit.Common.Web/CmsBlogsWebConsts.cs
  22. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/ar.json
  23. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/cs.json
  24. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/de-DE.json
  25. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/el.json
  26. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json
  27. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/es.json
  28. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/fi.json
  29. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/fr.json
  30. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/hi.json
  31. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/hu.json
  32. 3
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/is.json
  33. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/it.json
  34. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/nl.json
  35. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/pl-PL.json
  36. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/pt-BR.json
  37. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/ro-RO.json
  38. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/ru.json
  39. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/sk.json
  40. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/sl.json
  41. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json
  42. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/vi.json
  43. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/zh-Hans.json
  44. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/zh-Hant.json
  45. 3
      modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Blogs/IBlogPostPublicAppService.cs
  46. 14
      modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Blogs/BlogPostPublicAppService.cs
  47. 8
      modules/cms-kit/src/Volo.CmsKit.Public.HttpApi.Client/ClientProxies/Volo/CmsKit/Public/Blogs/BlogPostPublicClientProxy.Generated.cs
  48. 66
      modules/cms-kit/src/Volo.CmsKit.Public.HttpApi.Client/ClientProxies/cms-kit-generate-proxy.json
  49. 8
      modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Blogs/BlogPostPublicController.cs
  50. 12
      modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebModule.cs
  51. 6
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/CommentingViewComponent.cs
  52. 176
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml
  53. 91
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml
  54. 7
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs
  55. 3
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js
  56. 4
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Tags/Default.cshtml
  57. 9
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/BlogPost.cshtml
  58. 71
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/Index.cshtml
  59. 9
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/Index.cshtml.cs
  60. 14
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/bootstrap-toc.css
  61. 15
      modules/cms-kit/test/Volo.CmsKit.Application.Tests/Blogs/BlogPostPublicAppService_Tests.cs

3
abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json

@ -227,7 +227,8 @@
"Articles": "Articles", "Articles": "Articles",
"Organizations": "Organizations", "Organizations": "Organizations",
"ManageAccount": "Manage Account", "ManageAccount": "Manage Account",
"ABPProfile": "ABP Profile", "CommunityProfile": "Community Profile",
"BlogProfile": "Blog Profile",
"Tickets": "Tickets" "Tickets": "Tickets"
} }
} }

5
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json

@ -249,6 +249,9 @@
"ShareOnLinkedIn": "Share on LinkedIn", "ShareOnLinkedIn": "Share on LinkedIn",
"MoreFrom{0}": "More from {0}", "MoreFrom{0}": "More from {0}",
"SeeAllFrom{0}": "See all from {0}", "SeeAllFrom{0}": "See all from {0}",
"MostWatched": "Most Watched" "MostWatched": "Most Watched",
"Articles({0})": "Articles ({0})",
"Videos({0})": "Videos ({0})",
"LatestArticles": "Latest Articles"
} }
} }

30
docs/en/Community-Articles/2023-10-23-NET-8-Feature-containers/POST.md

@ -0,0 +1,30 @@
# New Containers feature with NET 8.0
This article will show you the new feature of containers with NET 8.0.
## Non-root user
The `Non-root user` feature on net 8 is a security measure that allows users to have limited access to the system without having full administrative privileges. Hosting containers as `non-root` aligns with the principle of least privilege.
It’s free security provided by the operating system. If you run your app as root, your app process can do anything in the container, like modify files, install packages, or run arbitrary executables.
That’s a concern if your app is ever attacked. If you run your app as non-root, your app process cannot do much, greatly limiting what a bad actor could accomplish.
## Default ASP.NET Core port changed from 80 to 8080
In .NET 8, there has been a change in the default port used by ASP.NET Core applications. Previously, the default port assigned to ASP.NET Core applications was `80`. However, starting from .NET 8, the default port has been changed to `8080`.
This change was made to avoid conflicts with other applications and services that commonly use port 80, such as web servers like IIS or Apache. By using port 8080 as the default, there is less potential for clashes and easier deployment of ASP.NET Core applications alongside other services.
It's important to note that this change only affects the default port used when an ASP.NET Core application is run without explicitly specifying a port.
If you want your application to continue using port 80, you can still specify it during the application launch or configure it in the application settings.
* Recommended: Explicitly set the `ASPNETCORE_HTTP_PORTS`, `ASPNETCORE_HTTPS_PORTS``, and `ASPNETCORE_URLS` environment variables to the desired port. Example: `docker run --rm -it -p 9999:80 -e ASPNETCORE_HTTP_PORTS=80 <my-app>``
* Update existing commands and configuration that rely on the expected default port of port 80 to reference port 8080 instead. Example: `docker run --rm -it -p 9999:8080 <my-app>``
> The `dockerfile` of ABP templates has been updated to use port `80`.
## References
- [Secure your .NET cloud apps with rootless Linux Containers](https://devblogs.microsoft.com/dotnet/securing-containers-with-rootless/)
- [Containers breaking changes](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#containers)
- [ASP.NET Core apps use port 8080 by default](https://learn.microsoft.com/en-us/dotnet/core/compatibility/8.0#containers)
- [Docker images for ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/docker/building-net-docker-images?view=aspnetcore-8.0)

53
docs/en/Community-Articles/2023-10-23-NET-8-Feature-raw-sql-queries-for-unmapped-types/POST.md

@ -0,0 +1,53 @@
# New Raw SQL queries for unmapped types feature with EF Core 8.0
## Introduction
I would love to talk about the new feature in EF Core 8.0, specifically the `raw SQL queries for unmapped types`.
This feature was recently introduced by Microsoft and is aimed at providing more flexibility and customization in database queries.
## What is the raw SQL queries for the unmapped types feature?
To give you a better understanding, let's look at a sample repository method with the ABP framework.
Here is an example of a raw SQL query using the new feature:
````csharp
public interface IAuthorRepository : IRepository<Author, Guid>
{
Task<List<AuthorIdWithNames>> GetAllAuthorNamesAsync();
}
public class AuthorIdWithNames
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class EfCoreAuthorRepository : EfCoreRepository<BookStoreDbContext, Author, Guid>, IAuthorRepository
{
public EfCoreAuthorRepository(IDbContextProvider<BookStoreDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public virtual async Task<List<AuthorIdWithNames>> GetAllAuthorNamesAsync()
{
return await (await GetDbContextAsync()).Database.SqlQuery<AuthorIdWithNames>(@$"SELECT Id, Name FROM Authors").ToListAsync();
}
}
````
In this code, we can see that we are using the `SqlQuery` method to execute a raw SQL query on a custom type, `AuthorIdWithNames` in this case. This allows us to retrieve data that may not be mapped to any of our entity classes in the context.
## In summary
This feature can be particularly useful in scenarios where we need to access data from tables or views that are not directly mapped to our entities. It also provides an alternative to using stored procedures for querying data.
However, it's important to note that using raw SQL queries can increase the risk of SQL injection attacks. So, it's recommended to use parameterized queries to prevent this. Additionally, this feature may not work with certain database providers, so it's important to check for compatibility before implementing it.
In conclusion, the raw SQL queries for unmapped types feature in EF Core 8.0 is a great addition for developers looking for more flexibility in database queries. It allows us to work with data that may not be directly mapped to our entities and can be a useful tool in certain scenarios. Just remember to use parameterized queries and check for compatibility before implementing it.
## References
- [Raw SQL queries for unmapped types](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#raw-sql-queries-for-unmapped-types)
- [SQL Queries](https://learn.microsoft.com/en-us/ef/core/querying/sql-queries#querying-scalar-(non-entity)-types)

189
docs/en/Community-Articles/2023-11-05-EF-Core-8-Complex-Types/POST.MD

@ -0,0 +1,189 @@
# Using Complex Types as Value Objects with Entity Framework Core 8.0
Entity Framework Core 8.0 is being shipped in a week as a part of .NET 8.0. In this article, I will introduce the new **[Complex Types](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#value-objects-using-complex-types)** feature of EF Core 8 and show some examples of how you can use it in your projects built with ABP Framework.
## What is a Value Object?
A [Value Object](https://docs.abp.io/en/abp/latest/Value-Objects) is a simple object that has no conceptual identity (Id). Instead, a Value Object is identified by its properties.
A Value Object is typically owned by a parent [Entity](https://docs.abp.io/en/abp/latest/Entities) object. Using [Complex Types](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#value-objects-using-complex-types) with EF Core 8 is the best way to create Value Objects that are stored as a part of your main entity.
## Creating an ABP Project
> **WARNING**: ABP Framework has not a .NET 8.0 compatible version yet. It is planned to be released on November 15, 2023. I created this project with ABP 7.4.1 (based on .NET 7.0), then manually changed the `TargetFramework` (in the `csproj`) to `net8.0` and added the `Microsoft.EntityFrameworkCore.SqlServer` package with version `8.0.0-rc.2.23480.1`. After that, the project is being compiled, but some parts of this article may not work as expected until ABP Framework 8.0-RC.1 is released.
>
> **I will update the article once ABP Framework 8.0-RC.1 is released.**
I will show code examples, so I am creating a new ABP project using the following [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) command:
````bash
abp new ComplexTypeDemo -t app-nolayers
````
> I prefer a non-layered project to keep the things simple. If you are new to ABP Framework, follow the [Getting Started](https://docs.abp.io/en/abp/latest/Getting-Started-Overall) tutorial to learn how to create a new project from scratch.
Once I created the solution, I am running the following command to execute the database migrations in order to create the initial database:
````bash
dotnet run --project ComplexTypeDemo --migrate-database
````
> We could also execute the `migrate-database.ps1` (that is coming as a part of the solution) as a shortcut.
## Creating a Complex Type
Assume that we have a `Customer` [entity](https://docs.abp.io/en/abp/latest/Entities) as shown below:
````csharp
public class Customer : BasicAggregateRoot<Guid>
{
public string Name { get; set; }
public Address HomeAddress { get; set; }
public Address BusinessAddress { get; set; }
}
````
Here, we have two address properties, one for home and the other one for business. Since an address is a multi-values property, we can define it as a separate object:
````csharp
public class Address
{
public string City { get; set; }
public string Line1 { get; set; }
public string? Line2 { get; set; }
public string PostCode { get; set; }
}
````
`Address` is a typical complex object. It is actually a part of the `Customer` object, but we wanted to collect its parts into a dedicated class to make it a domain concept and easily manage its properties together.
## Configure EF Core Mappings
We should set the `Address` class as a Complex Type in our EF Core mapping configuration. There are two ways of it.
As the first way, we can add the `ComplexType` attribute on top of the `Address` class:
````csharp
[ComplexType] // Added this line
public class Address
{
...
}
````
As an alternative, we can configure the mapping using the fluent mapping API. You can write the following code into the `OnModelCreating` method of your `DbContext` class:
````csharp
builder.Entity<Customer>(b =>
{
b.ToTable("Customers");
b.ComplexProperty(x => x.HomeAddress); // Mapping a Complex Type
b.ComplexProperty(x => x.BusinessAddress); // Mapping another Complex Type
//... configure other properties
});
````
You can further configure the properties of the `Address` class:
````csharp
b.ComplexProperty(x => x.HomeAddress, a =>
{
a.Property(x => x.City).HasMaxLength(50).IsRequired();
});
````
Once you configure the mappings, you can use the [EF Core command-line tool](https://learn.microsoft.com/en-us/ef/core/cli/dotnet) to create a database migration:
````bash
dotnet ef migrations add "Added_Customer_And_Address"
````
And update the database:
````bash
dotnet ef database update
````
If you check the fields of the `Customers` table in your dayabase, you will see the following fields:
* `Id`
* `Name`
* `HomeAddress_City`
* `HomeAddress_Line1`
* `HomeAddress_Line2`
* `HomeAddress_PostCode`
* `BusinessAddress_City`
* `BusinessAddress_Line1`
* `BusinessAddress_Line2`
* `BusinessAddress_PostCode`
As you see, EF Core stores the `Address` properties as a part of your main entity.
## Querying Objects
You can query entities from database using the properties of a complex type as same as you query by the properties of the main entity.
**Example: Find customers by `BusinessAddress.PostCode`:**
````csharp
public class MyService : ITransientDependency
{
private readonly IRepository<Customer, Guid> _customerRepository;
public MyService(IRepository<Customer, Guid> customerRepository)
{
_customerRepository = customerRepository;
}
public async Task DemoAsync()
{
var customers = await _customerRepository.GetListAsync(
c => c.BusinessAddress.PostCode == "12345"
);
//...
}
}
````
## Mutable vs Immutable
Entity Framework Core Complex Types can work with mutable and immutable types. In the `Address` example above, I've shown a simple mutable class - that means you can change an individual property of an `Address` object after creating it (or after querying from database). However, designing Value Objects as immutable is a highly common approach.
For example, you can use C#'s `struct` type to define an immutable `Address` type:
````csharp
public readonly struct Address(string line1, string? line2, string city, string postCode)
{
public string City { get; } = city;
public string Line1 { get; } = line1;
public string? Line2 { get; } = line2;
public string PostCode { get; } = postCode;
}
````
See the [Microsoft's documentation](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#mutability) for more examples and different usages.
## Final Notes
There are more details about using Complex Types in your applications. I want to mention some of them here:
* You can have nested complex types. For example, `Address` may have one or more `PhoneNumber` objects as its properties. Everything will work seamlessly.
* A single instance of a Complex Type can be set to multiple properties (of the same or different entities). In that case, changing the Complex object's properties will affect all of the properties. However, try to avoid that since it may create unnecessary complexities in your code that is hard to understand.
* You can manipulate mutable complex object properties just as another property in your entity. EF Core change tracking system will track them as you expect.
* Currently, EF Core doesn't support to have a collection of complex objects in an entity. It works only for properties.
For more details and examples, see the Microsoft's document in the *References* section.
## Source Code
You can find the sample project here:
https://github.com/hikalkan/samples/tree/master/EfCoreComplexTypeDemo
## References
* [Value objects using Complex Types](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#value-objects-using-complex-types) in [What's new with EF Core 8.0](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew) document by Microsoft.
* [ABP Entity Framework Core integration document](https://docs.abp.io/en/abp/latest/Entity-Framework-Core)

11
docs/en/Modules/Cms-Kit/Blogging.md

@ -29,7 +29,16 @@ A screenshot from the new blog creation modal:
![blogs-edit](../../images/cmskit-module-blogs-edit.png) ![blogs-edit](../../images/cmskit-module-blogs-edit.png)
**Slug** is the URL part of the blog. For this example, the root URL of the blog becomes *https://your-domain.com/blogs/technical-blog/*. **Slug** is the URL part of the blog. For this example, the root URL of the blog becomes `your-domain.com/blogs/technical-blog/`.
- You can change the default slug by using `CmsBlogsWebConsts.BlogRoutePrefix` constant. For example, if you set it to `foo`, the root URL of the blog becomes `your-domain.com/foo/technical-blog/`.
```csharp
public override void PreConfigureServices(ServiceConfigurationContext context)
{
CmsBlogsWebConsts.BlogsRoutePrefix = "foo";
}
```
#### Blog Features #### Blog Features

1
docs/en/Modules/Cms-Kit/Comments.md

@ -53,6 +53,7 @@ The comment system provides a commenting [widget](../../UI/AspNetCore/Widgets.md
{ {
entityType = "Product", entityType = "Product",
entityId = "...", entityId = "...",
isReadOnly = false,
referralLinks = new [] {"nofollow"} referralLinks = new [] {"nofollow"}
}) })
``` ```

3
docs/en/Modules/Cms-Kit/Ratings.md

@ -41,7 +41,8 @@ The ratings system provides a rating widget to allow users send ratings to resou
@await Component.InvokeAsync(typeof(RatingViewComponent), new @await Component.InvokeAsync(typeof(RatingViewComponent), new
{ {
entityType = "Product", entityType = "Product",
entityId = "entityId" entityId = "entityId",
isReadOnly = false
}) })
``` ```

24
modules/cms-kit/README.md

@ -0,0 +1,24 @@
# CMS Kit
## Updating Client Proxies
This project have 3 types of client proxies. Before updating client proxies, make sure `Volo.CmsKit.Web.Unified` project is running.
Then you can update Client proxies in 3 different projects. Execute the following commands in the directory of the each project.
- CMS Kit Public (**Volo.CmsKit.Public.HttpApi.Client**)
```bash
abp generate-proxy -t csharp -url https://localhost:44349 -m cms-kit --without-contracts
```
- CMS Kit Common (**Volo.CmsKit.Common.HttpApi.Client**)
```bash
abp generate-proxy -t csharp -url https://localhost:44349 -m cms-kit-common --without-contracts
```
- CMS Kit Admin (**Volo.CmsKit.Admin.HttpApi.Client**)
```bash
abp generate-proxy -t csharp -url https://localhost:44349 -m cms-kit-admin --without-contracts
```

15
modules/cms-kit/host/Volo.CmsKit.Web.Unified/Pages/Index.cshtml

@ -48,7 +48,12 @@
<abp-column size-md="_6"> <abp-column size-md="_6">
@if (GlobalFeatureManager.Instance.IsEnabled<RatingsFeature>()) @if (GlobalFeatureManager.Instance.IsEnabled<RatingsFeature>())
{ {
@await Component.InvokeAsync(typeof(RatingViewComponent), new {entityType = "quote", entityId = "1"}) @await Component.InvokeAsync(typeof(RatingViewComponent), new
{
entityType = "quote",
entityId = "1",
isReadOnly = false
})
} }
</abp-column> </abp-column>
<abp-column size-md="_6"> <abp-column size-md="_6">
@ -60,7 +65,13 @@
<abp-column size-md="_12"> <abp-column size-md="_12">
@if (GlobalFeatureManager.Instance.IsEnabled<CommentsFeature>()) @if (GlobalFeatureManager.Instance.IsEnabled<CommentsFeature>())
{ {
@await Component.InvokeAsync(typeof(CommentingViewComponent), new {entityType = "quote", entityId = "1", referralLinks = new [] {"nofollow"}}) @await Component.InvokeAsync(typeof(CommentingViewComponent), new
{
entityType = "quote",
entityId = "1",
isReadOnly = false,
referralLinks = new [] {"nofollow"}
})
} }
</abp-column> </abp-column>
</abp-row> </abp-row>

4
modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi.Client/ClientProxies/cms-kit-admin-generate-proxy.json

@ -2873,8 +2873,8 @@
"nameOnMethod": "input", "nameOnMethod": "input",
"name": "Status", "name": "Status",
"jsonName": null, "jsonName": null,
"type": "Volo.CmsKit.Blogs.BlogPostStatus?", "type": "System.String",
"typeSimple": "Volo.CmsKit.Blogs.BlogPostStatus?", "typeSimple": "string",
"isOptional": false, "isOptional": false,
"defaultValue": null, "defaultValue": null,
"constraintTypes": null, "constraintTypes": null,

3
modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Blogs/CreateModal.cshtml

@ -9,6 +9,7 @@
@using Volo.Abp.Data @using Volo.Abp.Data
@using Volo.Abp.Localization @using Volo.Abp.Localization
@using Volo.Abp.ObjectExtending @using Volo.Abp.ObjectExtending
@using Volo.CmsKit.Web
@inject IStringLocalizerFactory StringLocalizerFactory @inject IStringLocalizerFactory StringLocalizerFactory
@inherits CmsKitAdminPageBase @inherits CmsKitAdminPageBase
@ -25,7 +26,7 @@
<abp-modal-body> <abp-modal-body>
<abp-input asp-for="ViewModel.Name" /> <abp-input asp-for="ViewModel.Name" />
<abp-input asp-for="ViewModel.Slug" /> <abp-input asp-for="ViewModel.Slug" label-tooltip-icon="fa fa-info-circle" label-tooltip="@L.GetString("BlogSlugInformation", CmsBlogsWebConsts.BlogsRoutePrefix)" />
@foreach (var propertyInfo in ObjectExtensionManager.Instance.GetProperties<CreateModalModel.CreateBlogViewModel>()) @foreach (var propertyInfo in ObjectExtensionManager.Instance.GetProperties<CreateModalModel.CreateBlogViewModel>())
{ {

3
modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Blogs/UpdateModal.cshtml

@ -8,6 +8,7 @@
@using Volo.Abp.Data @using Volo.Abp.Data
@using Volo.Abp.Localization @using Volo.Abp.Localization
@using Volo.Abp.ObjectExtending @using Volo.Abp.ObjectExtending
@using Volo.CmsKit.Web
@inject IStringLocalizerFactory StringLocalizerFactory @inject IStringLocalizerFactory StringLocalizerFactory
@inherits CmsKitAdminPageBase @inherits CmsKitAdminPageBase
@ -26,7 +27,7 @@
<abp-input asp-for="ViewModel.Name" /> <abp-input asp-for="ViewModel.Name" />
<abp-input asp-for="ViewModel.Slug" /> <abp-input asp-for="ViewModel.Slug" label-tooltip-icon="fa fa-info-circle" label-tooltip="@L.GetString("BlogSlugInformation", CmsBlogsWebConsts.BlogsRoutePrefix)" />
@foreach (var propertyInfo in ObjectExtensionManager.Instance.GetProperties<UpdateModalModel.UpdateBlogViewModel>()) @foreach (var propertyInfo in ObjectExtensionManager.Instance.GetProperties<UpdateModalModel.UpdateBlogViewModel>())
{ {

2
modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/Create.cshtml

@ -59,7 +59,7 @@
<form asp-page="/CmsKit/Pages/Create" id="form-page-create"> <form asp-page="/CmsKit/Pages/Create" id="form-page-create">
<abp-input asp-for="@Model.ViewModel.Title" /> <abp-input asp-for="@Model.ViewModel.Title" />
<abp-input asp-for="ViewModel.Slug" title="@L["PageSlugInformation"]" data-toggle="tooltip" /> <abp-input asp-for="ViewModel.Slug" label-tooltip-icon="fa fa-info-circle" label-tooltip="@L["PageSlugInformation"]" />
<abp-input asp-for="@Model.ViewModel.Content" /> <abp-input asp-for="@Model.ViewModel.Content" />

2
modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Pages/Update.cshtml

@ -64,7 +64,7 @@
<abp-input asp-for="@Model.ViewModel.Title" /> <abp-input asp-for="@Model.ViewModel.Title" />
<abp-input asp-for="ViewModel.Slug" title="@L["PageSlugInformation"]" data-toggle="tooltip" /> <abp-input asp-for="ViewModel.Slug" label-tooltip-icon="fa fa-info-circle" label-tooltip="@L["PageSlugInformation"]" />
<abp-input asp-for="@Model.ViewModel.Content" /> <abp-input asp-for="@Model.ViewModel.Content" />

28
modules/cms-kit/src/Volo.CmsKit.Common.HttpApi.Client/ClientProxies/Volo/CmsKit/Blogs/BlogFeatureClientProxy.Generated.cs

@ -0,0 +1,28 @@
// This file is automatically generated by ABP framework to use MVC Controllers from CSharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client;
using Volo.Abp.Http.Client.ClientProxying;
using Volo.Abp.Http.Modeling;
using Volo.CmsKit.Blogs;
// ReSharper disable once CheckNamespace
namespace Volo.CmsKit.Blogs;
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IBlogFeatureAppService), typeof(BlogFeatureClientProxy))]
public partial class BlogFeatureClientProxy : ClientProxyBase<IBlogFeatureAppService>, IBlogFeatureAppService
{
public virtual async Task<BlogFeatureDto> GetOrDefaultAsync(Guid blogId, string featureName)
{
return await RequestAsync<BlogFeatureDto>(nameof(GetOrDefaultAsync), new ClientProxyRequestTypeValue
{
{ typeof(Guid), blogId },
{ typeof(string), featureName }
});
}
}

7
modules/cms-kit/src/Volo.CmsKit.Common.HttpApi.Client/ClientProxies/Volo/CmsKit/Blogs/BlogFeatureClientProxy.cs

@ -0,0 +1,7 @@
// This file is part of BlogFeatureClientProxy, you can customize it here
// ReSharper disable once CheckNamespace
namespace Volo.CmsKit.Blogs;
public partial class BlogFeatureClientProxy
{
}

28
modules/cms-kit/src/Volo.CmsKit.Common.HttpApi.Client/ClientProxies/Volo/CmsKit/MediaDescriptors/MediaDescriptorClientProxy.Generated.cs

@ -0,0 +1,28 @@
// This file is automatically generated by ABP framework to use MVC Controllers from CSharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Content;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client;
using Volo.Abp.Http.Client.ClientProxying;
using Volo.Abp.Http.Modeling;
using Volo.CmsKit.MediaDescriptors;
// ReSharper disable once CheckNamespace
namespace Volo.CmsKit.MediaDescriptors;
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IMediaDescriptorAppService), typeof(MediaDescriptorClientProxy))]
public partial class MediaDescriptorClientProxy : ClientProxyBase<IMediaDescriptorAppService>, IMediaDescriptorAppService
{
public virtual async Task<RemoteStreamContent> DownloadAsync(Guid id)
{
return await RequestAsync<RemoteStreamContent>(nameof(DownloadAsync), new ClientProxyRequestTypeValue
{
{ typeof(Guid), id }
});
}
}

7
modules/cms-kit/src/Volo.CmsKit.Common.HttpApi.Client/ClientProxies/Volo/CmsKit/MediaDescriptors/MediaDescriptorClientProxy.cs

@ -0,0 +1,7 @@
// This file is part of MediaDescriptorClientProxy, you can customize it here
// ReSharper disable once CheckNamespace
namespace Volo.CmsKit.MediaDescriptors;
public partial class MediaDescriptorClientProxy
{
}

54
modules/cms-kit/src/Volo.CmsKit.Common.HttpApi.Client/ClientProxies/cms-kit-common-generate-proxy.json

@ -8,11 +8,32 @@
"controllerName": "MediaDescriptor", "controllerName": "MediaDescriptor",
"controllerGroupName": "MediaDescriptor", "controllerGroupName": "MediaDescriptor",
"isRemoteService": true, "isRemoteService": true,
"isIntegrationService": false,
"apiVersion": null, "apiVersion": null,
"type": "Volo.CmsKit.MediaDescriptors.MediaDescriptorController", "type": "Volo.CmsKit.MediaDescriptors.MediaDescriptorController",
"interfaces": [ "interfaces": [
{ {
"type": "Volo.CmsKit.MediaDescriptors.IMediaDescriptorAppService" "type": "Volo.CmsKit.MediaDescriptors.IMediaDescriptorAppService",
"name": "IMediaDescriptorAppService",
"methods": [
{
"name": "DownloadAsync",
"parametersOnMethod": [
{
"name": "id",
"typeAsString": "System.Guid, System.Private.CoreLib",
"type": "System.Guid",
"typeSimple": "string",
"isOptional": false,
"defaultValue": null
}
],
"returnValue": {
"type": "Volo.Abp.Content.RemoteStreamContent",
"typeSimple": "Volo.Abp.Content.RemoteStreamContent"
}
}
]
} }
], ],
"actions": { "actions": {
@ -59,11 +80,40 @@
"controllerName": "BlogFeature", "controllerName": "BlogFeature",
"controllerGroupName": "BlogFeature", "controllerGroupName": "BlogFeature",
"isRemoteService": true, "isRemoteService": true,
"isIntegrationService": false,
"apiVersion": null, "apiVersion": null,
"type": "Volo.CmsKit.Blogs.BlogFeatureController", "type": "Volo.CmsKit.Blogs.BlogFeatureController",
"interfaces": [ "interfaces": [
{ {
"type": "Volo.CmsKit.Blogs.IBlogFeatureAppService" "type": "Volo.CmsKit.Blogs.IBlogFeatureAppService",
"name": "IBlogFeatureAppService",
"methods": [
{
"name": "GetOrDefaultAsync",
"parametersOnMethod": [
{
"name": "blogId",
"typeAsString": "System.Guid, System.Private.CoreLib",
"type": "System.Guid",
"typeSimple": "string",
"isOptional": false,
"defaultValue": null
},
{
"name": "featureName",
"typeAsString": "System.String, System.Private.CoreLib",
"type": "System.String",
"typeSimple": "string",
"isOptional": false,
"defaultValue": null
}
],
"returnValue": {
"type": "Volo.CmsKit.Blogs.BlogFeatureDto",
"typeSimple": "Volo.CmsKit.Blogs.BlogFeatureDto"
}
}
]
} }
], ],
"actions": { "actions": {

6
modules/cms-kit/src/Volo.CmsKit.Common.Web/CmsBlogsWebConsts.cs

@ -0,0 +1,6 @@
namespace Volo.CmsKit.Web;
public static class CmsBlogsWebConsts
{
public static string BlogsRoutePrefix { get; set; } = "blogs";
}

1
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/ar.json

@ -77,6 +77,7 @@
"PageId": "صفحة", "PageId": "صفحة",
"Pages": "الصفحات", "Pages": "الصفحات",
"PageSlugInformation": "سبيكة تستخدم على url. سيكون عنوان url الخاص بك هو \"/{slug}}\".", "PageSlugInformation": "سبيكة تستخدم على url. سيكون عنوان url الخاص بك هو \"/{slug}}\".",
"BlogSlugInformation": "سبيكة تستخدم على url. سيكون عنوان url الخاص بك هو \"/{0}/{slug}}\".",
"Permission:BlogManagement": "إدارة المدونة", "Permission:BlogManagement": "إدارة المدونة",
"Permission:BlogManagement.Create": "إنشاء", "Permission:BlogManagement.Create": "إنشاء",
"Permission:BlogManagement.Delete": "حذف", "Permission:BlogManagement.Delete": "حذف",

1
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/cs.json

@ -77,6 +77,7 @@
"PageId": "Strana", "PageId": "Strana",
"Pages": "stránky", "Pages": "stránky",
"PageSlugInformation": "Na adrese URL je použit Slug. Vaše adresa URL bude '/{{slug}}'.", "PageSlugInformation": "Na adrese URL je použit Slug. Vaše adresa URL bude '/{{slug}}'.",
"BlogSlugInformation": "Na adrese URL je použit Slug. Vaše adresa URL bude '/{0}/{{slug}}'.",
"Permission:BlogManagement": "Správa blogu", "Permission:BlogManagement": "Správa blogu",
"Permission:BlogManagement.Create": "Vytvořit", "Permission:BlogManagement.Create": "Vytvořit",
"Permission:BlogManagement.Delete": "Vymazat", "Permission:BlogManagement.Delete": "Vymazat",

1
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/de-DE.json

@ -77,6 +77,7 @@
"PageId": "Buchseite", "PageId": "Buchseite",
"Pages": "Seiten", "Pages": "Seiten",
"PageSlugInformation": "Slug wird auf URL verwendet. Ihre URL lautet '/{{slug}}'.", "PageSlugInformation": "Slug wird auf URL verwendet. Ihre URL lautet '/{{slug}}'.",
"BlogSlugInformation": "Slug wird auf URL verwendet. Ihre URL lautet '/{0}/{{slug}}'.",
"Permission:BlogManagement": "Blog-Verwaltung", "Permission:BlogManagement": "Blog-Verwaltung",
"Permission:BlogManagement.Create": "Schaffen", "Permission:BlogManagement.Create": "Schaffen",
"Permission:BlogManagement.Delete": "Löschen", "Permission:BlogManagement.Delete": "Löschen",

1
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/el.json

@ -77,6 +77,7 @@
"PageId": "Σελίδα", "PageId": "Σελίδα",
"Pages": "Σελίδες", "Pages": "Σελίδες",
"PageSlugInformation": "Το Slug χρησιμοποιείται στο url. Το url σας θα είναι '/{{slug}}'.", "PageSlugInformation": "Το Slug χρησιμοποιείται στο url. Το url σας θα είναι '/{{slug}}'.",
"BlogSlugInformation": "Το Slug χρησιμοποιείται στο url. Το url σας θα είναι '/{0}/{{slug}}'.",
"Permission:BlogManagement": "Διαχείριση ιστολογίου", "Permission:BlogManagement": "Διαχείριση ιστολογίου",
"Permission:BlogManagement.Create": "Δημιουργώ", "Permission:BlogManagement.Create": "Δημιουργώ",
"Permission:BlogManagement.Delete": "Διαγράφω", "Permission:BlogManagement.Delete": "Διαγράφω",

1
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json

@ -76,6 +76,7 @@
"PageId": "Page", "PageId": "Page",
"Pages": "Pages", "Pages": "Pages",
"PageSlugInformation": "Slug is used on url. Your url will be '/{{slug}}'.", "PageSlugInformation": "Slug is used on url. Your url will be '/{{slug}}'.",
"BlogSlugInformation": "Slug is used on url. Your url will be '/{0}/{{slug}}'.",
"Permission:BlogManagement": "Blog Management", "Permission:BlogManagement": "Blog Management",
"Permission:BlogManagement.Create": "Create", "Permission:BlogManagement.Create": "Create",
"Permission:BlogManagement.Delete": "Delete", "Permission:BlogManagement.Delete": "Delete",

1
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/es.json

@ -77,6 +77,7 @@
"PageId": "Página", "PageId": "Página",
"Pages": "Paginas", "Pages": "Paginas",
"PageSlugInformation": "Slug se usa en la URL. Su URL será '/{{slug}}'.", "PageSlugInformation": "Slug se usa en la URL. Su URL será '/{{slug}}'.",
"BlogSlugInformation": "Slug se usa en la URL. Su URL será '/{0}/{{slug}}'.",
"Permission:BlogManagement": "Gestión de blogs", "Permission:BlogManagement": "Gestión de blogs",
"Permission:BlogManagement.Create": "Crear", "Permission:BlogManagement.Create": "Crear",
"Permission:BlogManagement.Delete": "Borrar", "Permission:BlogManagement.Delete": "Borrar",

1
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/fi.json

@ -76,6 +76,7 @@
"PageId": "Sivu", "PageId": "Sivu",
"Pages": "Sivut", "Pages": "Sivut",
"PageSlugInformation": "Etanaa käytetään URL-osoitteessa. URL-osoitteesi on '/{{slug}}'.", "PageSlugInformation": "Etanaa käytetään URL-osoitteessa. URL-osoitteesi on '/{{slug}}'.",
"BlogSlugInformation": "Etanaa käytetään URL-osoitteessa. URL-osoitteesi on '/{0}/{{slug}}'.",
"Permission:BlogManagement": "Blogin hallinta", "Permission:BlogManagement": "Blogin hallinta",
"Permission:BlogManagement.Create": "Luonti", "Permission:BlogManagement.Create": "Luonti",
"Permission:BlogManagement.Delete": "Poisto", "Permission:BlogManagement.Delete": "Poisto",

1
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/fr.json

@ -77,6 +77,7 @@
"PageId": "Page", "PageId": "Page",
"Pages": "Pages", "Pages": "Pages",
"PageSlugInformation": "Slug est utilisé sur l'url. Votre URL sera '/{{slug}}'.", "PageSlugInformation": "Slug est utilisé sur l'url. Votre URL sera '/{{slug}}'.",
"BlogSlugInformation": "Slug est utilisé sur l'url. Votre URL sera '/{0}/{{slug}}'.",
"Permission:BlogManagement": "Gestion de blog", "Permission:BlogManagement": "Gestion de blog",
"Permission:BlogManagement.Create": "Créer", "Permission:BlogManagement.Create": "Créer",
"Permission:BlogManagement.Delete": "Effacer", "Permission:BlogManagement.Delete": "Effacer",

1
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/hi.json

@ -77,6 +77,7 @@
"PageId": "पृष्ठ", "PageId": "पृष्ठ",
"Pages": "पृष्ठों", "Pages": "पृष्ठों",
"PageSlugInformation": "स्लग का उपयोग url पर किया जाता है। आपका url '/{{slug}}' होगा।", "PageSlugInformation": "स्लग का उपयोग url पर किया जाता है। आपका url '/{{slug}}' होगा।",
"BlogSlugInformation": "स्लग का उपयोग url पर किया जाता है। आपका url '/{0}/{{slug}}' होगा।",
"Permission:BlogManagement": "ब्लॉग प्रबंधन", "Permission:BlogManagement": "ब्लॉग प्रबंधन",
"Permission:BlogManagement.Create": "सृजन करना", "Permission:BlogManagement.Create": "सृजन करना",
"Permission:BlogManagement.Delete": "हटाएं", "Permission:BlogManagement.Delete": "हटाएं",

1
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/hu.json

@ -76,6 +76,7 @@
"PageId": "oldal", "PageId": "oldal",
"Pages": "Oldalak", "Pages": "Oldalak",
"PageSlugInformation": "A Slug az url-en használatos. Az Ön URL-je a következő lesz: '/{{slug}}'.", "PageSlugInformation": "A Slug az url-en használatos. Az Ön URL-je a következő lesz: '/{{slug}}'.",
"BlogSlugInformation": "A Slug az url-en használatos. Az Ön URL-je a következő lesz: '/{0}/{{slug}}'.",
"Permission:BlogManagement": "Blogkezelés", "Permission:BlogManagement": "Blogkezelés",
"Permission:BlogManagement.Create": "Teremt", "Permission:BlogManagement.Create": "Teremt",
"Permission:BlogManagement.Delete": "Töröl", "Permission:BlogManagement.Delete": "Töröl",

3
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/is.json

@ -76,7 +76,8 @@
"PageDeletionConfirmationMessage": "Ertu viss um að eyða þessari síðu?", "PageDeletionConfirmationMessage": "Ertu viss um að eyða þessari síðu?",
"PageId": "Síða", "PageId": "Síða",
"Pages": "Síður", "Pages": "Síður",
"PageSlugInformation": "Snigill er notaður á slóð. Slóðin þín verður '/síður/{{slug}}'.", "PageSlugInformation": "Snigill er notaður á slóð. Slóðin þín verður '/{{slug}}'.",
"BlogSlugInformation": "Snigill er notaður á slóð. Slóðin þín verður '/{0}/{{slug}}'.",
"Permission:BlogManagement": "Bloggstjórnun", "Permission:BlogManagement": "Bloggstjórnun",
"Permission:BlogManagement.Create": "Búa til", "Permission:BlogManagement.Create": "Búa til",
"Permission:BlogManagement.Delete": "Eyða", "Permission:BlogManagement.Delete": "Eyða",

1
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/it.json

@ -77,6 +77,7 @@
"PageId": "Pagina", "PageId": "Pagina",
"Pages": "Pagine", "Pages": "Pagine",
"PageSlugInformation": "Lo slug viene utilizzato sull'URL. Il tuo URL sarà '/{{slug}}'.", "PageSlugInformation": "Lo slug viene utilizzato sull'URL. Il tuo URL sarà '/{{slug}}'.",
"BlogSlugInformation": "Lo slug viene utilizzato sull'URL. Il tuo URL sarà '/{0}/{{slug}}'.",
"Permission:BlogManagement": "Gestione del blog", "Permission:BlogManagement": "Gestione del blog",
"Permission:BlogManagement.Create": "Crea", "Permission:BlogManagement.Create": "Crea",
"Permission:BlogManagement.Delete": "Elimina", "Permission:BlogManagement.Delete": "Elimina",

1
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/nl.json

@ -77,6 +77,7 @@
"PageId": "Bladzijde", "PageId": "Bladzijde",
"Pages": "Pagina's", "Pages": "Pagina's",
"PageSlugInformation": "Slug wordt gebruikt voor de url. Uw url wordt '/{{slug}}'.", "PageSlugInformation": "Slug wordt gebruikt voor de url. Uw url wordt '/{{slug}}'.",
"BlogSlugInformation": "Slug wordt gebruikt voor de url. Uw url wordt '/{0}/{{slug}}'.",
"Permission:BlogManagement": "Blogbeheer", "Permission:BlogManagement": "Blogbeheer",
"Permission:BlogManagement.Create": "Toevoegen", "Permission:BlogManagement.Create": "Toevoegen",
"Permission:BlogManagement.Delete": "Verwijderen", "Permission:BlogManagement.Delete": "Verwijderen",

1
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/pl-PL.json

@ -77,6 +77,7 @@
"PageId": "Strona", "PageId": "Strona",
"Pages": "Strony", "Pages": "Strony",
"PageSlugInformation": "Slug jest używany na adresie URL. Twój adres URL to „/{{slug}}”.", "PageSlugInformation": "Slug jest używany na adresie URL. Twój adres URL to „/{{slug}}”.",
"BlogSlugInformation": "Slug jest używany na adresie URL. Twój adres URL to „/{0}/{{slug}}”.",
"Permission:BlogManagement": "Zarządzanie blogiem", "Permission:BlogManagement": "Zarządzanie blogiem",
"Permission:BlogManagement.Create": "Tworzyć", "Permission:BlogManagement.Create": "Tworzyć",
"Permission:BlogManagement.Delete": "Kasować", "Permission:BlogManagement.Delete": "Kasować",

1
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/pt-BR.json

@ -77,6 +77,7 @@
"PageId": "Página", "PageId": "Página",
"Pages": "Páginas", "Pages": "Páginas",
"PageSlugInformation": "Slug é usado na url. Sua url será '/{{slug}}'.", "PageSlugInformation": "Slug é usado na url. Sua url será '/{{slug}}'.",
"BlogSlugInformation": "Slug é usado na url. Sua url será '/{0}/{{slug}}'.",
"Permission:BlogManagement": "Gerenciamento de blogs", "Permission:BlogManagement": "Gerenciamento de blogs",
"Permission:BlogManagement.Create": "Criar", "Permission:BlogManagement.Create": "Criar",
"Permission:BlogManagement.Delete": "Excluir", "Permission:BlogManagement.Delete": "Excluir",

1
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/ro-RO.json

@ -77,6 +77,7 @@
"PageId": "Pagina", "PageId": "Pagina",
"Pages": "Pagini", "Pages": "Pagini",
"PageSlugInformation": "Slug este folosit pe url. Url-ul dumneavoastră va fi '/{{slug}}'.", "PageSlugInformation": "Slug este folosit pe url. Url-ul dumneavoastră va fi '/{{slug}}'.",
"BlogSlugInformation": "Slug este folosit pe url. Url-ul dumneavoastră va fi '/{0}/{{slug}}'.",
"Permission:BlogManagement": "Administrare Blog", "Permission:BlogManagement": "Administrare Blog",
"Permission:BlogManagement.Create": "Creează", "Permission:BlogManagement.Create": "Creează",
"Permission:BlogManagement.Delete": "Şterge", "Permission:BlogManagement.Delete": "Şterge",

1
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/ru.json

@ -77,6 +77,7 @@
"PageId": "Страница", "PageId": "Страница",
"Pages": "Страницы", "Pages": "Страницы",
"PageSlugInformation": "Слаг используется для URL. Ваш URL-адрес будет '/{{slug}}'.", "PageSlugInformation": "Слаг используется для URL. Ваш URL-адрес будет '/{{slug}}'.",
"BlogSlugInformation": "Слаг используется для URL. Ваш URL-адрес будет '/{0}/{{slug}}'.",
"Permission:BlogManagement": "Управление блогом", "Permission:BlogManagement": "Управление блогом",
"Permission:BlogManagement.Create": "Создавать", "Permission:BlogManagement.Create": "Создавать",
"Permission:BlogManagement.Delete": "Удалить", "Permission:BlogManagement.Delete": "Удалить",

1
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/sk.json

@ -77,6 +77,7 @@
"PageId": "Stránka", "PageId": "Stránka",
"Pages": "Stránky", "Pages": "Stránky",
"PageSlugInformation": "Slug sa používa v URL. Vaša URL bude '/{{slug}}'.", "PageSlugInformation": "Slug sa používa v URL. Vaša URL bude '/{{slug}}'.",
"BlogSlugInformation": "Slug sa používa v URL. Vaša URL bude '/{0}/{{slug}}'.",
"Permission:BlogManagement": "Správa blogov", "Permission:BlogManagement": "Správa blogov",
"Permission:BlogManagement.Create": "Vytvoriť", "Permission:BlogManagement.Create": "Vytvoriť",
"Permission:BlogManagement.Delete": "Zmazať", "Permission:BlogManagement.Delete": "Zmazať",

1
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/sl.json

@ -77,6 +77,7 @@
"PageId": "stran", "PageId": "stran",
"Pages": "strani", "Pages": "strani",
"PageSlugInformation": "Slug se uporablja na url-ju. Vaš url bo '/{{slug}}'.", "PageSlugInformation": "Slug se uporablja na url-ju. Vaš url bo '/{{slug}}'.",
"BlogSlugInformation": "Slug se uporablja na url-ju. Vaš url bo '/{0}/{{slug}}'.",
"Permission:BlogManagement": "Upravljanje blogov", "Permission:BlogManagement": "Upravljanje blogov",
"Permission:BlogManagement.Create": "Ustvari", "Permission:BlogManagement.Create": "Ustvari",
"Permission:BlogManagement.Delete": "Izbriši", "Permission:BlogManagement.Delete": "Izbriši",

1
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json

@ -76,6 +76,7 @@
"PageId": "Sayfa", "PageId": "Sayfa",
"Pages": "Sayfalar", "Pages": "Sayfalar",
"PageSlugInformation": "Etiket URL'de kullanılır. Url şöyle görünür: '/{{slug}}'.", "PageSlugInformation": "Etiket URL'de kullanılır. Url şöyle görünür: '/{{slug}}'.",
"BlogSlugInformation": "Etiket URL'de kullanılır. Url şöyle görünür: '/{0}/{{slug}}'.",
"Permission:BlogManagement": "Blog Yönetimi", "Permission:BlogManagement": "Blog Yönetimi",
"Permission:BlogManagement.Create": "Oluşturma", "Permission:BlogManagement.Create": "Oluşturma",
"Permission:BlogManagement.Delete": "Silme", "Permission:BlogManagement.Delete": "Silme",

1
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/vi.json

@ -77,6 +77,7 @@
"PageId": "Trang", "PageId": "Trang",
"Pages": "Các trang", "Pages": "Các trang",
"PageSlugInformation": "Slug được sử dụng trên url. Url của bạn sẽ là '/{{slug}}'.", "PageSlugInformation": "Slug được sử dụng trên url. Url của bạn sẽ là '/{{slug}}'.",
"BlogSlugInformation": "Slug được sử dụng trên url. Url của bạn sẽ là '/{0}/{{slug}}'.",
"Permission:BlogManagement": "Quản lý blog", "Permission:BlogManagement": "Quản lý blog",
"Permission:BlogManagement.Create": "Tạo ra", "Permission:BlogManagement.Create": "Tạo ra",
"Permission:BlogManagement.Delete": "Xóa bỏ", "Permission:BlogManagement.Delete": "Xóa bỏ",

1
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/zh-Hans.json

@ -76,6 +76,7 @@
"PageId": "页", "PageId": "页",
"Pages": "页面", "Pages": "页面",
"PageSlugInformation": "Slug用于url. 你的url将是 '/{{slug}}'.", "PageSlugInformation": "Slug用于url. 你的url将是 '/{{slug}}'.",
"BlogSlugInformation": "Slug用于url. 你的url将是 '/{0}/{{slug}}'.",
"Permission:BlogManagement": "博客管理", "Permission:BlogManagement": "博客管理",
"Permission:BlogManagement.Create": "创建", "Permission:BlogManagement.Create": "创建",
"Permission:BlogManagement.Delete": "删除", "Permission:BlogManagement.Delete": "删除",

1
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/zh-Hant.json

@ -77,6 +77,7 @@
"PageId": "頁", "PageId": "頁",
"Pages": "頁面", "Pages": "頁面",
"PageSlugInformation": "Slug用於網址. 你的網址將是 '/{{slug}}'.", "PageSlugInformation": "Slug用於網址. 你的網址將是 '/{{slug}}'.",
"BlogSlugInformation": "Slug用於網址. 你的網址將是 '/{0}/{{slug}}'.",
"Permission:BlogManagement": "部落格管理", "Permission:BlogManagement": "部落格管理",
"Permission:BlogManagement.Create": "創建", "Permission:BlogManagement.Create": "創建",
"Permission:BlogManagement.Delete": "刪除", "Permission:BlogManagement.Delete": "刪除",

3
modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Blogs/IBlogPostPublicAppService.cs

@ -17,5 +17,8 @@ public interface IBlogPostPublicAppService : IApplicationService
Task<PagedResultDto<CmsUserDto>> GetAuthorsHasBlogPostsAsync(BlogPostFilteredPagedAndSortedResultRequestDto input); Task<PagedResultDto<CmsUserDto>> GetAuthorsHasBlogPostsAsync(BlogPostFilteredPagedAndSortedResultRequestDto input);
Task<CmsUserDto> GetAuthorHasBlogPostAsync(Guid id); Task<CmsUserDto> GetAuthorHasBlogPostAsync(Guid id);
Task DeleteAsync(Guid id); Task DeleteAsync(Guid id);
Task<string> GetTagNameAsync([NotNull] Guid tagId);
} }

14
modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Blogs/BlogPostPublicAppService.cs

@ -12,6 +12,7 @@ using Volo.CmsKit.Blogs;
using Volo.CmsKit.Contents; using Volo.CmsKit.Contents;
using Volo.CmsKit.Features; using Volo.CmsKit.Features;
using Volo.CmsKit.GlobalFeatures; using Volo.CmsKit.GlobalFeatures;
using Volo.CmsKit.Tags;
using Volo.CmsKit.Users; using Volo.CmsKit.Users;
namespace Volo.CmsKit.Public.Blogs; namespace Volo.CmsKit.Public.Blogs;
@ -24,12 +25,16 @@ public class BlogPostPublicAppService : CmsKitPublicAppServiceBase, IBlogPostPub
protected IBlogPostRepository BlogPostRepository { get; } protected IBlogPostRepository BlogPostRepository { get; }
protected ITagRepository TagRepository { get; }
public BlogPostPublicAppService( public BlogPostPublicAppService(
IBlogRepository blogRepository, IBlogRepository blogRepository,
IBlogPostRepository blogPostRepository) IBlogPostRepository blogPostRepository,
ITagRepository tagRepository)
{ {
BlogRepository = blogRepository; BlogRepository = blogRepository;
BlogPostRepository = blogPostRepository; BlogPostRepository = blogPostRepository;
TagRepository = tagRepository;
} }
public virtual async Task<BlogPostCommonDto> GetAsync( public virtual async Task<BlogPostCommonDto> GetAsync(
@ -85,4 +90,11 @@ public class BlogPostPublicAppService : CmsKitPublicAppServiceBase, IBlogPostPub
await BlogPostRepository.DeleteAsync(id); await BlogPostRepository.DeleteAsync(id);
} }
public async Task<string> GetTagNameAsync([NotNull] Guid tagId)
{
var tag = await TagRepository.GetAsync(tagId);
return tag.Name;
}
} }

8
modules/cms-kit/src/Volo.CmsKit.Public.HttpApi.Client/ClientProxies/Volo/CmsKit/Public/Blogs/BlogPostPublicClientProxy.Generated.cs

@ -60,4 +60,12 @@ public partial class BlogPostPublicClientProxy : ClientProxyBase<IBlogPostPublic
{ typeof(Guid), id } { typeof(Guid), id }
}); });
} }
public virtual async Task<string> GetTagNameAsync(Guid tagId)
{
return await RequestAsync<string>(nameof(GetTagNameAsync), new ClientProxyRequestTypeValue
{
{ typeof(Guid), tagId }
});
}
} }

66
modules/cms-kit/src/Volo.CmsKit.Public.HttpApi.Client/ClientProxies/cms-kit-generate-proxy.json

@ -1512,6 +1512,23 @@
"type": "System.Void", "type": "System.Void",
"typeSimple": "System.Void" "typeSimple": "System.Void"
} }
},
{
"name": "GetTagNameAsync",
"parametersOnMethod": [
{
"name": "tagId",
"typeAsString": "System.Guid, System.Private.CoreLib",
"type": "System.Guid",
"typeSimple": "string",
"isOptional": false,
"defaultValue": null
}
],
"returnValue": {
"type": "System.String",
"typeSimple": "string"
}
} }
] ]
} }
@ -1825,6 +1842,55 @@
}, },
"allowAnonymous": null, "allowAnonymous": null,
"implementFrom": "Volo.CmsKit.Public.Blogs.IBlogPostPublicAppService" "implementFrom": "Volo.CmsKit.Public.Blogs.IBlogPostPublicAppService"
},
"GetTagNameAsyncByTagId": {
"uniqueName": "GetTagNameAsyncByTagId",
"name": "GetTagNameAsync",
"httpMethod": "GET",
"url": "api/cms-kit-public/blog-posts/tags/{id}",
"supportedVersions": [],
"parametersOnMethod": [
{
"name": "tagId",
"typeAsString": "System.Guid, System.Private.CoreLib",
"type": "System.Guid",
"typeSimple": "string",
"isOptional": false,
"defaultValue": null
}
],
"parameters": [
{
"nameOnMethod": "tagId",
"name": "tagId",
"jsonName": null,
"type": "System.Guid",
"typeSimple": "string",
"isOptional": false,
"defaultValue": null,
"constraintTypes": null,
"bindingSourceId": "ModelBinding",
"descriptorName": ""
},
{
"nameOnMethod": "id",
"name": "id",
"jsonName": null,
"type": null,
"typeSimple": null,
"isOptional": false,
"defaultValue": null,
"constraintTypes": [],
"bindingSourceId": "Path",
"descriptorName": ""
}
],
"returnValue": {
"type": "System.String",
"typeSimple": "string"
},
"allowAnonymous": null,
"implementFrom": "Volo.CmsKit.Public.Blogs.IBlogPostPublicAppService"
} }
} }
} }

8
modules/cms-kit/src/Volo.CmsKit.Public.HttpApi/Volo/CmsKit/Public/Blogs/BlogPostPublicController.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
@ -60,4 +61,11 @@ public class BlogPostPublicController : CmsKitPublicControllerBase, IBlogPostPub
{ {
return BlogPostPublicAppService.DeleteAsync(id); return BlogPostPublicAppService.DeleteAsync(id);
} }
[HttpGet]
[Route("tags/{id}")]
public Task<string> GetTagNameAsync([NotNull] Guid tagId)
{
return BlogPostPublicAppService.GetTagNameAsync(tagId);
}
} }

12
modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebModule.cs

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc.RazorPages; using System;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.Localization;
@ -91,8 +92,13 @@ public class CmsKitPublicWebModule : AbpModule
{ {
Configure<RazorPagesOptions>(options => Configure<RazorPagesOptions>(options =>
{ {
options.Conventions.AddPageRoute("/Public/CmsKit/Blogs/Index", @"/blogs/{blogSlug:minlength(1)}"); options.Conventions.AddPageRoute(
options.Conventions.AddPageRoute("/Public/CmsKit/Blogs/BlogPost", @"/blogs/{blogSlug}/{blogPostSlug:minlength(1)}"); "/Public/CmsKit/Blogs/Index",
CmsBlogsWebConsts.BlogsRoutePrefix.EnsureStartsWith('/') + @"/{blogSlug:minlength(1)}");
options.Conventions.AddPageRoute(
"/Public/CmsKit/Blogs/BlogPost",
CmsBlogsWebConsts.BlogsRoutePrefix.EnsureStartsWith('/') + @"/{blogSlug}/{blogPostSlug:minlength(1)}");
}); });
} }

6
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/CommentingViewComponent.cs

@ -60,6 +60,7 @@ public class CommentingViewComponent : AbpViewComponent
public virtual async Task<IViewComponentResult> InvokeAsync( public virtual async Task<IViewComponentResult> InvokeAsync(
string entityType, string entityType,
string entityId, string entityId,
bool isReadOnly,
IEnumerable<string> referralLinks = null) IEnumerable<string> referralLinks = null)
{ {
referralLinks ??= Enumerable.Empty<string>(); referralLinks ??= Enumerable.Empty<string>();
@ -74,7 +75,8 @@ public class CommentingViewComponent : AbpViewComponent
EntityType = entityType, EntityType = entityType,
ReferralLinks = referralLinks, ReferralLinks = referralLinks,
LoginUrl = loginUrl, LoginUrl = loginUrl,
Comments = comments.OrderByDescending(i => i.CreationTime).ToList() IsReadOnly = isReadOnly,
Comments = comments.OrderByDescending(i => i.CreationTime).ToList(),
}; };
await ConvertMarkdownTextsToHtml(viewModel); await ConvertMarkdownTextsToHtml(viewModel);
@ -144,6 +146,8 @@ public class CommentingViewComponent : AbpViewComponent
public string Captcha { get; set; } public string Captcha { get; set; }
public string CaptchaImageBase64 { get; set; } public string CaptchaImageBase64 { get; set; }
public bool IsReadOnly { get; set; }
} }
} }

176
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml

@ -20,16 +20,16 @@
@<span> @<span>
<i class="far fa-comment-alt me-2"></i> <i class="far fa-comment-alt me-2"></i>
@((string.IsNullOrWhiteSpace(author.Name) @((string.IsNullOrWhiteSpace(author.Name)
? author.UserName ? author.UserName
: author.Name + " " + author.Surname).Trim()) : author.Name + " " + author.Surname).Trim())
<small class="text-muted float-end" style="opacity: .5; font-size: 14px">@creationTime.ToString()</small> <small class="text-muted float-end" style="opacity: .5; font-size: 14px">@creationTime.ToString()</small>
</span>; </span>;
} }
@{ @{
Func<dynamic, IHtmlContent> GetCommentArea(Guid? repliedCommentId, bool cancelButton = false) => Func<dynamic, IHtmlContent> GetCommentArea(Guid? repliedCommentId, bool cancelButton = false) =>
@<div class="cms-comment-form-area bg-light card p-3 mx-0 @(repliedCommentId.HasValue ? "my-3" : "mt-3")" @<div class="cms-comment-form-area bg-light card p-3 mx-0 @(repliedCommentId.HasValue ? "my-3" : "mt-3")"
data-reply-id="@(repliedCommentId?.ToString() ?? "")" data-reply-id="@(repliedCommentId?.ToString() ?? "")"
style="@(string.IsNullOrEmpty(repliedCommentId?.ToString() ?? "") ? "" : "display:none")"> style="@(string.IsNullOrEmpty(repliedCommentId?.ToString() ?? "") ? "" : "display:none")">
<form class="cms-comment-form"> <form class="cms-comment-form">
<input hidden value="@(Guid.NewGuid().ToString("N"))" name="idempotencyToken" /> <input hidden value="@(Guid.NewGuid().ToString("N"))" name="idempotencyToken" />
<input hidden value="@(repliedCommentId?.ToString() ?? "")" name="repliedCommentId" /> <input hidden value="@(repliedCommentId?.ToString() ?? "")" name="repliedCommentId" />
@ -44,31 +44,31 @@
</div> </div>
@if (CmsKitCommentOptions.Value.IsRecaptchaEnabled) @if (CmsKitCommentOptions.Value.IsRecaptchaEnabled)
{ {
<div class="volo-captcha"> <div class="volo-captcha">
<label class="form-label" for="Input_Captcha">@L["CaptchaCode"]</label> <label class="form-label" for="Input_Captcha">@L["CaptchaCode"]</label>
<div class="d-flex"> <div class="d-flex">
<div class="bd-highlight"> <div class="bd-highlight">
<img src="@Model.Input.CaptchaImageBase64" /> <img src="@Model.Input.CaptchaImageBase64" />
</div> </div>
<div class="flex-grow-1 bd-highlight"> <div class="flex-grow-1 bd-highlight">
<abp-input type="number" asp-for="@Model.Input.Captcha" suppress-label="true" class="d-inline-block" autocomplete="off" /> <abp-input type="number" asp-for="@Model.Input.Captcha" suppress-label="true" class="d-inline-block" autocomplete="off" />
</div>
<abp-input asp-for="@Model.CaptchaId" value="@Model.CaptchaOutput.Id" />
</div> </div>
<abp-input asp-for="@Model.CaptchaId" value="@Model.CaptchaOutput.Id" />
</div> </div>
} </div>
}
<div class="col-auto"> <div class="col-auto">
<div class="text-end"> <div class="text-end">
<abp-button id="submit-button" type="submit" button-type="Primary"> <abp-button id="submit-button" type="submit" button-type="Primary">
<i class="fa fa-comment-alt me-1"></i> @L["Send"] <i class="fa fa-comment-alt me-1"></i> @L["Send"]
</abp-button> </abp-button>
@if (cancelButton) @if (cancelButton)
{ {
<abp-button type="button" button-type="Light" class="reply-cancel-button" data-reply-id="@(repliedCommentId?.ToString() ?? "")"> <abp-button type="button" button-type="Light" class="reply-cancel-button" data-reply-id="@(repliedCommentId?.ToString() ?? "")">
<i class="fa fa-times me-1"></i> @L["Cancel"] <i class="fa fa-times me-1"></i> @L["Cancel"]
</abp-button> </abp-button>
} }
</div> </div>
</div> </div>
</div> </div>
@ -87,32 +87,32 @@
Func<dynamic, IHtmlContent> GetCommentActionArea(Guid id, Guid authorId, bool isReply) => Func<dynamic, IHtmlContent> GetCommentActionArea(Guid id, Guid authorId, bool isReply) =>
@<div class="inner-comment-buttons text-start"> @<div class="inner-comment-buttons text-start">
@if (!isReply) @if (!isReply)
{ {
@if (CurrentUser.IsAuthenticated) @if (CurrentUser.IsAuthenticated)
{ {
<a href="#" class="comment-links comment-reply-link btn btn-sm shadow-sm btn-primary" data-reply-id="@id.ToString()" id="@($"cms-comment_{Model.Input.EntityType}_{Model.Input.EntityId}_{id}_link")"> <a href="#" class="comment-links comment-reply-link btn btn-sm shadow-sm btn-primary" data-reply-id="@id.ToString()" id="@($"cms-comment_{Model.Input.EntityType}_{Model.Input.EntityId}_{id}_link")">
<i class="fa fa-reply mr -1"></i> @L["Reply"] <i class="fa fa-reply mr -1"></i> @L["Reply"]
</a> </a>
<a href="#" class="comment-links comment-delete-link btn btn-sm shadow-sm btn-link text-muted bg-white " data-author-id="@authorId.ToString()" data-id="@id.ToString()">
<i class="fa fa-trash mr -1"></i> @L["Delete"]
</a>
}
else
{
<a href="@(Model.Input.LoginUrl + "_" + id)" class="btn btn-sm btn-light shadow-sm">
@L["LoginToReply"]
</a>
}
}
<a href="#" class="comment-links comment-delete-link btn btn-sm shadow-sm btn-link text-muted bg-white " data-author-id="@authorId.ToString()" data-id="@id.ToString()">
<i class="fa fa-trash mr -1"></i> @L["Delete"]
</a>
}
else
{
<a href="@(Model.Input.LoginUrl + "_" + id)" class="btn btn-sm btn-light shadow-sm">
@L["LoginToReply"]
</a>
}
}
@if (authorId == CurrentUser.Id) @if (authorId == CurrentUser.Id)
{ {
<a href="#" class="comment-links comment-edit-link btn btn-sm shadow-sm btn-link text-muted bg-white" data-id="@id.ToString()"> <a href="#" class="comment-links comment-edit-link btn btn-sm shadow-sm btn-link text-muted bg-white" data-id="@id.ToString()">
<i class="fa fa-pencil mr -1 "></i> @L["Edit"] <i class="fa fa-pencil mr -1 "></i> @L["Edit"]
</a> </a>
} }
</div>; </div>;
} }
@{ @{
@ -129,26 +129,26 @@
</div> </div>
</div> </div>
<div class="mt-0"> <div class="mt-0">
<small class="text-muted float-end" >@L["MarkdownSupported"]</small> <small class="text-muted float-end">@L["MarkdownSupported"]</small>
</div> </div>
@if (CmsKitCommentOptions.Value.IsRecaptchaEnabled) @if (CmsKitCommentOptions.Value.IsRecaptchaEnabled)
{ {
var output = await Model.GenerateCaptchaAsync(); var output = await Model.GenerateCaptchaAsync();
<div class="volo-captcha"> <div class="volo-captcha">
<label class="form-label" for="Input_Captcha_@output.Id">@L["CaptchaCode"]</label> <label class="form-label" for="Input_Captcha_@output.Id">@L["CaptchaCode"]</label>
<div class="d-flex"> <div class="d-flex">
<div class="bd-highlight"> <div class="bd-highlight">
<img src="@Model.GetCaptchaImageBase64(output.ImageBytes)"/> <img src="@Model.GetCaptchaImageBase64(output.ImageBytes)" />
</div> </div>
<div class="flex-grow-1 bd-highlight"> <div class="flex-grow-1 bd-highlight">
<abp-input id="Input_Captcha_@output.Id" type="number" asp-for="@Model.Input.Captcha" suppress-label="true" class="d-inline-block" autocomplete="off"/> <abp-input id="Input_Captcha_@output.Id" type="number" asp-for="@Model.Input.Captcha" suppress-label="true" class="d-inline-block" autocomplete="off" />
</div>
<abp-input asp-for="@Model.CaptchaId" value="@output.Id"/>
</div> </div>
<abp-input asp-for="@Model.CaptchaId" value="@output.Id" />
</div> </div>
} </div>
}
<div class="col-auto"> <div class="col-auto">
<div class="text-end"> <div class="text-end">
<abp-button type="submit" button-type="Primary" size="Block"> @L["Update"] </abp-button> <abp-button type="submit" button-type="Primary" size="Block"> @L["Update"] </abp-button>
@ -184,22 +184,25 @@
@GetCommentContentArea(comment.Id, comment.Text).Invoke(null) @GetCommentContentArea(comment.Id, comment.Text).Invoke(null)
<div class="row mt-2"> @if (!Model.Input.IsReadOnly)
<div class="col"> {
<div class="my-2 "> <div class="row mt-2">
@GetCommentActionArea(comment.Id, comment.Author.Id, false).Invoke(null) <div class="col">
<div class="my-2 ">
@GetCommentActionArea(comment.Id, comment.Author.Id, false).Invoke(null)
</div>
</div> </div>
</div> <div class="col-auto">
<div class="col-auto"> <div class="reaction-in-comment">
<div class="reaction-in-comment"> @if (cmsKitUiOptions.Value.CommentsOptions.IsReactionsEnabled && GlobalFeatureManager.Instance.IsEnabled<ReactionsFeature>())
@if (cmsKitUiOptions.Value.CommentsOptions.IsReactionsEnabled && GlobalFeatureManager.Instance.IsEnabled<ReactionsFeature>()) {
{ @await Component.InvokeAsync(typeof(ReactionSelectionViewComponent), new { entityType = "comment", entityId = comment.Id.ToString() })
@await Component.InvokeAsync(typeof(ReactionSelectionViewComponent), new { entityType = "comment", entityId = comment.Id.ToString() }) }
} </div>
</div> </div>
</div> </div>
</div> @GetEditArea(comment.Id, Model.Input.RawCommentTexts[comment.Id], comment.ConcurrencyStamp).Invoke(null)
@GetEditArea(comment.Id, Model.Input.RawCommentTexts[comment.Id], comment.ConcurrencyStamp).Invoke(null) }
@if (comment.Replies.Any()) @if (comment.Replies.Any())
{ {
@ -213,23 +216,26 @@
@GetCommentContentArea(reply.Id, reply.Text).Invoke(null) @GetCommentContentArea(reply.Id, reply.Text).Invoke(null)
<div class="row mt-2"> @if (!Model.Input.IsReadOnly)
<div class="col"> {
<div class="my-2 "> <div class="row mt-2">
@GetCommentActionArea(reply.Id, reply.Author.Id, true).Invoke(null) <div class="col">
<div class="my-2 ">
@GetCommentActionArea(reply.Id, reply.Author.Id, true).Invoke(null)
</div>
</div> </div>
</div> <div class="col-auto">
<div class="col-auto"> <div class="reaction-in-comment">
<div class="reaction-in-comment"> @if (cmsKitUiOptions.Value.CommentsOptions.IsReactionsEnabled && GlobalFeatureManager.Instance.IsEnabled<ReactionsFeature>())
@if (cmsKitUiOptions.Value.CommentsOptions.IsReactionsEnabled && GlobalFeatureManager.Instance.IsEnabled<ReactionsFeature>()) {
{ @await Component.InvokeAsync(typeof(ReactionSelectionViewComponent), new { entityType = "comment", entityId = reply.Id.ToString() })
@await Component.InvokeAsync(typeof(ReactionSelectionViewComponent), new { entityType = "comment", entityId = reply.Id.ToString() }) }
} </div>
</div> </div>
</div> </div>
</div>
@GetEditArea(reply.Id, Model.Input.RawCommentTexts[reply.Id], reply.ConcurrencyStamp).Invoke(null) @GetEditArea(reply.Id, Model.Input.RawCommentTexts[reply.Id], reply.ConcurrencyStamp).Invoke(null)
}
</div> </div>
</div> </div>
} }

91
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/Default.cshtml

@ -6,58 +6,59 @@
@inject IHtmlLocalizer<CmsKitResource> L @inject IHtmlLocalizer<CmsKitResource> L
<div class="row row"> <div class="row row">
<div class="col"> <div class="col">
<div class="cms-rating-area" data-entity-type="@Model.EntityType" data-entity-id="@Model.EntityId" id="cms-rating_{@Model.EntityType}_{@Model.EntityId}"> <div class="cms-rating-area" data-entity-type="@Model.EntityType" data-entity-id="@Model.EntityId" id="cms-rating_{@Model.EntityType}_{@Model.EntityId}">
@if (CurrentUser.IsAuthenticated)
@if (CurrentUser.IsAuthenticated)
{
@if (!Model.IsReadOnly && Model.CurrentRating != null)
{ {
@if (Model.CurrentRating != null) <a href="#" class="rating-undo-link text-decoration-none">
{ <small class="text-muted"><i class="fa fa-undo"></i> @L["Undo"]</small>
<a href="#" class="rating-undo-link text-decoration-none"> </a>
<small class="text-muted"><i class="fa fa-undo"></i> @L["Undo"]</small> }
</a> if (Model.Ratings != null)
} {
if (Model.Ratings != null) <a href="#" class="text-muted ms-1 text-decoration-none" data-bs-toggle="modal" data-bs-target="#ratingDetail">
{ <i class="far fa-question-circle"></i>
<a href="#" class="text-muted ms-1 text-decoration-none" data-bs-toggle="modal" data-bs-target="#ratingDetail"> </a>
<i class="far fa-question-circle"></i>
</a>
<div class="modal fade" id="ratingDetail" tabindex="-1" role="dialog" aria-labelledby="ratingDetail" aria-hidden="true"> <div class="modal fade" id="ratingDetail" tabindex="-1" role="dialog" aria-labelledby="ratingDetail" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document"> <div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Rating Detail</h5> <h5 class="modal-title" id="exampleModalLabel">Rating Detail</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="row text-center"> <div class="row text-center">
@foreach (var rating in Model.Ratings) @foreach (var rating in Model.Ratings)
{ {
<div class="col"> <div class="col">
<label>@rating.StarCount @L["Star"]</label> <label>@rating.StarCount @L["Star"]</label>
<div class="bar-container"> <div class="bar-container">
<div class="bar bar-@rating.StarCount" style="width: @(rating.Count * 100 / Model.TotalRating)%"></div> <div class="bar bar-@rating.StarCount" style="width: @(rating.Count * 100 / Model.TotalRating)%"></div>
</div>
<div><small class="text-muted">@rating.Count Rate(s)</small> </div>
</div> </div>
} <div><small class="text-muted">@rating.Count Rate(s)</small> </div>
</div> </div>
}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
} </div>
}
<small class="live-rating text-center d-inline-block" style="width: 24px">@(Model.CurrentRating != null ? Model.CurrentRating + " " : 0 + "")</small> <small class="live-rating text-center d-inline-block" style="width: 24px">@(Model.CurrentRating != null ? Model.CurrentRating + " " : 0 + "")</small>
<span class="my-rating text-dark p-1" data-rating="@(Model.CurrentRating ?? 0)" data-authenticated="@(Model.CurrentRating != null)"> <span class="my-rating text-dark p-1" data-rating="@(Model.CurrentRating ?? 0)" data-authenticated="@(Model.CurrentRating != null)" data-readonly="@Model.IsReadOnly">
</span> </span>
} }
else else
{ {
<span class="my-rating text-dark p-1" data-authenticated="True" data-bs-toggle="popover" data-bs-placement="right" data-html="true" data-content="<div class='text-center'><div class='d-grid gap-2'><a href='@Model.LoginUrl' class='btn btn-primary'>@L["LoginToRate"]</a></div></div>"></span> <span class="my-rating text-dark p-1" data-authenticated="True" data-bs-toggle="popover" data-bs-placement="right" data-html="true" data-content="<div class='text-center'><div class='d-grid gap-2'><a href='@Model.LoginUrl' class='btn btn-primary'>@L["LoginToRate"]</a></div></div>"></span>
<span class="rating-login"></span> <span class="rating-login"></span>
} }
</div>
</div> </div>
</div> </div>
</div>

7
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/RatingViewComponent.cs

@ -31,7 +31,7 @@ public class RatingViewComponent : AbpViewComponent
CurrentUser = currentUser; CurrentUser = currentUser;
} }
public virtual async Task<IViewComponentResult> InvokeAsync(string entityType, string entityId) public virtual async Task<IViewComponentResult> InvokeAsync(string entityType, string entityId, bool isReadOnly = false)
{ {
var ratings = await RatingPublicAppService.GetGroupedStarCountsAsync(entityType, entityId); var ratings = await RatingPublicAppService.GetGroupedStarCountsAsync(entityType, entityId);
var totalRating = ratings.Sum(x => x.Count); var totalRating = ratings.Sum(x => x.Count);
@ -52,7 +52,8 @@ public class RatingViewComponent : AbpViewComponent
LoginUrl = loginUrl, LoginUrl = loginUrl,
Ratings = ratings, Ratings = ratings,
CurrentRating = currentUserRating, CurrentRating = currentUserRating,
TotalRating = totalRating TotalRating = totalRating,
IsReadOnly = isReadOnly
}; };
return View("~/Pages/CmsKit/Shared/Components/Rating/Default.cshtml", viewModel); return View("~/Pages/CmsKit/Shared/Components/Rating/Default.cshtml", viewModel);
@ -72,4 +73,6 @@ public class RatingViewModel
public short? CurrentRating { get; set; } public short? CurrentRating { get; set; }
public int TotalRating { get; set; } public int TotalRating { get; set; }
public bool IsReadOnly { get; set; }
} }

3
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Rating/default.js

@ -15,6 +15,7 @@
function registerCreateOfNewRating() { function registerCreateOfNewRating() {
$widget.find(".my-rating").each(function () { $widget.find(".my-rating").each(function () {
var authenticated = $(this).attr("data-authenticated"); var authenticated = $(this).attr("data-authenticated");
var readonly = $(this).attr("data-readonly");
$(this).starRating({ $(this).starRating({
initialRating: 0, initialRating: 0,
@ -26,7 +27,7 @@
strokeWidth: 0, strokeWidth: 0,
disableAfterRate: true, disableAfterRate: true,
useFullStars: true, useFullStars: true,
readOnly: authenticated === "True", readOnly: authenticated === "True" || readonly === "True",
onHover: function (currentIndex, currentRating, $el) { onHover: function (currentIndex, currentRating, $el) {
$widget.find(".live-rating").text(currentIndex); $widget.find(".live-rating").text(currentIndex);
}, },

4
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Tags/Default.cshtml

@ -10,14 +10,14 @@
{ {
if (Model.UrlFormat.IsNullOrWhiteSpace()) if (Model.UrlFormat.IsNullOrWhiteSpace())
{ {
<span class="badge rounded-pill bg-light text-dark px-3 py-2 cmskit-tag font-weight-normal"> <span class="badge rounded-pill badge-secondary text-bg-secondary cmskit-tag font-weight-normal">
@tag.Name @tag.Name
</span> </span>
} }
else else
{ {
var formattedUrl = Model.UrlFormat.Replace("{TagId}", tag.Id.ToString()).Replace("{TagName}", tag.Name); var formattedUrl = Model.UrlFormat.Replace("{TagId}", tag.Id.ToString()).Replace("{TagName}", tag.Name);
<a href="@formattedUrl"><span class="badge rounded-pill bg-light text-dark px-3 py-2 cmskit-tag font-weight-normal">@tag.Name</span></a> <a href="@formattedUrl"><span class="badge rounded-pill badge-secondary text-bg-secondary rounded-pill cmskit-tag font-weight-normal">@tag.Name</span></a>
} }
} }
} }

9
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/BlogPost.cshtml

@ -10,6 +10,7 @@
@using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Tags @using Volo.CmsKit.Public.Web.Pages.CmsKit.Shared.Components.Tags
@using Volo.Abp.AspNetCore.Mvc.UI.Packages.HighlightJs @using Volo.Abp.AspNetCore.Mvc.UI.Packages.HighlightJs
@using Volo.CmsKit.Contents @using Volo.CmsKit.Contents
@using Volo.CmsKit.Web
@using Volo.CmsKit.Web.Pages.CmsKit.Components.Contents @using Volo.CmsKit.Web.Pages.CmsKit.Components.Contents
@inherits CmsKitPublicPageBase @inherits CmsKitPublicPageBase
@ -62,7 +63,7 @@
<div class="col-lg-8 col-md-10 mx-auto pb-4"> <div class="col-lg-8 col-md-10 mx-auto pb-4">
<h1 class="mt-lg-4 mt-md-3">@Model.ViewModel.Title</h1> <h1 class="mt-lg-4 mt-md-3">@Model.ViewModel.Title</h1>
<p class="mb-lg-5 mb-md-3"> <p class="mb-lg-5 mb-md-3">
<a href="/blogs/@Model.BlogSlug?authorId=@Model.ViewModel.Author.Id"> <a href="/@CmsBlogsWebConsts.BlogsRoutePrefix/@Model.BlogSlug?authorId=@Model.ViewModel.Author.Id">
<span class="font-weight-bold">@@@Model.ViewModel.Author?.UserName</span> <span class="font-weight-bold">@@@Model.ViewModel.Author?.UserName</span>
</a> </a>
<small style="opacity:.65;">@Model.ViewModel.CreationTime</small> <small style="opacity:.65;">@Model.ViewModel.CreationTime</small>
@ -89,7 +90,7 @@
{ {
entityType = Volo.CmsKit.Blogs.BlogPostConsts.EntityType, entityType = Volo.CmsKit.Blogs.BlogPostConsts.EntityType,
entityId = Model.ViewModel.Id.ToString(), entityId = Model.ViewModel.Id.ToString(),
urlFormat = $"/blogs/{Model.BlogSlug}?tagId={{TagId}}" urlFormat = $"/{CmsBlogsWebConsts.BlogsRoutePrefix}/{Model.BlogSlug}?tagId={{TagId}}"
}) })
} }
} }
@ -133,9 +134,11 @@
{ {
<button id="deleteBlogPost" class="btn btn-danger"> <button id="deleteBlogPost" class="btn btn-danger">
<i class="fa fa-remove"> <i class="fa fa-remove">
@L["Delete"]
</i> </i>
@L["Delete"]
</button> </button>
<hr />
} }
@if (isScrollIndexEnabled) @if (isScrollIndexEnabled)
{ {

71
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/Index.cshtml

@ -2,18 +2,19 @@
@using Volo.CmsKit.Public.Web.Pages @using Volo.CmsKit.Public.Web.Pages
@using Volo.CmsKit.Public.Web.Pages.Public.CmsKit.Blogs @using Volo.CmsKit.Public.Web.Pages.Public.CmsKit.Blogs
@using Volo.CmsKit.Web
@inherits CmsKitPublicPageBase @inherits CmsKitPublicPageBase
@model IndexModel @model IndexModel
@section styles{ @section styles {
<abp-style src="/Pages/Public/CmsKit/Blogs/index.css"/> <abp-style src="/Pages/Public/CmsKit/Blogs/index.css" />
} }
@section scripts { @section scripts {
<abp-script-bundle> <abp-script-bundle>
<abp-script src="/Pages/Public/CmsKit/Blogs/index.js"/> <abp-script src="/Pages/Public/CmsKit/Blogs/index.js" />
</abp-script-bundle> </abp-script-bundle>
} }
@ -21,29 +22,43 @@
const string dummyImageSource = "https://dummyimage.com/320x180/a3a3a3/fff.png"; const string dummyImageSource = "https://dummyimage.com/320x180/a3a3a3/fff.png";
} }
<abp-row id="blogs-filter-area"> @if (Model.AuthorId.HasValue)
<abp-column size="_4"> {
<div class="mb-3"> <abp-row id="blogs-filter-area">
<label class="form-label" asp-for="@Model.SelectedAuthor"></label> <abp-column size="_4">
<div class="mb-3">
<label class="form-label" asp-for="@Model.SelectedAuthor"></label>
<select id="AuthorSelect" asp-for="@Model.AuthorId" <select id="AuthorSelect" asp-for="@Model.AuthorId"
class="auto-complete-select" class="auto-complete-select"
data-placeholder="@L["SelectAnAuthor"]" data-placeholder="@L["SelectAnAuthor"]"
data-allow-clear="true" data-allow-clear="true"
data-autocomplete-api-url="/api/cms-kit-public/blog-posts/authors" data-autocomplete-api-url="/api/cms-kit-public/blog-posts/authors"
data-autocomplete-display-property="userName" data-autocomplete-display-property="userName"
data-autocomplete-value-property="id" data-autocomplete-value-property="id"
data-autocomplete-items-property="items" data-autocomplete-items-property="items"
data-autocomplete-filter-param-name="filter"> data-autocomplete-filter-param-name="filter">
@if (Model.SelectedAuthor != null)
{
<option selected value="@Model.AuthorId" selected="selected">@Model.SelectedAuthor.UserName</option>
}
</select>
</div>
</abp-column>
</abp-row>
<hr />
}
@if (Model.TagId.HasValue)
{
<h4>@L["Tags"]</h4>
<span class="badge rounded-pill badge-secondary text-bg-secondary">@Model.FilteredTagName
<a href="/@CmsBlogsWebConsts.BlogsRoutePrefix/@Model.BlogSlug" class="text-bg-secondary"><i class="fa fa-close ms-1"></i></a>
</span>
<hr />
}
@if(Model.SelectedAuthor != null)
{
<option selected value="@Model.AuthorId" selected="selected">@Model.SelectedAuthor.UserName</option>
}
</select>
</div>
</abp-column>
</abp-row>
<abp-row id="blogs-container"> <abp-row id="blogs-container">
@foreach (var blog in Model.Blogs.Items) @foreach (var blog in Model.Blogs.Items)
{ {
@ -51,11 +66,11 @@
<abp-card> <abp-card>
@if (blog.CoverImageMediaId != null) @if (blog.CoverImageMediaId != null)
{ {
<img src="/api/cms-kit/media/@blog.CoverImageMediaId" class="card-img-top" onerror="this.src='@dummyImageSource'"/> <img src="/api/cms-kit/media/@blog.CoverImageMediaId" class="card-img-top" onerror="this.src='@dummyImageSource'" />
} }
else else
{ {
<img src="@(dummyImageSource)?text=@blog.Title" class="card-img-top"/> <img src="@(dummyImageSource)?text=@blog.Title" class="card-img-top" />
} }
<abp-card-body class="p-4"> <abp-card-body class="p-4">
<h5>@blog.Title</h5> <h5>@blog.Title</h5>
@ -65,7 +80,7 @@
</p> </p>
<p style="min-height: 60px;">@blog.ShortDescription</p> <p style="min-height: 60px;">@blog.ShortDescription</p>
<div class="d-grid gap-2"> <div class="d-grid gap-2">
<a href="/blogs/@Model.BlogSlug/@blog.Slug" class="btn btn-light"> <a href="/@CmsBlogsWebConsts.BlogsRoutePrefix/@Model.BlogSlug/@blog.Slug" class="btn btn-light">
@L["Read"] @L["Read"]
</a> </a>
</div> </div>
@ -76,6 +91,6 @@
</abp-row> </abp-row>
<abp-row> <abp-row>
<abp-column> <abp-column>
<abp-paginator model="Model.PagerModel"/> <abp-paginator model="Model.PagerModel" />
</abp-column> </abp-column>
</abp-row> </abp-row>

9
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/Index.cshtml.cs

@ -31,6 +31,8 @@ public class IndexModel : CmsKitPublicPageModelBase
public CmsUserDto SelectedAuthor { get; protected set; } public CmsUserDto SelectedAuthor { get; protected set; }
public string FilteredTagName { get; protected set; }
protected IBlogPostPublicAppService BlogPostPublicAppService { get; } protected IBlogPostPublicAppService BlogPostPublicAppService { get; }
public IndexModel(IBlogPostPublicAppService blogPostPublicAppService) public IndexModel(IBlogPostPublicAppService blogPostPublicAppService)
@ -54,7 +56,12 @@ public class IndexModel : CmsKitPublicPageModelBase
{ {
SelectedAuthor = await BlogPostPublicAppService.GetAuthorHasBlogPostAsync(AuthorId.Value); SelectedAuthor = await BlogPostPublicAppService.GetAuthorHasBlogPostAsync(AuthorId.Value);
} }
if (TagId is not null)
{
FilteredTagName = await BlogPostPublicAppService.GetTagNameAsync(TagId.Value);
}
return Page(); return Page();
} }
} }

14
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/Public/CmsKit/Blogs/bootstrap-toc.css

@ -20,15 +20,17 @@ nav[data-toggle='toc'] .nav > li > a:focus {
text-decoration: none; text-decoration: none;
background-color: transparent; background-color: transparent;
border-left: 1px solid #563d7c; border-left: 1px solid #563d7c;
border-radius: 0;
} }
nav[data-toggle='toc'] .nav-link.active, nav[data-toggle='toc'] .nav-link.active,
nav[data-toggle='toc'] .nav-link.active:hover, nav[data-toggle='toc'] .nav-link.active:hover,
nav[data-toggle='toc'] .nav-link.active:focus { nav[data-toggle='toc'] .nav-link.active:focus {
padding-left: 18px; padding-left: 8px;
font-weight: bold; font-weight: bold;
color: #563d7c; color: #563d7c;
background-color: transparent; background-color: transparent;
border-left: 2px solid #563d7c; border-left: 2px solid #563d7c;
border-radius: 0;
} }
/* Nav: second level (shown on .active) */ /* Nav: second level (shown on .active) */
@ -40,18 +42,16 @@ nav[data-toggle='toc'] .nav-link + ul {
nav[data-toggle='toc'] .nav .nav > li > a { nav[data-toggle='toc'] .nav .nav > li > a {
padding-top: 1px; padding-top: 1px;
padding-bottom: 1px; padding-bottom: 1px;
padding-left: 30px; padding-left: 8px;
margin-left: 8px;
font-size: 12px; font-size: 12px;
font-weight: normal; font-weight: normal;
} }
nav[data-toggle='toc'] .nav .nav > li > a:hover,
nav[data-toggle='toc'] .nav .nav > li > a:focus {
padding-left: 29px;
}
nav[data-toggle='toc'] .nav .nav > li > .active, nav[data-toggle='toc'] .nav .nav > li > .active,
nav[data-toggle='toc'] .nav .nav > li > .active:hover, nav[data-toggle='toc'] .nav .nav > li > .active:hover,
nav[data-toggle='toc'] .nav .nav > li > .active:focus { nav[data-toggle='toc'] .nav .nav > li > .active:focus {
padding-left: 28px; padding-left: 8px;
font-weight: 500; font-weight: 500;
} }

15
modules/cms-kit/test/Volo.CmsKit.Application.Tests/Blogs/BlogPostPublicAppService_Tests.cs

@ -179,4 +179,19 @@ public class BlogPostPublicAppService_Tests : CmsKitApplicationTestBase
allItemsFromRepository.Any(x => x.Id == draftBlogPost2.Id).ShouldBeTrue(); allItemsFromRepository.Any(x => x.Id == draftBlogPost2.Id).ShouldBeTrue();
allItemsFromRepository.Any(x => x.Id == publishedBlogPost1.Id).ShouldBeTrue(); allItemsFromRepository.Any(x => x.Id == publishedBlogPost1.Id).ShouldBeTrue();
} }
[Fact]
public async Task GetTagNameAsync_ShouldReturnCorrectName()
{
var tagName = await blogPostAppService.GetTagNameAsync(cmsKitTestData.TagId_1);
tagName.ShouldBe(cmsKitTestData.TagName_1);
}
[Fact]
public async Task GetTagNameAsync_WithNonExistingIdShouldReturnCorrectName()
{
await Should.ThrowAsync<EntityNotFoundException>(
blogPostAppService.GetTagNameAsync(Guid.NewGuid()));
}
} }

Loading…
Cancel
Save