diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json index a5089e06f7..07aa50adf2 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json @@ -13,8 +13,10 @@ "Permission:Edit": "Edit", "Permission:Delete": "Delete", "Permission:Create": "Create", + "Permission:Accounting": "Accounting", + "Permission:Accounting:Quotation": "Quotation", "Menu:Organizations": "Organizations", - "Menu:Accounting": "Accounting", + "Menu:Accounting": "Accounting", "Menu:Packages": "Packages", "NpmPackageDeletionWarningMessage": "This NPM Package will be deleted. Do you confirm that?", "NugetPackageDeletionWarningMessage": "This Nuget Package will be deleted. Do you confirm that?", @@ -94,13 +96,30 @@ "UsernameOrEmail": "Username or email", "UsernameOrEmailPlaceholder": "Username or email...", "Member": "Member", - "QuotationPurchasedOrderNo": "Quotation Purchased Order No", - "QuotationTime": "Quotation Time", - "CompanyName": "Company Name", - "CompanyAddress": "Company Address", + "PurchaseOrderNo": "Purchase order no", + "QuotationDate": "Quotation date", + "CompanyName": "Company name", + "CompanyAddress": "Company address", "Price": "Price", +<<<<<<< Updated upstream + "DiscountText": "Discount text", + "DiscountQuantity": "Discount quantity", + "DiscountPrice": "Discount price", + "Quotation": "Quotation", + "Generate": "Generate" +======= "ExtraText": "Extra Text", "ExtraAmount": "Extra Amount", - "DownloadQuotation": "Download Quotation" + "DownloadQuotation": "Download Quotation", + "Invoice": "Invoice", + "TaxNumber": "Tax Number", + "InvoiceNumber": "Invoice Number", + "InvoiceDate": "Invoice Date", + "Quantity": "Quantity", + "AddProduct": "Add Product", + "NeedToAddProduct": "Need to add product to generate invoice !", + "TotalPrice": "Total Price", + "Generate": "Generate" +>>>>>>> Stashed changes } } \ No newline at end of file diff --git a/common.DotSettings b/common.DotSettings index 0eb4875d49..6f40d029a7 100644 --- a/common.DotSettings +++ b/common.DotSettings @@ -20,5 +20,11 @@ False False SQL + False + False + False + False + False + False True \ No newline at end of file diff --git a/docs/en/CLI.md b/docs/en/CLI.md index b687a5244a..75e3e027b4 100644 --- a/docs/en/CLI.md +++ b/docs/en/CLI.md @@ -135,6 +135,8 @@ abp update [options] * `--include-previews` or `-p`: Includes preview, beta and rc packages while checking the latest versions. * `--npm`: Only updates NPM packages. * `--nuget`: Only updates NuGet packages. +* `--solution-path` or `-sp`: Specify the solution path. Use the current directory by default +* `--solution-name` or `-sn`: Specify the solution name. Search `*.sln` files in the directory by default. ### switch-to-preview @@ -170,7 +172,11 @@ Some features of the CLI requires to be logged in to abp.io platform. To login w abp login ``` -Notice that, a new login with an already active session, will kill the previous session and creates a new one. +```bash +abp login -p +``` + +Notice that, a new login with an already active session, overwrites the previous session. ### logout diff --git a/docs/en/Getting-Started.md b/docs/en/Getting-Started.md index 07e55f553e..72e2f2886c 100644 --- a/docs/en/Getting-Started.md +++ b/docs/en/Getting-Started.md @@ -400,8 +400,8 @@ Enter **admin** as the username and **1q2w3E*** as the password to login to the The application is up and running. You can continue to develop your application based on this startup template. -> The [application startup template](startup-templates/application/index.md) includes the TenantManagement and Identity modules. +> The [application startup template](Startup-Templates/Application.md) includes the TenantManagement and Identity modules. ## What's next? -[Application development tutorial](tutorials/book-store/part-1.md) +[Application development tutorial](Tutorials/Part-1.md) diff --git a/docs/en/Tutorials/Part-1.md b/docs/en/Tutorials/Part-1.md index e3f988779b..c684d1fc1c 100644 --- a/docs/en/Tutorials/Part-1.md +++ b/docs/en/Tutorials/Part-1.md @@ -38,7 +38,7 @@ Create a new project named `Acme.BookStore` where `Acme` is the company name and #### Create the project -By running the below command, it creates a new ABP project with the database provider `{{DB_Text}}` and UI option `MVC`. To see the other CLI options, check out [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) document. +By running the below command, it creates a new ABP project with the database provider `{{DB_Text}}` and UI option `{{UI_Value}}`. To see the other CLI options, check out [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) document. ```bash abp new Acme.BookStore --template app --database-provider {{DB}} --ui {{UI_Text}} --mobile none @@ -61,7 +61,7 @@ After creating the project, you need to apply the initial migrations and create To run the project, right click to the {{if UI == "MVC"}} `Acme.BookStore.Web`{{end}} {{if UI == "NG"}} `Acme.BookStore.HttpApi.Host` {{end}} project and click **Set As StartUp Project**. And run the web project by pressing **CTRL+F5** (*without debugging and fast*) or press **F5** (*with debugging and slow*). {{if UI == "NG"}}You will see the Swagger UI for BookStore API.{{end}} -Further information, see the [running the application section](../../Getting-Started-{{if UI == "NG"}}Angular{{else}}AspNetCore-MVC{{end}}-Template#running-the-application).Getting-Started-AspNetCore-MVC-Template#running-the-application +Further information, see the [running the application section](../Getting-Started?UI={{UI}}#run-the-application). ![Set as startup project](./images/bookstore-start-project-{{UI_Text}}.png) @@ -335,7 +335,7 @@ INSERT INTO AppBooks (Id,CreationTime,[Name],[Type],PublishDate,Price) VALUES ### Create the application service -The next step is to create an [application service](../../Application-Services.md) to manage the books which will allow us the four basic functions: creating, reading, updating and deleting. Application layer is separated into two projects: +The next step is to create an [application service](../Application-Services.md) to manage the books which will allow us the four basic functions: creating, reading, updating and deleting. Application layer is separated into two projects: * `Acme.BookStore.Application.Contracts` mainly contains your `DTO`s and application service interfaces. * `Acme.BookStore.Application` contains the implementations of your application services. @@ -874,8 +874,6 @@ We'll see **book-list works!** text on the books page: Run the following command in the terminal to create a new state, named `BooksState`: -![Initial book list page](./images/bookstore-generate-state-books.png) - ```bash npx @ngxs/cli --name books --directory src/app/books ``` diff --git a/docs/en/Tutorials/Part-2.md b/docs/en/Tutorials/Part-2.md index ddee41325f..cfe9936812 100644 --- a/docs/en/Tutorials/Part-2.md +++ b/docs/en/Tutorials/Part-2.md @@ -525,7 +525,7 @@ export class BooksState { * We imported `CreateUpdateBook` action and defined the `save` method that will listen to a `CreateUpdateBook` action to create a book. -When the `SaveBook` action dispatched, the save method is being executed. It calls `create` method of the `BookService`. +When the `SaveBook` action dispatched, the save method is being executed. It calls `createByInput` method of the `BookService`. #### Add a modal to BookListComponent @@ -885,32 +885,6 @@ Now, you can open your browser to see the changes: #### Saving the book -Open `book-list.component.html` in `app\books\book-list` folder and add the following `abp-button` to save the new book. - -```html - - - - - - -``` - -Find the `
` tag and replace below content: - -```html - -``` - - -* We added the `(ngSubmit)="save()"` to `` element to save a new book by pressing the enter. -* We added `abp-button` to the bottom area of the modal to save a new book. - Open `book-list.component.ts` file in `app\books\book-list` folder and replace the content as below: ```js @@ -993,6 +967,32 @@ export class BookListComponent implements OnInit { * We imported `CreateUpdateBook`. * We added `save` method +Open `book-list.component.html` in `app\books\book-list` folder and add the following `abp-button` to save the new book. + +```html + + + + + + +``` + +Find the `` tag and replace below content: + +```html + +``` + + +* We added the `(ngSubmit)="save()"` to `` element to save a new book by pressing the enter. +* We added `abp-button` to the bottom area of the modal to save a new book. + The final modal UI looks like below: ![Save button to the modal](./images/bookstore-new-book-form-v2.png) diff --git a/docs/en/Tutorials/images/bookstore-angular-file-tree.png b/docs/en/Tutorials/images/bookstore-angular-file-tree.png index 28e570f604..a3197b6457 100644 Binary files a/docs/en/Tutorials/images/bookstore-angular-file-tree.png and b/docs/en/Tutorials/images/bookstore-angular-file-tree.png differ diff --git a/docs/en/UI/Angular/Config-State.md b/docs/en/UI/Angular/Config-State.md index 7fc202982c..71a5da01f0 100644 --- a/docs/en/UI/Angular/Config-State.md +++ b/docs/en/UI/Angular/Config-State.md @@ -236,7 +236,7 @@ const newRoute: ABP.Route = { path: "page", invisible: false, order: 2, - requiredPolicy: "MyProjectName::MyNewPage" + requiredPolicy: "MyProjectName.MyNewPage" }; this.config.dispatchAddRoute(newRoute); @@ -248,23 +248,25 @@ The `newRoute` will be placed as at root level, i.e. without any parent routes a If you want **to add a child route, you can do this:** ```js +import { eIdentityRouteNames } from '@abp/ng.identity'; + // this.config is instance of ConfigStateService const newRoute: ABP.Route = { - parentName: "AbpAccount::Login", + parentName: eIdentityRouteNames.IdentityManagement, name: "My New Page", iconClass: "fa fa-dashboard", path: "page", invisible: false, order: 2, - requiredPolicy: "MyProjectName::MyNewPage" + requiredPolicy: "MyProjectName.MyNewPage" }; this.config.dispatchAddRoute(newRoute); // returns a state stream which emits after dispatch action is complete ``` -The `newRoute` will then be placed as a child of the parent route named `'AbpAccount::Login'` and its url will be set as `'/account/login/page'`. +The `newRoute` will then be placed as a child of the parent route named `eIdentityRouteNames.IdentityManagement` and its url will be set as `'/identity/page'`. #### Route Configuration Properties @@ -291,4 +293,4 @@ Please refer to `Config.Environment` type for all the properties you can pass to ## What's Next? -* [Component Replacement](./Component-Replacement.md) \ No newline at end of file +- [Modifying the Menu](./Modifying-the-Menu.md) \ No newline at end of file diff --git a/docs/en/UI/Angular/Confirmation-Service.md b/docs/en/UI/Angular/Confirmation-Service.md index a8d2c4c2a7..2d1c5c1843 100644 --- a/docs/en/UI/Angular/Confirmation-Service.md +++ b/docs/en/UI/Angular/Confirmation-Service.md @@ -94,6 +94,28 @@ With the options above, the confirmation popup looks like this: ![confirmation](./images/confirmation.png) +You are able to pass in an HTML string as title, message, or button texts. Here is an example: + +```js +const options: Partial = { + yesText: 'Yes, delete it', +}; + +this.confirmation.warn( + ` + Role Demo will be deleted +
+ Do you confirm that? + `, + 'Are you sure?', + options, +); +``` + +Since the values are HTML now, localization should be handled manually. Check out the [LocalizationService](./Localization#using-the-localization-service) to see how you can accomplish that. + +> Please note that all strings will be sanitized by Angular and not every HTML string will work. Only values that are considered as "safe" by Angular will be displayed. + ### How to Remove a Confirmation Popup The open confirmation popup can be removed manually via the `clear` method: diff --git a/docs/en/UI/Angular/Modifying-the-Menu.md b/docs/en/UI/Angular/Modifying-the-Menu.md new file mode 100644 index 0000000000..450a5aecd1 --- /dev/null +++ b/docs/en/UI/Angular/Modifying-the-Menu.md @@ -0,0 +1,199 @@ +# Modifying the Menu + +The menu is inside the `ApplicationLayoutComponent` in the @abp/ng.theme.basic package. There are several methods for modifying the menu elements. This document covers these methods. If you would like to replace the menu completely, please refer to [Component Replacement documentation](./Component-Replacement.md) and learn how to replace a layout. + + + + + +## How to Add a Logo + +The `logoUrl` property in the environment variables is the url of the logo. + +You can add your logo to `src/assets` folder and set the `logoUrl` as shown below: + +```js +export const environment = { + // other configurations + application: { + name: 'MyProjectName', + logoUrl: 'assets/logo.png', + }, + // other configurations +}; +``` + +## How to Add a Navigation Element + +### Via `routes` Property in `AppRoutingModule` + +You can define your routes by adding `routes` as a child property to `data` property of a route configuration in the `app-routing.module`. The `@abp/ng.core` package organizes your routes and stores them in the `ConfigState`. `ApplicationLayoutComponent` gets routes from store and displays them on the menu. + +You can add the `routes` property like below: + +```js +{ + path: 'your-path', + data: { + routes: { + name: 'Your navigation', + order: 3, + iconClass: 'fas fa-question-circle', + requiredPolicy: 'permission key here', + children: [ + { + path: 'child', + name: 'Your child navigation', + order: 1, + requiredPolicy: 'permission key here', + }, + ], + } as ABP.Route, // can be imported from @abp/ng.core + } +} +``` + +- `name` is the label of the navigation element. A localization key or a localization object can be passed. +- `order` is the order of the navigation element. +- `iconClass` is the class of the `i` tag, which is placed to the left of the navigation label. +- `requiredPolicy` is the permission key to access the page. See the [Permission Management document](./Permission-Management.md) +- `children` is an array and is used for declaring child navigation elements. The child navigation element will be placed as a child route which will be available at `'/your-path/child'` based on the given `path` property. + +After adding the `routes` property as described above, the navigation menu looks like this: + +![navigation-menu-via-app-routing](./images/navigation-menu-via-app-routing.png) + +## Via ConfigState + +The `dispatchAddRoute` method of `ConfigStateService` adds a new navigation element to the menu. + +```js +// this.config is instance of ConfigStateService + +const newRoute: ABP.Route = { + name: 'My New Page', + iconClass: 'fa fa-dashboard', + path: 'page', + invisible: false, + order: 2, + requiredPolicy: 'MyProjectName.MyNewPage', +} as Omit; + +this.config.dispatchAddRoute(newRoute); +// returns a state stream which emits after dispatch action is complete +``` + +The `newRoute` will be placed as at root level, i.e. without any parent routes, and its url will be stored as `'/path'`. + +If you want **to add a child route, you can do this:** + +```js +// this.config is instance of ConfigStateService +// eIdentityRouteNames enum can be imported from @abp/ng.identity + +const newRoute: ABP.Route = { + parentName: eIdentityRouteNames.IdentityManagement, + name: 'My New Page', + iconClass: 'fa fa-dashboard', + path: 'page', + invisible: false, + order: 3, + requiredPolicy: 'MyProjectName.MyNewPage' +} as Omit; + +this.config.dispatchAddRoute(newRoute); +// returns a state stream which emits after dispatch action is complete +``` + +The `newRoute` will then be placed as a child of the parent route named `eIdentityRouteNames.IdentityManagement` and its url will be set as `'/identity/page'`. + +The new route will be added like below: + +![navigation-menu-via-config-state](./images/navigation-menu-via-config-state.png) + +## How to Patch a Navigation Element + +The `dispatchPatchRouteByName` method finds a route by its name and replaces its configuration in the store with the new configuration passed as the second parameter. + +```js +// this.config is instance of ConfigStateService +// eIdentityRouteNames enum can be imported from @abp/ng.identity + +const newRouteConfig: Partial = { + iconClass: 'fas fa-home', + parentName: eIdentityRouteNames.Administration, + order: 0, + children: [ + { + name: 'Dashboard', + path: 'dashboard', + }, + ], +}; + +this.config.dispatchPatchRouteByName('::Menu:Home', newRouteConfig); +// returns a state stream which emits after dispatch action is complete +``` + +* Moved the _Home_ navigation under the _Administration_ dropdown based on given `parentName`. +* Added an icon. +* Specified the order. +* Added a child route named _Dashboard_. + +After the patch above, navigation elements looks like below: + +![navigation-menu-after-patching](./images/navigation-menu-after-patching.png) + + +## How to Add an Element to Right Part of the Menu + +The right part elements are stored in the `LayoutState` that is in the @abp/ng.theme.basic package. + +The `dispatchAddNavigationElement` method of the `LayoutStateService` adds an element to the right part of the menu. + +You can insert an element by adding your template to `app.component` and calling the `dispatchAddNavigationElement` method: + +```js +import { Layout, LayoutStateService } from '@abp/ng.theme.basic'; // added this line + +@Component({ + selector: 'app-root', + template: ` + + + + `, +}) +export class AppComponent { + // Added ViewChild + @ViewChild('search', { static: false, read: TemplateRef }) searchElementRef: TemplateRef; + + constructor(private layout: LayoutStateService) {} // injected LayoutStateService + + // Added ngAfterViewInit + ngAfterViewInit() { + const newElement = { + name: 'Search', + element: this.searchElementRef, + order: 1, + } as Layout.NavigationElement; + + this.layout.dispatchAddNavigationElement(newElement); + } +} +``` + +This inserts a search input to the menu. The final UI looks like below: + +![navigation-menu-search-input](./images/navigation-menu-search-input.png) + +## How to Remove an Element From Right Part of the Menu + +TODO + + +## What's Next + +* [Component Replacement](./Component-Replacement.md) diff --git a/docs/en/UI/Angular/Toaster-Service.md b/docs/en/UI/Angular/Toaster-Service.md index 191f0b34fd..b194811d9a 100644 --- a/docs/en/UI/Angular/Toaster-Service.md +++ b/docs/en/UI/Angular/Toaster-Service.md @@ -150,6 +150,7 @@ clear(): void Removes all open toasts. ## See Also + - [Confirmation Popup](./Confirmation-Service.md) ## What's Next? diff --git a/docs/en/UI/Angular/images/navigation-menu-after-patching.png b/docs/en/UI/Angular/images/navigation-menu-after-patching.png new file mode 100644 index 0000000000..2f6bf08c88 Binary files /dev/null and b/docs/en/UI/Angular/images/navigation-menu-after-patching.png differ diff --git a/docs/en/UI/Angular/images/navigation-menu-search-input.png b/docs/en/UI/Angular/images/navigation-menu-search-input.png new file mode 100644 index 0000000000..ebdc05e3e0 Binary files /dev/null and b/docs/en/UI/Angular/images/navigation-menu-search-input.png differ diff --git a/docs/en/UI/Angular/images/navigation-menu-via-app-routing.png b/docs/en/UI/Angular/images/navigation-menu-via-app-routing.png new file mode 100644 index 0000000000..4d5f61301c Binary files /dev/null and b/docs/en/UI/Angular/images/navigation-menu-via-app-routing.png differ diff --git a/docs/en/UI/Angular/images/navigation-menu-via-config-state.png b/docs/en/UI/Angular/images/navigation-menu-via-config-state.png new file mode 100644 index 0000000000..19944f154d Binary files /dev/null and b/docs/en/UI/Angular/images/navigation-menu-via-config-state.png differ diff --git a/docs/en/UI/Common/Utils/Linked-List.md b/docs/en/UI/Common/Utils/Linked-List.md index 0d6b259603..0c047d28e5 100644 --- a/docs/en/UI/Common/Utils/Linked-List.md +++ b/docs/en/UI/Common/Utils/Linked-List.md @@ -1270,7 +1270,7 @@ find(predicate: ListIteratorFn): ListNode | undefined Finds the first node from the list that matches the given predicate: ```js -list.addTailMany(['a', 'b', 'b', 'c']); +list.addManyTail(['a', 'b', 'b', 'c']); // "a" <-> "b" <-> "b" <-> "c" @@ -1294,7 +1294,7 @@ findIndex(predicate: ListIteratorFn): number Finds the position of the first node from the list that matches the given predicate: ```js -list.addTailMany(['a', 'b', 'b', 'c']); +list.addManyTail(['a', 'b', 'b', 'c']); // "a" <-> "b" <-> "b" <-> "c" @@ -1322,7 +1322,7 @@ get(position: number): ListNode | undefined Finds and returns the node with specific position in the list: ```js -list.addTailMany(['a', 'b', 'c']); +list.addManyTail(['a', 'b', 'c']); // "a" <-> "b" <-> "c" @@ -1346,7 +1346,7 @@ indexOf(value: T, compareFn?: ListComparisonFn): number Finds the position of the first node from the list that has the given value: ```js -list.addTailMany(['a', 'b', 'b', 'c']); +list.addManyTail(['a', 'b', 'b', 'c']); // "a" <-> "b" <-> "b" <-> "c" @@ -1368,7 +1368,7 @@ i3 === -1 You may pass a custom compare function to detect the searched value: ```js -list.addTailMany([{ x: 1 }, { x: 0 }, { x: 2 }, { x: 0 }, { x: 3 }]); +list.addManyTail([{ x: 1 }, { x: 0 }, { x: 2 }, { x: 0 }, { x: 3 }]); // {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3} @@ -1408,7 +1408,7 @@ forEach(iteratorFn: ListIteratorFn): void Runs a function on all nodes in a linked list from head to tail: ```js -list.addTailMany(['a', 'b', 'c']); +list.addManyTail(['a', 'b', 'c']); // "a" <-> "b" <-> "c" @@ -1425,7 +1425,7 @@ list.forEach((node, index) => console.log(node.value + index)); A linked list is iterable. In other words, you may use methods like `for...of` on it. ```js -list.addTailMany(['a', 'b', 'c']); +list.addManyTail(['a', 'b', 'c']); // "a" <-> "b" <-> "c" @@ -1449,7 +1449,7 @@ toArray(): T[] Converts a linked list to an array of values: ```js -list.addTailMany(['a', 'b', 'c']); +list.addManyTail(['a', 'b', 'c']); // "a" <-> "b" <-> "c" @@ -1471,7 +1471,7 @@ toNodeArray(): ListNode[] Converts a linked list to an array of nodes: ```js -list.addTailMany(['a', 'b', 'c']); +list.addManyTail(['a', 'b', 'c']); // "a" <-> "b" <-> "c" @@ -1495,7 +1495,7 @@ toString(mapperFn: ListMapperFn = JSON.stringify): string Converts a linked list to a string representation of nodes and their relations: ```js -list.addTailMany(['a', 2, 'c', { k: 4, v: 'd' }]); +list.addManyTail(['a', 2, 'c', { k: 4, v: 'd' }]); // "a" <-> 2 <-> "c" <-> {"k":4,"v":"d"} @@ -1562,7 +1562,7 @@ export class ListNode { - `previous` refers to the previous node in the list. ```js -list.addTailMany([ 0, 1, 2 ]); +list.addManyTail([ 0, 1, 2 ]); console.log( list.head.value, // 0 diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 53caea554b..c0515c18de 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -352,6 +352,10 @@ "text": "Config State", "path": "UI/Angular/Config-State.md" }, + { + "text": "Modifying the Menu", + "path": "UI/Angular/Modifying-the-Menu.md" + }, { "text": "Component Replacement", "path": "UI/Angular/Component-Replacement.md" @@ -398,7 +402,7 @@ "text": "Data Access", "items": [ { - "text": "Overall", + "text": "Overall", "path": "Data-Access.md" }, { diff --git a/docs/zh-Hans/CLI.md b/docs/zh-Hans/CLI.md index 4d037ef2fd..658545c4fd 100644 --- a/docs/zh-Hans/CLI.md +++ b/docs/zh-Hans/CLI.md @@ -135,6 +135,8 @@ abp update [options] * `--include-previews` 或 `-p`: 将预览版, 测试版本 和 rc 包 同时更新到最新版本. * `--npm`: 仅更新NPM包 * `--nuget`: 仅更新的NuGet包 +* `--solution-path` 或 `-sp`: 指定解决方案路径/目录. 默认使用当前目录 +* `--solution-name` 或 `-sn`: 指定解决方案名称. 默认在目录中搜索`*.sln`文件. ### 切换到每晚构建(预览)包 @@ -164,6 +166,10 @@ CLI的一些功能需要登录到abp.io平台. 使用你的用户名登录 abp login ``` +```bash +abp login -p +``` + 请注意,新的登录将终止先前的会话并创建一个新的会话. ### logout diff --git a/docs/zh-Hans/Getting-Started-Angular-Template.md b/docs/zh-Hans/Getting-Started-Angular-Template.md index d2ec83293b..d242b4f39b 100644 --- a/docs/zh-Hans/Getting-Started-Angular-Template.md +++ b/docs/zh-Hans/Getting-Started-Angular-Template.md @@ -1,3 +1,8 @@ -## Getting Started With the Angular Application Template +# 启动模板入门 -TODO... \ No newline at end of file +请参阅以下教程,了解如何使用预构建的应用程序启动模板开始使用ABP框架: + +* [ASP.NET Core MVC / Razor Pages UI 入门](Getting-Started?UI=MVC&DB=EF&Tiered=No) +* [Angular UI 入门](Getting-Started?UI=NG&DB=EF&Tiered=No) + + \ No newline at end of file diff --git a/docs/zh-Hans/Getting-Started-AspNetCore-Application.md b/docs/zh-Hans/Getting-Started-AspNetCore-Application.md index 46348ecbdf..9d195562fd 100644 --- a/docs/zh-Hans/Getting-Started-AspNetCore-Application.md +++ b/docs/zh-Hans/Getting-Started-AspNetCore-Application.md @@ -2,7 +2,7 @@ 本教程将介绍如何开始以最少的依赖关系开始使用ABP开发. -通常情况下你需要下载一个 ***[启动模板](https://abp.io/Templates)*** +通常情况下你需要下载一个 ***[启动模板](Getting-Started-AspNetCore-MVC-Template.md)*** ### 创建一个新项目 diff --git a/docs/zh-Hans/Getting-Started-AspNetCore-MVC-Template.md b/docs/zh-Hans/Getting-Started-AspNetCore-MVC-Template.md index 2778f3db42..d242b4f39b 100644 --- a/docs/zh-Hans/Getting-Started-AspNetCore-MVC-Template.md +++ b/docs/zh-Hans/Getting-Started-AspNetCore-MVC-Template.md @@ -1,102 +1,8 @@ -## ASP.NET Core MVC 模板入门 +# 启动模板入门 -### 创建新项目 +请参阅以下教程,了解如何使用预构建的应用程序启动模板开始使用ABP框架: -本教程使用 **ABP CLI** 创建一个新项目. 更多选项, 请参阅[入门](https://abp.io/get-started)页面. +* [ASP.NET Core MVC / Razor Pages UI 入门](Getting-Started?UI=MVC&DB=EF&Tiered=No) +* [Angular UI 入门](Getting-Started?UI=NG&DB=EF&Tiered=No) -如果你之前未安装,请使用命令行安装ABP CLI: - -````bash -dotnet tool install -g Volo.Abp.Cli -```` - -在空文件夹中使用 `abp new` 命令来创建项目: - -````bash -abp new Acme.BookStore -```` - -> 你可以使用不同级别的命名空间; 例如BookStore, Acme.BookStore或Acme.Retail.BookStore. - -`new` 命令创建**分层MVC应用程序**, **Entity Framework Core**作为数据库提供程序. 但是,它还有其他选择. 有关所有可用选项,请参见[CLI文档](CLI.md) - -#### 预先要求 - -创建项目的要求: - -* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/) -* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/) -* [Node v12+](https://nodejs.org) -* [Yarn v1.19+](https://classic.yarnpkg.com/) - -### 解决方案结构 - -在**Visual Studio**中打开解决方案: - -![bookstore-visual-studio-solution](images/bookstore-visual-studio-solution-v3.png) - -该解决方案具有分层结构(基于[Domain Driven Design](Domain-Driven-Design.md)), 并包含配置好的的单元&集成测试项目,可与**EF Core**和**SQLite**数据库内存一起使用. - -> 请参阅[应用程序模板文档](Startup-Templates/Application.md)以详细了解解决方案结构. - -### 数据库连接字符串 - -查看`.Web`项目下`appsettings.json`文件中的 **连接字符串**: - -````json -{ - "ConnectionStrings": { - "Default": "Server=localhost;Database=BookStore;Trusted_Connection=True" - } -} -```` - -解决方案使用 **Entity Framework Core** 和 **MS SQL Server**. EF Core支持[各种](https://docs.microsoft.com/zh-cn/ef/core/providers/)数据库提供程序,因此你可以根据实际需要使用其他DBMS. 如果需要,请更改连接字符串. - -### 创建数据库并应用数据库迁移 - -你有两个选项来创建数据库. - -#### 使用DbMigrator应用程序 - -该解决方案包含一个控制台应用程序(在此示例中名为`Acme.BookStore.DbMigrator`),可以创建数据库,应用迁移和初始化数据. 它对开发和生产环境都很有用. - -> `.DbMigrator`项目有自己的`appsettings.json`. 因此,如果你更改了上面的连接字符串,则还应更改此字符串. - -右键单击`.DbMigrator`项目并选择 **设置为启动项目**: - -![set-as-startup-project](images/set-as-startup-project.png) - -按F5(或Ctrl + F5)运行应用程序. 它将具有如下所示的输出: - -![set-as-startup-project](images/db-migrator-app.png) - -#### 使用EF Core Update-Database命令 - -Ef Core具有`Update-Database`命令, 可根据需要创建数据库并应用挂起的迁移. 右键单击`.Web`项目并选择**设置为启动项目**: - -![set-as-startup-project](images/set-as-startup-project.png) - -打开**包管理器控制台(Package Manager Console)**, 选择`.EntityFrameworkCore.DbMigrations`项目作为**默认项目**并运行`Update-Database`命令: - -![pcm-update-database](images/pcm-update-database-v2.png) - -这将基于配置的连接字符串创建新数据库. - -> 使用`.Migrator`工具是建议的方法, 因为它还能初始化初始数据能够正确运行Web应用程序. - -### 运行应用程序 - -你现在可以运行应用程序,它将会打开**home**页面: - -![bookstore-homepage](images/bookstore-homepage.png) - -单击 **登录** 按钮, 输入用户名`admin`, 密码`1q2w3E*`, 登录应用程序. - -启动模板包括**身份管理**和**租户管理**模块. 登录后,将显示"管理"菜单, 你可以在其中管理**租户**,**角色**,**用户**和**权限**. 用户管理页面如下所示: - -![bookstore-user-management](images/bookstore-user-management-v2.png) - -### 下一步是什么? - -* [应用程序开发教程](Tutorials/AspNetCore-Mvc/Part-I.md) + \ No newline at end of file diff --git a/docs/zh-Hans/Getting-Started-With-Startup-Templates.md b/docs/zh-Hans/Getting-Started-With-Startup-Templates.md index 2b23350612..d242b4f39b 100644 --- a/docs/zh-Hans/Getting-Started-With-Startup-Templates.md +++ b/docs/zh-Hans/Getting-Started-With-Startup-Templates.md @@ -1,6 +1,8 @@ # 启动模板入门 -参阅下面的教程来学习如何开始使用的ABP框架预构建的应用程序启动模板: +请参阅以下教程,了解如何使用预构建的应用程序启动模板开始使用ABP框架: -* [ASP.NET Core MVC/Razor页面模板入门](Getting-Started-AspNetCore-MVC-Template.md) -* [Angular UI模板入门](Getting-Started-Angular-Template.md) \ No newline at end of file +* [ASP.NET Core MVC / Razor Pages UI 入门](Getting-Started?UI=MVC&DB=EF&Tiered=No) +* [Angular UI 入门](Getting-Started?UI=NG&DB=EF&Tiered=No) + + \ No newline at end of file diff --git a/docs/zh-Hans/Getting-Started.md b/docs/zh-Hans/Getting-Started.md new file mode 100644 index 0000000000..a2b3013fe5 --- /dev/null +++ b/docs/zh-Hans/Getting-Started.md @@ -0,0 +1,411 @@ +## 入门 + +````json +//[doc-params] +{ + "UI": ["MVC","NG"], + "DB": ["EF", "Mongo"], + "Tiered": ["Yes", "No"] +} +```` + +本教程介绍了如何创建一个新的{{if UI == "MVC"}} ASP.NET Core MVC web {{else if UI == "NG"}} Angular {{end}}. 配置并运行它. + +## 设置你的开发环境 + +创建第一个项目之前,需要正确的设置你的开发环境. + +### 预先要求 + +你需要安装以下工具: + +* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/) for Windows / [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/). +* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/) + +* [Node v12+](https://nodejs.org) +* [Yarn v1.19+](https://classic.yarnpkg.com/) + +> 你可以也使用其他支持.NET Core 和 ASP.NET Core的编辑器. + +### 安装ABP CLI + +[ABP CLI](./CLI.md)是一个命令行页面,用于为基于ABP的应用程序验证和自动化一些任务. + +> ABP CLI是[ABP框架](https://abp.io/)一个免费开源的工具. + +你需要使用以下命令安排ABP CLI: + +````shell +dotnet tool install -g Volo.Abp.Cli +```` + +如果你已经安装,你可以使用以下命令更新到最新版本: + +````shell +dotnet tool update -g Volo.Abp.Cli +```` + +## 创建新项目 + +> 本文假设你使用 **{{ UI_Value }}** 做为UI框架 **{{ DB_Value }}** 做为数据库提供程序,对于其它选项,你可以更改文档顶部的首选项. + +### 使用ABP CLI创建一个新项目 + +使用ABP CLI的 `new` 命令创建新项目: + +````shell +abp new Acme.BookStore -t app{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes" && UI != "NG"}} --tiered {{else if Tiered == "Yes" && UI == "NG"}}--separate-identity-server{{end}} +```` + +* `-t` 参数指定 [启动模板](Startup-Templates/Application.md) 名称. `app` 是一个启动模板名称,包含了预安装并且配置好的[ABP模块](Modules/Index.md). + +{{ if UI == "NG" }} + +* `-u` 指定UI框架, 本例中是 `angular`. + +{{ if Tiered == "Yes" }} + +* `--separate-identity-server` 参数用于将Identity服务器应用程序与API主机应用程序分隔开. 如果未指定,你将只有一个端点. + +{{ end }} + +{{ end }} + +{{ if DB == "Mongo" }} + +* `-d` 指定数据库提供程序, 本例中是 `mongodb`. + +{{ end }} + +{{ if Tiered == "Yes" && UI != "NG" }} + +* `--tiered` 参数用于创建n层解决方案,其中身份验证服务器层,UI层和API层在物理上是分离的. + +{{ end }} + +> 你可以使用不同级别的命令空间; 例如. BookStore, Acme.BookStore or Acme.Retail.BookStore. + +## 解决方案结构 + +{{ if UI == "MVC" }} + +创建项目后你会有以下解决方案目录和文件: + +![](images/solution-files-mvc.png) + +在Visual Studio中打开 `.sln` 文件时,将看到以下解决方案结构: + +{{if DB == "Mongo"}} + +![vs-default-app-solution-structure](images/vs-app-solution-structure-mongodb.png) + +{{else}} + +![vs-default-app-solution-structure](images/vs-app-solution-structure{{if Tiered == "Yes"}}-tiered{{end}}.png) + +{{end}} + +{{ else if UI == "NG" }} +在创建的解决方案中有三个文件夹: + +![](images/solution-files-non-mvc.png) + +* `angular` 文件夹包含Angular UI应用程序. +* `aspnet-core` 文件夹包含后端应用程序. +* `react-native` 文件夹包含React Native UI 应用程序. + +打开 `aspnet-core` 文件夹下的 `.sln`(`Visual Studio`解决方案)文件: +![vs-angular-app-backend-solution-structure](images/vs-spa-app-backend-structure{{if DB == "Mongo"}}-mongodb{{end}}.png) + +{{ end }} + +> ###### 关于解决方案中的项目 +> +> 根据你的**UI**,**数据库**和其他选项,你的解决方案的结构可能略有不同. + +该解决方案具有分层结构(基于[Domain Driven Design](Domain-Driven-Design.md)), 并包含配置好的的单元&集成测试项目. + +{{ if DB == "EF" }} + +集成测试项目已配置为可与 **EF Core** & **SQLite 内存** database同时使用. + +{{ else if DB == "Mongo" }} + +集成测试项目已配置为每个测试创建的内存中的**MongoDB**数据库(使用的[Mongo2Go](https://github.com/Mongo2Go/Mongo2Go)库). + +{{ end }} + +> 请参阅[应用程序模板文档](Startup-Templates/Application.md)详细了解解决方案结构. + +## 创建数据库 + +### 数据库连接字符串 + +检查 {{if UI == "MVC"}}{{if Tiered == "Yes"}}`.IdentityServer` 和 `.HttpApi.Host` 项目{{else}}`.Web` 项目{{end}}{{else if UI == "NG" }}`.HttpApi.Host` 项目{{end}}下 `appsettings.json` 文件中的 **链接字符串**: + +{{ if DB == "EF" }} + +````json +"ConnectionStrings": { + "Default": "Server=localhost;Database=BookStore;Trusted_Connection=True" +} +```` + +该解决方案配置为**Entity Framework Core**与**MS SQL Server**一起使用. EF Core支持[各种](https://docs.microsoft.com/en-us/ef/core/providers/)数据库提供程序,因此你可以使用任何受支持的DBMS. 请参阅[Entity Framework集成文档](https://docs.abp.io/en/abp/latest/Entity-Framework-Core)了解如何切换到另一个DBMS. + +### 数据库连接字符串 + +查看`.Web`项目下`appsettings.json`文件中的 **连接字符串**: + +````json +{ + "ConnectionStrings": { + "Default": "Server=localhost;Database=BookStore;Trusted_Connection=True" + } +} +```` + +解决方案使用 **Entity Framework Core** 和 **MS SQL Server**. EF Core支持[各种](https://docs.microsoft.com/zh-cn/ef/core/providers/)数据库提供程序,因此你可以根据实际需要使用其他DBMS. 如果需要,请更改连接字符串. + +### 应用迁移 + +该解决方案使用[Entity Framework Core Code First 迁移](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli). 你需要应用迁移来创建数据库,有两种方法迁移数据库. + +#### 使用DbMigrator应用程序应用迁移 + +该解决方案包含一个控制台应用程序(在此示例中名为`Acme.BookStore.DbMigrator`),可以创建数据库,应用迁移和初始化数据. 它对开发和生产环境都很有用. + +> `.DbMigrator`项目有自己的`appsettings.json`. 因此,如果你更改了上面的连接字符串,则还应更改此字符串. + +右键单击`.DbMigrator`项目并选择 **设置为启动项目**: + +![set-as-startup-project](images/set-as-startup-project.png) + +按F5(或Ctrl + F5)运行应用程序. 它将具有如下所示的输出: + +![set-as-startup-project](images/db-migrator-app.png) + +#### 使用EF Core Update-Database命令 + +Ef Core具有`Update-Database`命令, 可根据需要创建数据库并应用挂起的迁移. 右键单击`.Web`项目并选择**设置为启动项目**: + +{{ if UI == "MVC" }} + +右键单击{{if Tiered == "Yes"}}`.IdentityServer`{{else}}`.Web`{{end}}项目并选择**设置为启动项目**: + +{{ else if UI != "MVC" }} + +右键单击`.HttpApi.Host`项目并选择**设置为启动项目**: + +{{ end }} + +![set-as-startup-project](images/set-as-startup-project.png) + +打开**包管理器控制台(Package Manager Console)**, 选择`.EntityFrameworkCore.DbMigrations`项目作为**默认项目**并运行`Update-Database`命令: + +![package-manager-console-update-database](images/package-manager-console-update-database.png) + +这将基于配置的连接字符串创建新数据库. + +> 使用`.Migrator`工具是建议的方法, 因为它还能初始化初始数据能够正确运行Web应用程序. + +{{ else if DB == "Mongo" }} + +````json +"ConnectionStrings": { + "Default": "mongodb://localhost:27017/BookStore" +} +```` + +该解决方案被配置为在你的本地计算机中使用 **MongoDB**,因此你需要启动并运行一个MongoDB服务器实例或者将连接字符串更改为另一个MongoDB服务器. + +### 初始化种子数据 + +该解决方案附带一个 `.DbMigrator` 控制台应用程序,该应用程序为初始数据提供了种子. 它对于开发以及生产环境都很有用. + +> `.DbMigrator` 项目有自己的 `appsettings.json`.如果你更改了其他项目的 `appsettings.json`,也应该更改这个. + +右键点击 `.DbMigrator` 并选择 **设置为启动项目**. + +![set-as-startup-project](images/set-as-startup-project.png) + + 按F5(或Ctrl+F5)启动应用程序,你会看到以下输出: + + ![db-migrator-output](images/db-migrator-output.png) + +> 数据库创建后会初始化种子数据, 其中包含用于登录的 `admin` 用户. 所以你至少使用 `.DbMigrator` 一次. + +{{ end }} + +### 运行应用程序 + +{{ if UI == "MVC" }} + +{{ if Tiered == "Yes" }} + +确保 `.IdentityServer` 是启动项目,运行应用程序后会在你的浏览器打开一个 **login** 页面. + +> 在Visual Studio中使用Ctrl+F5(而不是F5)运行应用,如果你不用于调试,这会减少启动时间. + +你可以登录,但是不能在这里进入主应用程序,它仅是验证服务器. + +确保 `.HttpApi.Host` 是启动项目,运行应用程序后会在你的浏览器打开一个 **Swagger UI** 页面. + +![swagger-ui](images/swagger-ui.png) + +这里是Web应用程序使用的API应用程序. + +最后确保 `.Web` 是启动项目,运行应用程序后会在你的浏览器打开一个 **welcome** 页面. + +![mvc-tiered-app-home](images/bookstore-home.png) + +点击 **login** 按钮重定向到 `Identity Server` 来登录应用程序. + +![bookstore-login](images/bookstore-login.png) + +{{ else }} + +最后确保 `.Web` 是启动项目,运行应用程序后会在你的浏览器打开一个 **login** 页面. + +> 在Visual Studio中使用Ctrl+F5(而不是F5)运行应用,如果你不用于调试,这会减少启动时间. + +![bookstore-login](images/bookstore-login.png) + +{{ end }} + +{{ else if UI != "MVC" }} + +#### 运行HTTP API Host (服务器端) + +{{ if Tiered == "Yes" }} + +确保 `.IdentityServer` 是启动项目,运行应用程序后会在你的浏览器打开一个 **login** 页面. + +> 在Visual Studio中使用Ctrl+F5(而不是F5)运行应用,如果你不用于调试,这会减少启动时间. + +你可以登录,但是不能在这里进入主应用程序,它仅是验证服务器. + +{{ end }} + +确保 `.HttpApi.Host` 是启动项目,运行应用程序后会在你的浏览器打开一个 **Swagger UI** 页面. + +{{ if Tiered == "No" }} + +> 在Visual Studio中使用Ctrl+F5(而不是F5)运行应用,如果你不用于调试,这会减少启动时间. + +{{ end }} + +![swagger-ui](images/swagger-ui.png) + +你可以看到应用程序的API并进行测试. 更多信息,请参阅[Swagger UI](https://swagger.io/tools/swagger-ui/). + +> ##### Swagger UI 授权 +> +> 大多数的HTTP API都需要身份验证和授权. 如果你要测试授权API, 请手动进入 `/Account/Login` 页面, 输入用户名: `admin` 和密码: `1q2w3E*` 登录到应用程序. 然后你可以访问授权API. + +{{ end }} + +{{ if UI == "NG" }} +#### 运行 Angular 应用程序 (客户端) + +在 `angular` 下打开命令行终端, 输入 `yarn` 命令(我们推荐使用[yarn](https://yarnpkg.com/)包管理, `npm install` 在大多数情况下也可以工作). + +```bash +yarn +``` + +等到所有node模块加载成功, 执行 `yarn start` (或 `npm start`) 命令: + +```bash +yarn start +``` + +等待 `Angular CLI` 使用 `BrowserSync` 启动 `Webpack` dev-server. +它会负责编译你的 `TypeScript`代码, 并自动重新加载浏览器. +完成后 `Angular Live Development Server` 会监听 localhost:4200. +打开你的浏览器并导航到[localhost:4200](http://localhost:4200/). + +![bookstore-login](images/bookstore-login.png) + +{{ end }} + +输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序. + +![bookstore-home](images/bookstore-home.png) + +应用程序已经启动并执行,你可以基于该启动模板开发应用程序. + +#### 移动开发 + +ABP平台提供了[React Native](https://reactnative.dev/)模板用于开发移动应用程序. + +> 该解决方案默认 `react-native` 包含了React Native应用程序,如果你不计划使用React Native开发移动应用程序,你可以忽略并删除 `react-native` 文件夹. + +运行在Android模拟器或真机上的React Native应用程序无法连接到 `localhost` 上的后.要修复此问题,需要在本地IP上运行后端. + +{{ if Tiered == "No"}} +![React Native host project local IP entry](images/rn-host-local-ip.png) + +* 打开 `.HttpApi.Host` 文件夹下的 `appsettings.json` 文件. 将 `SelfUrl` 和 `Authority` 属性的 `localhost` 替换为你本地的IP地址. +* 打开 `.HttpApi.Host/Properties` 文件夹下的 `launchSettings.json` 文件. 将 `applicationUrl` 属性的 `localhost` 替换为你本地的IP地址. + +{{ else if Tiered == "Yes" }} + +![React Native tiered project local IP entry](images/rn-tiered-local-ip.png) + +* 打开 `.IdentityServer` 文件夹下的 `appsettings.json` 文件. 将 `SelfUrl` 属性的 `localhost` 替换为你本地的IP地址. +* 打开 `.IdentityServer/Properties` 文件夹下的 `launchSettings.json` 文件. 将 `applicationUrl` 属性的 `localhost` 替换为你本地的IP地址. +* 打开 `.HttpApi.Host` 文件夹下的 `appsettings.json` 文件. 将 `Authority` 属性的 `localhost` 替换为你本地的IP地址. +* 打开 `.HttpApi.Host/Properties` 文件夹下的 `launchSettings.json` 文件. 将 `applicationUrl` 属性的 `localhost` 替换为你本地的IP地址. + +{{ end }} + +按照**运行HTTP API Host (服务端口)**那样运行后端. + +> React Native应用程序不信任自动生成的.NET HTTPS证书,你可以在开发期间使用HTTP. + +在 `react-native` 文件夹打开命令行终端,输入 `yarn` 命令(我们推荐使用[yarn](https://yarnpkg.com/)包管理, `npm install` 在大多数情况下也可以工作). + +```bash +yarn +``` + +* 打开 `react-nativer` 文件夹下的 `Environment.js` 文件. 将 `apiUrl` 和 `issuer` 属性的 `localhost` 替换为你本地的IP地址: + +![react native environment local IP](images/rn-environment-local-ip.png) + +{{ if Tiered == "Yes" }} + +> 确保 `issuer` 与正在运行的 `.IdentityServer` 项目匹配, `apiUrl` 与正在运行的 `.HttpApi.Host` 项目匹配. + +{{else}} + +> 确保 `issuer` 和 `apiUrl` 与正在运行的 `.HttpApi.Host` 项目匹配 + +{{ end }} + +等到所有node模块加载成功, 执行 `yarn start` (或 `npm start`) 命令: + +```bash +yarn start +``` + +等待Expo CLI启动后Expo CLI在 `http://localhost:19002/` 地址要开管理页面. + +![expo-interface](images/rn-expo-interface.png) + +在上面的管理界面中,可以通过使用[Expo Client](https://expo.io/tools#client)扫描二维码,使用Android模拟器,iOS模拟器或真机来启动应用程序. + +> 请参阅[expo.io](https://docs.expo.io/versions/v36.0.0/workflow/ios-simulator/)上的[Android Studio模拟器](https://docs.expo.io/versions/v36.0.0/workflow/android-studio-emulator/)和[iOS模拟器文档](https://docs.expo.io/versions/v36.0.0/workflow/android-studio-emulator/). + +![React Native login screen on iPhone 11](images/rn-login-iphone.png) + +输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序. + +应用程序已经启动并执行,你可以基于该启动模板开发应用程序. + +> [应用程序启动模板](Startup-Templates/Application.md) 包含租户管理和Identity模块. + +## 下一步是什么? + +[应用程序开发教程](Tutorials/Part-1.md) diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-book-list-2.png b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-book-list-2.png deleted file mode 100644 index a7d49a661b..0000000000 Binary files a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-book-list-2.png and /dev/null differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-book-list.png b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-book-list.png deleted file mode 100644 index f531e6f457..0000000000 Binary files a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-book-list.png and /dev/null differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-template.png b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-template.png deleted file mode 100644 index 7cc96c8c94..0000000000 Binary files a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-template.png and /dev/null differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-localization-files-v2.png b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-localization-files-v2.png deleted file mode 100644 index 79314dd2dc..0000000000 Binary files a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-localization-files-v2.png and /dev/null differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-new-book-button.png b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-new-book-button.png deleted file mode 100644 index dfd4b5d8aa..0000000000 Binary files a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-new-book-button.png and /dev/null differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-pmc-add-book-migration-v2.png b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-pmc-add-book-migration-v2.png deleted file mode 100644 index edf2826361..0000000000 Binary files a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-pmc-add-book-migration-v2.png and /dev/null differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-swagger.png b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-swagger.png deleted file mode 100644 index 437c772503..0000000000 Binary files a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-swagger.png and /dev/null differ diff --git a/docs/zh-Hans/Tutorials/Part-1.md b/docs/zh-Hans/Tutorials/Part-1.md new file mode 100644 index 0000000000..e427b290a6 --- /dev/null +++ b/docs/zh-Hans/Tutorials/Part-1.md @@ -0,0 +1,1078 @@ +## ASP.NET Core {{UI_Value}} 教程 - 第一章 +````json +//[doc-params] +{ + "UI": ["MVC","NG"] +} +```` +{{ +if UI == "MVC" + DB="ef" + DB_Text="Entity Framework Core" + UI_Text="mvc" +else if UI == "NG" + DB="mongodb" + DB_Text="MongoDB" + UI_Text="angular" +else + DB ="?" + UI_Text="?" +end +}} + +### 关于本教程 + +在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的应用程序. **{{DB_Text}}**将用作ORM提供者,前端使用{{UI_Value}} 和 JavaScript. + +ASP.NET Core {{UI_Value}} 系列教程包括三个3个部分: + +- **Part-1: 创建项目和书籍列表页面(本章)** +- [Part-2: 创建,编辑,删除书籍](Part-II.md) +- [Part-3: 集成测试](Part-III.md) + +> 你也可以观看由ABP社区成员为本教程录制的[视频课程](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application). + +### 创建新项目 + +创建一个名为 `Acme.BookStore` 的新项目,其中 `Acme` 是公司名 `BookStore` 是项目名. 你可以参阅[入门](../Getting-Started?UI={{UI}}#run-the-application) 文档了解如何创建新项目. 我们将使用CLI创建新项目. + +#### 创建项目 + +使用以下命令创建一个新的ABP项目,使用 `{{DB_Text}}` 做为数据库提供者, UI选项使用 `{{UI_Value}}`. 其他CLI选项请参考[ABP CLI](https://docs.abp.io/en/abp/latest/CLI)文档. + +```bash +abp new Acme.BookStore --template app --database-provider {{DB}} --ui {{UI_Text}} --mobile none +``` + +![Creating project](./images/bookstore-create-project-{{UI_Text}}.png) + +### 应用迁移 + +项目创建后,需要应用初始化迁移创建数据库. 运行 `Acme.BookStore.DbMigrator` 应用程序. 它会应用所有迁移,完成流程后你会看到以下结果,数据库已经准备好了! + +![Migrations applied](./images/bookstore-migrations-applied-{{UI_Text}}.png) + +> 另外你也可以在 Visual Studio 包管理控制台运行 `Update-Database` 命令应用迁移. + +#### 初始化数据库表 + +![Initial database tables](./images/bookstore-database-tables-{{DB}}.png) + +### 运行应用程序 + +右键单击{{if UI == "MVC"}} `Acme.BookStore.Web`{{end}} {{if UI == "NG"}} `Acme.BookStore.HttpApi.Host` {{end}} 项目**设置为启动项**. 使用 **CTRL+F5** 或 **F5** 运行应用程序. {{if UI == "NG"}}你会看到BookStore API的Swagger UI.{{end}} + +更多信息,参阅[入门教程](../../Getting-Started?UI={{UI}})的运行应用程序部分. + +![Set as startup project](./images/bookstore-start-project-{{UI_Text}}.png) + +{{if UI == "NG"}} + +在 `angular` 下打开命令行终端,执行 `yarn` 命令: + +```bash +yarn +``` + +所有的模块加载后,执行 `yarn start` 命令: + +```bash +yarn start +``` + +默认网站从以下URL访问: + +http://localhost:4200/ + +如果你成功看到登录页面,可以按 `ctrl-c` 退出Angular托管.(我们稍后再运行). + +> 注意, Firefox不使用Windows凭据存储,你需要手动将自签名的开发人员证书导入到Firefox. 打开Firefox并导航到以下网址: +> +> https://localhost:44322/api/abp/application-configuration +> +> 如果你看到下图,单击 **Accept the Risk 和 Continue** 按钮绕过警告. +> +> ![Set as startup project](./images/mozilla-self-signed-cert-error.png) + +{{end}} + +默认的登录凭证: + +* **Username**: admin +* **Password**: 1q2w3E* + +### 解决方案的结构 + +下面的图片展示了从启动模板创建的项目是如何分层的. + +![bookstore-visual-studio-solution](./images/bookstore-solution-structure-{{UI_Text}}.png) + +> 你可以查看[应用程序模板文档](../startup-templates/application#solution-structure)以详细了解解决方案结构. + +### 创建Book实体 + +启动模板中的域层分为两个项目: + + - `Acme.BookStore.Domain`包含你的[实体](https://docs.abp.io/zh-Hans/abp/latest/Entities), [领域服务](https://docs.abp.io/zh-Hans/abp/latest/Domain-Services)和其他核心域对象. + - `Acme.BookStore.Domain.Shared`包含可与客户共享的常量,枚举或其他域相关对象. + +在解决方案的**领域层**(`Acme.BookStore.Domain`项目)中定义[实体](https://docs.abp.io/zh-Hans/abp/latest/Entities). 该应用程序的主要实体是`Book`. 在`Acme.BookStore.Domain`项目中创建一个名为`Book`的类,如下所示: + +````C# +using System; +using Volo.Abp.Domain.Entities.Auditing; + +namespace Acme.BookStore +{ + public class Book : AuditedAggregateRoot + { + public string Name { get; set; } + + public BookType Type { get; set; } + + public DateTime PublishDate { get; set; } + + public float Price { get; set; } + + protected Book() + { + } + public Book(Guid id, string name, BookType type, DateTime publishDate, float price) + :base(id) + { + Name = name; + Type = type; + PublishDate = publishDate; + Price = price; + } + } +} +```` + +* ABP为实体提供了两个基本的基类: `AggregateRoot`和`Entity`. **Aggregate Root**是**域驱动设计(DDD)** 概念之一. 有关详细信息和最佳做法,请参阅[实体文档](https://docs.abp.io/zh-Hans/abp/latest/Entities). +* `Book`实体继承了`AuditedAggregateRoot`,`AuditedAggregateRoot`类在`AggregateRoot`类的基础上添加了一些审计属性(`CreationTime`, `CreatorId`, `LastModificationTime` 等). +* `Guid`是`Book`实体的主键类型. +* 使用 **数据注解** 为EF Core添加映射.或者你也可以使用 EF Core 自带的[fluent mapping API](https://docs.microsoft.com/en-us/ef/core/modeling). + +#### BookType枚举 + +上面所用到的`BookType`枚举定义如下: + +````C# +namespace Acme.BookStore +{ + public enum BookType + { + Undefined, + Adventure, + Biography, + Dystopia, + Fantastic, + Horror, + Science, + ScienceFiction, + Poetry + } +} +```` + +#### 将Book实体添加到DbContext中 + +{{if DB == "ef"}} + +EF Core需要你将实体和 `DbContext` 建立关联.最简单的做法是在`Acme.BookStore.EntityFrameworkCore`项目的`BookStoreDbContext`类中添加`DbSet`属性.如下所示: + +````C# +public class BookStoreDbContext : AbpDbContext +{ + public DbSet Books { get; set; } + ... +} +```` + +{{end}} + +{{if DB == "mongodb"}} + +添加 `IMongoCollection Books` 属性到 `Acme.BookStore.MongoDB` 项目的 `BookStoreMongoDbContext` 中. + +```csharp +public class BookStoreMongoDbContext : AbpMongoDbContext +{ + public IMongoCollection Users => Collection(); + public IMongoCollection Books => Collection();//<--added this line--> + //... +} +``` + +{{end}} + +{{if DB == "ef"}} + +#### 配置你的Book实体 + +在 `Acme.BookStore.EntityFrameworkCore` 项目中打开 `BookStoreDbContextModelCreatingExtensions.cs` 文件,并将以下代码添加到 `ConfigureBookStore` 方法的末尾以配置Book实体: + +````csharp +builder.Entity(b => +{ + b.ToTable(BookStoreConsts.DbTablePrefix + "Books", BookStoreConsts.DbSchema); + b.ConfigureByConvention(); //auto configure for the base class props + b.Property(x => x.Name).IsRequired().HasMaxLength(128); +}); +```` + +添加 `using Volo.Abp.EntityFrameworkCore.Modeling;` 以使用 `ConfigureByConvention` 扩展方法. + +{{end}} + +{{if DB == "mongodb"}} + +#### 添加种子数据 + +添加种子数据是可选的,但第一次运行时最好将初始数据添加到数据库中. ABP提供了[数据种子系统](https://docs.abp.io/en/abp/latest/Data-Seeding). 在 `*.Domain` 项目下创建派生 `IDataSeedContributor` 的类: + +```csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Guids; + +namespace Acme.BookStore +{ + public class BookStoreDataSeederContributor + : IDataSeedContributor, ITransientDependency + { + private readonly IRepository _bookRepository; + private readonly IGuidGenerator _guidGenerator; + + public BookStoreDataSeederContributor( + IRepository bookRepository, + IGuidGenerator guidGenerator) + { + _bookRepository = bookRepository; + _guidGenerator = guidGenerator; + } + + public async Task SeedAsync(DataSeedContext context) + { + if (await _bookRepository.GetCountAsync() > 0) + { + return; + } + + await _bookRepository.InsertAsync( + new Book( + id: _guidGenerator.Create(), + name: "1984", + type: BookType.Dystopia, + publishDate: new DateTime(1949, 6, 8), + price: 19.84f + ) + ); + + await _bookRepository.InsertAsync( + new Book( + id: _guidGenerator.Create(), + name: "The Hitchhiker's Guide to the Galaxy", + type: BookType.ScienceFiction, + publishDate: new DateTime(1995, 9, 27), + price: 42.0f + ) + ); + } + } +} +``` + +{{end}} + +{{if DB == "ef"}} + +#### 添加新的Migration并更新数据库 + +这个启动模板使用了[EF Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/)来创建并维护数据库结构.打开 **程序包管理器控制台(Package Manager Console) (PMC)** (工具/Nuget包管理器菜单) + +![Open Package Manager Console](./images/bookstore-open-package-manager-console.png) + +选择 `Acme.BookStore.EntityFrameworkCore.DbMigrations`作为默认的项目然后执行下面的命令: + +```bash +Add-Migration "Created_Book_Entity" +``` + +![bookstore-pmc-add-book-migration](./images/bookstore-pmc-add-book-migration-v2.png) + +这样就会在 `Migrations` 文件夹中创建一个新的migration类.然后执行 `Update-Database` 命令更新数据库结构: + +````bash +Update-Database +```` + +![bookstore-update-database-after-book-entity](./images/bookstore-update-database-after-book-entity.png) + +#### 添加示例数据 + +`Update-Database`命令在数据库中创建了`AppBooks`表. 打开数据库并输入几个示例行,以便在页面上显示它们: + +```mssql +INSERT INTO AppBooks (Id,CreationTime,[Name],[Type],PublishDate,Price) VALUES +('f3c04764-6bfd-49e2-859e-3f9bfda6183e', '2018-07-01', '1984',3,'1949-06-08','19.84') + +INSERT INTO AppBooks (Id,CreationTime,[Name],[Type],PublishDate,Price) VALUES +('13024066-35c9-473c-997b-83cd8d3e29dc', '2018-07-01', 'The Hitchhiker`s Guide to the Galaxy',7,'1995-09-27','42') + +INSERT INTO AppBooks (Id,CreationTime,[Name],[Type],PublishDate,Price) VALUES +('4fa024a1-95ac-49c6-a709-6af9e4d54b54', '2018-07-02', 'Pet Sematary',5,'1983-11-14','23.7') +``` + +![bookstore-books-table](./images/bookstore-books-table.png) + +{{end}} + +### 创建应用服务 + +下一步是创建[应用服务](../Application-Services.md)来管理(创建,列出,更新,删除)书籍. 启动模板中的应用程序层分为两个项目: + +* `Acme.BookStore.Application.Contracts`主要包含你的DTO和应用程序服务接口. +* `Acme.BookStore.Application`包含应用程序服务的实现. + +#### BookDto + +在`Acme.BookStore.Application.Contracts`项目中创建一个名为`BookDto`的DTO类: + +````C# +using System; +using Volo.Abp.Application.Dtos; + +namespace Acme.BookStore +{ + public class BookDto : AuditedEntityDto + { + public string Name { get; set; } + + public BookType Type { get; set; } + + public DateTime PublishDate { get; set; } + + public float Price { get; set; } + } +} +```` + +* **DTO**类被用来在 **表示层** 和 **应用层** **传递数据**.查看[DTO文档](https://docs.abp.io/zh-Hans/abp/latest/Data-Transfer-Objects)查看更多信息. +* 为了在页面上展示书籍信息,`BookDto`被用来将书籍数据传递到表示层. +* `BookDto`继承自 `AuditedEntityDto`.跟上面定义的`Book`类一样具有一些审计属性. + +在将书籍返回到表示层时,需要将`Book`实体转换为`BookDto`对象. [AutoMapper](https://automapper.org)库可以在定义了正确的映射时自动执行此转换. 启动模板配置了AutoMapper,因此你只需在`Acme.BookStore.Application`项目的`BookStoreApplicationAutoMapperProfile`类中定义映射: + +````csharp +using AutoMapper; + +namespace Acme.BookStore +{ + public class BookStoreApplicationAutoMapperProfile : Profile + { + public BookStoreApplicationAutoMapperProfile() + { + CreateMap(); + } + } +} +```` + +#### CreateUpdateBookDto + +在`Acme.BookStore.Application.Contracts`项目中创建一个名为`CreateUpdateBookDto`的DTO类: + +````c# +using System; +using System.ComponentModel.DataAnnotations; +using Volo.Abp.AutoMapper; + +namespace Acme.BookStore +{ + public class CreateUpdateBookDto + { + [Required] + [StringLength(128)] + public string Name { get; set; } + + [Required] + public BookType Type { get; set; } = BookType.Undefined; + + [Required] + public DateTime PublishDate { get; set; } + + [Required] + public float Price { get; set; } + } +} +```` + +* 这个DTO类被用于在创建或更新书籍的时候从用户界面获取图书信息. +* 它定义了数据注释属性(如`[Required]`)来定义属性的验证. DTO由ABP框架[自动验证](https://docs.abp.io/zh-Hans/abp/latest/Validation). + +就像上面的`BookDto`一样,创建一个从`CreateUpdateBookDto`对象到`Book`实体的映射: + +````csharp +using AutoMapper; + +namespace Acme.BookStore +{ + public class BookStoreApplicationAutoMapperProfile : Profile + { + public BookStoreApplicationAutoMapperProfile() + { + CreateMap(); + CreateMap(); //<--added this line--> + } + } +} +```` + +#### IBookAppService + +在`Acme.BookStore.Application.Contracts`项目中定义一个名为`IBookAppService`的接口: + +````C# +using System; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; + +namespace Acme.BookStore +{ + public interface IBookAppService : + ICrudAppService< //定义了CRUD方法 + BookDto, //用来展示书籍 + Guid, //Book实体的主键 + PagedAndSortedResultRequestDto, //获取书籍的时候用于分页和排序 + CreateUpdateBookDto, //用于创建书籍 + CreateUpdateBookDto> //用于更新书籍 + { + + } +} +```` + +* 框架定义应用程序服务的接口**不是必需的**. 但是,它被建议作为最佳实践. +* `ICrudAppService`定义了常见的**CRUD**方法:`GetAsync`,`GetListAsync`,`CreateAsync`,`UpdateAsync`和`DeleteAsync`. 你可以从空的`IApplicationService`接口继承并手动定义自己的方法. +* `ICrudAppService`有一些变体, 你可以在每个方法中使用单独的DTO,也可以分别单独指定. + +#### BookAppService + +在`Acme.BookStore.Application`项目中实现名为`BookAppService`的`IBookAppService`: + +````C# +using System; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Repositories; + +namespace Acme.BookStore +{ + public class BookAppService : + CrudAppService, + IBookAppService + { + public BookAppService(IRepository repository) + : base(repository) + { + + } + } +} +```` + +* `BookAppService`继承了`CrudAppService<...>`.它实现了上面定义的CRUD方法. +* `BookAppService`注入`IRepository `,这是`Book`实体的默认仓储. ABP自动为每个聚合根(或实体)创建默认仓储. 请参阅[仓储文档](https://docs.abp.io/zh-Hans/abp/latest/Repositories) +* `BookAppService`使用`IObjectMapper`将`Book`对象转换为`BookDto`对象, 将`CreateUpdateBookDto`对象转换为`Book`对象. 启动模板使用[AutoMapper](http://automapper.org/)库作为对象映射提供程序. 你之前定义了映射, 因此它将按预期工作. + +### 自动生成API Controllers + +你通常创建**Controller**以将应用程序服务公开为**HTTP API**端点. 因此允许浏览器或第三方客户端通过AJAX调用它们. ABP可以[**自动**](https://docs.abp.io/zh-Hans/abp/latest/API/Auto-API-Controllers)按照惯例将你的应用程序服务配置为MVC API控制器. + +#### Swagger UI + +启动模板配置为使用[Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)运行[swagger UI](https://swagger.io/tools/swagger-ui/). 运行应用程序并在浏览器中输入`https://localhost:XXXX/swagger/`(用你自己的端口替换XXXX)作为URL. + +你会看到一些内置的接口和`Book`的接口,它们都是REST风格的: + +![bookstore-swagger](images/bookstore-swagger.png) + +Swagger有一个很好的UI来测试API. 你可以尝试执行`[GET] /api/app/book` API来获取书籍列表. + +{{if UI == "MVC"}} + +### 动态JavaScript代理 + +在Javascript端通过AJAX的方式调用HTTP API接口是很常见的,你可以使用`$.ajax`或者其他的工具来调用接口.当然,ABP中提供了更好的方式. + +ABP **自动** 为所有的API接口创建了JavaScript **代理**.因此,你可以像调用 **JavaScript function**一样调用任何接口. + +#### 在浏览器的开发者控制台中测试接口 + +你可以使用你钟爱的浏览器的 **开发者控制台** 中轻松测试JavaScript代理.运行程序,并打开浏览器的 **开发者工具**(快捷键:F12),切换到 **Console** 标签,输入下面的代码并回车: + +````js +acme.bookStore.book.getList({}).done(function (result) { console.log(result); }); +```` + +* `acme.bookStore`是`BookAppService`的命名空间,转换成了[驼峰命名](https://en.wikipedia.org/wiki/Camel_case). +* `book`是`BookAppService`转换后的名字(去除了AppService后缀并转成了驼峰命名). +* `getList`是定义在`AsyncCrudAppService`基类中的`GetListAsync`方法转换后的名字(去除了Async后缀并转成了驼峰命名). +* `{}`参数用于将空对象发送到`GetListAsync`方法,该方法通常需要一个类型为`PagedAndSortedResultRequestDto`的对象,用于向服务器发送分页和排序选项(所有属性都是可选的,所以你可以发送一个空对象). +* `getList`方法返回了一个`promise`.因此,你可以传递一个回调函数到`done`(或者`then`)方法中来获取服务返回的结果. + +运行这段代码会产生下面的输出: + +![bookstore-test-js-proxy-getlist](./images/bookstore-test-js-proxy-getlist.png) + +你可以看到服务器返回的 **book list**.你还可以切换到开发者工具的 **network** 查看客户端到服务器端的通讯信息: + +![bookstore-test-js-proxy-getlist-network](./images/bookstore-test-js-proxy-getlist-network.png) + +我们使用`create`方法 **创建一本新书**: + +````js +acme.bookStore.book.create({ name: 'Foundation', type: 7, publishDate: '1951-05-24', price: 21.5 }).done(function (result) { console.log('successfully created the book with id: ' + result.id); }); +```` + +你会看到控制台会显示类似这样的输出: + +````text +successfully created the book with id: 439b0ea8-923e-8e1e-5d97-39f2c7ac4246 +```` + +检查数据库中的`Books`表以查看新书. 你可以自己尝试`get`,`update`和`delete`功能. + +### 创建书籍页面 + +现在我们来创建一些可见和可用的东西,取代经典的MVC,我们使用微软推荐的[Razor Pages UI](https://docs.microsoft.com/en-us/aspnet/core/tutorials/razor-pages/razor-pages-start). + +在 `Acme.BookStore.Web`项目的`Pages`文件夹下创建一个新的文件夹叫`Books`并添加一个名为`Index.cshtml`的Razor Page. + +![bookstore-add-index-page](./images/bookstore-add-index-page-v2.png) + +打开`Index.cshtml`并把内容修改成下面这样: + +**Index.cshtml:** + +````html +@page +@using Acme.BookStore.Web.Pages.Books +@inherits Acme.BookStore.Web.Pages.BookStorePage +@model IndexModel + +

Books

+```` + +* 此代码更改了Razor View Page Model的默认继承,因此它从`BookStorePage`类(而不是`PageModel`)继承.启动模板附带的`BookStorePage`类,提供所有页面使用的一些共享属性/方法. +* 确保`IndexModel`(Index.cshtml.cs)具有`Acme.BookStore.Web.Pages.Books`命名空间,或者在`Index.cshtml`中更新它. + +**Index.cshtml.cs:** + +```csharp +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Acme.BookStore.Web.Pages.Books +{ + public class IndexModel : PageModel + { + public void OnGet() + { + + } + } +} +``` + +#### 将Books页面添加到主菜单 + +打开`Menus`文件夹中的 `BookStoreMenuContributor` 类,在`ConfigureMainMenuAsync`方法的底部添加如下代码: + +````csharp +//... +namespace Acme.BookStore.Web.Menus +{ + public class BookStoreMenuContributor : IMenuContributor + { + private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) + { + //<-- added the below code + context.Menu.AddItem( + new ApplicationMenuItem("BooksStore", l["Menu:BookStore"]) + .AddItem( + new ApplicationMenuItem("BooksStore.Books", l["Menu:Books"], url: "/Books") + ) + ); + //--> + } + } +} +```` + +{{end}} + +#### 本地化菜单 + +本地化文本位于`Acme.BookStore.Domain.Shared`项目的`Localization/BookStore`文件夹下: + +![bookstore-localization-files](./images/bookstore-localization-files-v2.png) + +打开`en.json`文件,将`Menu:BookStore`和`Menu:Books`键的本地化文本添加到文件末尾: + +````json +{ + "Culture": "en", + "Texts": { + "Menu:Home": "Home", + "Welcome": "Welcome", + "LongWelcomeMessage": "Welcome to the application. This is a startup project based on the ABP framework. For more information, visit abp.io.", + + "Menu:BookStore": "Book Store", + "Menu:Books": "Books", + "Actions": "Actions", + "Edit": "Edit", + "PublishDate": "Publish date", + "NewBook": "New book", + "Name": "Name", + "Type": "Type", + "Price": "Price", + "CreationTime": "Creation time", + "AreYouSureToDelete": "Are you sure you want to delete this item?" + } +} +```` + +* ABP的本地化功能建立在[ASP.NET Core's standard localization]((https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization))之上并增加了一些扩展.查看[本地化文档](https://docs.abp.io/zh-Hans/abp/latest/Localization). +* 本地化key是任意的. 你可以设置任何名称. 我们更喜欢为菜单项添加`Menu:`前缀以区别于其他文本. 如果未在本地化文件中定义文本,则它将**返回**到本地化的key(ASP.NET Core的标准行为). + +运行该应用程序,看到新菜单项已添加到顶部栏: + +![bookstore-menu-items](./images/bookstore-new-menu-item.png) + +点击BookStore下Books子菜单项就会跳转到新增的书籍页面. + +#### 书籍列表 + +我们将使用[Datatables.net](https://datatables.net/)JQuery插件来显示页面上的表格列表. [Datatables](https://datatables.net/)可以完全通过AJAX工作,速度快,并提供良好的用户体验. Datatables插件在启动模板中配置,因此你可以直接在任何页面中使用它,而需要在页面中引用样式和脚本文件. + +##### Index.cshtml + +将`Pages/Books/Index.cshtml`改成下面的样子: + +````html +@page +@inherits Acme.BookStore.Web.Pages.BookStorePage +@model Acme.BookStore.Web.Pages.Books.IndexModel +@section scripts +{ + +} + + +

@L["Books"]

+
+ + + + + @L["Name"] + @L["Type"] + @L["PublishDate"] + @L["Price"] + @L["CreationTime"] + + + + +
+```` + +* `abp-script` [tag helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro)用于将外部的 **脚本** 添加到页面中.它比标准的`script`标签多了很多额外的功能.它可以处理 **最小化**和 **版本**.查看[捆绑 & 压缩文档](https://docs.abp.io/zh-Hans/abp/latest/UI/AspNetCore/Bundling-Minification)获取更多信息. +* `abp-card` 和 `abp-table` 是为Twitter Bootstrap的[card component](http://getbootstrap.com/docs/4.1/components/card/)封装的 **tag helpers**.ABP中有很多tag helpers,可以很方便的使用大多数[bootstrap](https://getbootstrap.com/)组件.你也可以使用原生的HTML标签代替tag helpers.使用tag helper可以通过智能提示和编译时类型检查减少HTML代码并防止错误.查看[tag helpers 文档](https://docs.abp.io/zh-Hans/abp/latest/UI/AspNetCore/Tag-Helpers/Index). +* 你可以像上面本地化菜单一样 **本地化** 列名. + +#### 添加脚本文件 + +在`Pages/Books/`文件夹中创建 `index.js`文件 + +![bookstore-index-js-file](./images/bookstore-index-js-file-v2.png) + +`index.js`的内容如下: + +````js +$(function () { + var dataTable = $('#BooksTable').DataTable(abp.libs.datatables.normalizeConfiguration({ + ajax: abp.libs.datatables.createAjax(acme.bookStore.book.getList), + columnDefs: [ + { data: "name" }, + { data: "type" }, + { data: "publishDate" }, + { data: "price" }, + { data: "creationTime" } + ] + })); +}); +```` + +* `abp.libs.datatables.createAjax`是帮助ABP的动态JavaScript API代理跟[Datatable](https://datatables.net/)的格式相适应的辅助方法. +* `abp.libs.datatables.normalizeConfiguration`是另一个辅助方法.不是必须的, 但是它通过为缺少的选项提供常规值来简化数据表配置. +* `acme.bookStore.book.getList`是获取书籍列表的方法(上面已经介绍过了) +* 查看 [Datatable文档](https://datatables.net/manual/) 了解更多配置项. + +最终的页面如下: + +![Book list](./images/bookstore-book-list-2.png) + +{{end}} + +{{if UI == "NG"}} + +### Angular 开发 + +#### 创建books页面 + +是时候创建可见和可用的东西了!开发ABP Angular前端应用程序时,需要使用一些工具: + +- [Angular CLI](https://angular.io/cli) 用于创建模块,组件和服务. +- [NGXS](https://ngxs.gitbook.io/ngxs/) 用于管理状态库. +- [Ng Bootstrap](https://ng-bootstrap.github.io/#/home) 用做UI组件库. +- [Visual Studio Code](https://code.visualstudio.com/) 用做代码编辑器 (你可以选择自己喜欢的编辑器). + +#### 安装 NPM 包 + +在 `angular` 目录下打开命令行窗口,选择 `yarn` 命令安装NPM包: + +```bash +yarn +``` + +#### BooksModule + +运行以下命令创建一个名为 `BooksModule` 的新模块: + +```bash +yarn ng generate module books --route books --module app.module +``` + +![Generating books module](./images/bookstore-creating-books-module-terminal.png) + +#### 路由 + +打开位于 `src\app` 目录下的 `app-routing.module.ts` 文件. 添加新的 `import` 和替换 `books` 路径: + +```js +import { ApplicationLayoutComponent } from '@abp/ng.theme.basic'; //==> added this line to imports <== + +//...replaced original books path with the below +{ + path: 'books', + component: ApplicationLayoutComponent, + loadChildren: () => import('./books/books.module').then(m => m.BooksModule), + data: { + routes: { + name: '::Menu:Books', + iconClass: 'fas fa-book' + } as ABP.Route + }, +} +``` + +* `ApplicationLayoutComponent` 配置将应用程序布局设置为新页面, 我们添加了 `data` 对象. `name` 是菜单项的名称,`iconClass` 是菜单项的图标. + +运行 `yarn start` 等待Angular为应用程序启动服务: + +```bash +yarn start +``` + +打开浏览器导航到 http://localhost:4200/books. 你会看到一个带有 "*books works!*" 的空白页. + +![initial-books-page](./images/bookstore-initial-books-page-with-layout.png) + +#### Book 列表组件 + +用以下内容替换 `books.component.html`: + +```html + +``` + +在命令行运行以下命令,生成名为 book-list 的新组件: + +```bash +yarn ng generate component books/book-list +``` + +![Creating books list](./images/bookstore-creating-book-list-terminal.png) + +打开 `app\books` 目录下的 `books.module.ts` 文件,使用以下内容替换它: + +```js +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { BooksRoutingModule } from './books-routing.module'; +import { BooksComponent } from './books.component'; +import { BookListComponent } from './book-list/book-list.component'; +import { SharedModule } from '../shared/shared.module'; //<== added this line ==> + +@NgModule({ + declarations: [BooksComponent, BookListComponent], + imports: [ + CommonModule, + BooksRoutingModule, + SharedModule, //<== added this line ==> + ] +}) +export class BooksModule { } +``` + +* 我们导入了 `SharedModule` 并添加到 `imports` 数组. + +打开 `app\books` 目录下的 `books-routing.module.ts` 文件用以下内容替换它: + +```js +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { BooksComponent } from './books.component'; +import { BookListComponent } from './book-list/book-list.component'; //<== added this line ==> + +//<== replaced routes ==> +const routes: Routes = [ + { + path: '', + component: BooksComponent, + children: [{ path: '', component: BookListComponent }], + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class BooksRoutingModule { } +``` + +* 我们导入了 `BookListComponent` 并替换 `routes` 常量. + +我们将看到books页面的 **book-list works!**: + +![Initial book list page](./images/bookstore-initial-book-list-page.png) + +#### 创建 BooksState + +运行以下命令创建名为 `BooksState` 的新state: + +```bash +npx @ngxs/cli --name books --directory src/app/books +``` + +* 此命令在 `src/app/books/state` 文件夹下创建了 `books.state.ts` 和 `books.actions.ts` 文件. 参阅 [NGXS CLI文档](https://www.ngxs.io/plugins/cli)了解更多. + +将 `BooksState` 导入到 `src/app` 文件夹中的 `app.module.ts` 中. 然后添加 `BooksState` 到 `NgxsModule` 的 `forRoot` 静态方法,作为该方法的第一个参数的数组元素. + +```js +// ... +import { BooksState } from './books/state/books.state'; //<== imported BooksState ==> + +@NgModule({ + imports: [ + // other imports + + NgxsModule.forRoot([BooksState]), //<== added BooksState ==> + + //other imports + ], + // ... +}) +export class AppModule {} +``` + +#### 生成代理 + +ABP CLI提供了 `generate-proxy` 命令为你的服务HTTP API生成客户端代理简化客户端使用服务的成本. 运行 `generate-proxy` 命令前你的host必须正在运行. 参阅 [CLI 文档](../CLI.md). + +在 `angular` 文件夹下运行以下命令: + +```bash +abp generate-proxy --module app +``` + +![Generate proxy command](./images/generate-proxy-command.png) + +生成的文件如下: + +![Generated files](./images/generated-proxies.png) + +#### GetBooks 动作 + +动作可以被认为是一个命令,它应该触发某些事情发生,或者是已经发生的事情的结果事件.[See NGXS Actions文档](https://www.ngxs.io/concepts/actions). + +打开 `app/books/state` 目录下的 `books.actions.ts` 文件用以下内容替换它: + +```js +export class GetBooks { + static readonly type = '[Books] Get'; +} +``` + +#### 实现 BooksState + +打开 `app/books/state` 目录下的 `books.state.ts` 文件用以下内容替换它: + +```js +import { PagedResultDto } from '@abp/ng.core'; +import { State, Action, StateContext, Selector } from '@ngxs/store'; +import { GetBooks } from './books.actions'; +import { BookService } from '../../app/shared/services'; +import { tap } from 'rxjs/operators'; +import { Injectable } from '@angular/core'; +import { BookDto } from '../../app/shared/models'; + +export class BooksStateModel { + public book: PagedResultDto; +} + +@State({ + name: 'BooksState', + defaults: { book: {} } as BooksStateModel, +}) +@Injectable() +export class BooksState { + @Selector() + static getBooks(state: BooksStateModel) { + return state.book.items || []; + } + + constructor(private bookService: BookService) {} + + @Action(GetBooks) + get(ctx: StateContext) { + return this.bookService.getListByInput().pipe( + tap((booksResponse) => { + ctx.patchState({ + book: booksResponse, + }); + }) + ); + } +} +``` + +* 我们添加了book属性到BooksStateModel模态框. +* 我们添加了 `GetBooks` 动作. 它通过 ABP CLI生成的 `BooksService` 检索图书数据. +* `NGXS` 需要在不订阅get函数的情况下返回被观察对象. + +#### BookListComponent + +打开 `app\books\book-list` 目录下的 `book-list.component.ts` 用以下内容替换它: + +```js +import { Component, OnInit } from '@angular/core'; +import { Select, Store } from '@ngxs/store'; +import { Observable } from 'rxjs'; +import { finalize } from 'rxjs/operators'; +import { BookDto, BookType } from '../../app/shared/models'; +import { GetBooks } from '../state/books.actions'; +import { BooksState } from '../state/books.state'; + +@Component({ + selector: 'app-book-list', + templateUrl: './book-list.component.html', + styleUrls: ['./book-list.component.scss'], +}) +export class BookListComponent implements OnInit { + @Select(BooksState.getBooks) + books$: Observable; + + booksType = BookType; + + loading = false; + + constructor(private store: Store) {} + + ngOnInit() { + this.get(); + } + + get() { + this.loading = true; + this.store + .dispatch(new GetBooks()) + .pipe(finalize(() => (this.loading = false))) + .subscribe(() => {}); + } +} +``` + +* 我们添加了 `get` 函数获取books更新store. +* 有关 `NGXS` 特性的更多信息请参见NGXS文档中的[Dispatching actions](https://ngxs.gitbook.io/ngxs/concepts/store#dispatching-actions)和[Select](https://ngxs.gitbook.io/ngxs/concepts/select). + +打开 `app\books\book-list` 目录下的 `book-list.component.html` 用以下内容替换它: + +```html +
+
+
+
+
+ {%{{{ "::Menu:Books" | abpLocalization }}}%} +
+
+
+
+
+
+ + + + + {%{{{ "::Name" | abpLocalization }}}%} + {%{{{ "::Type" | abpLocalization }}}%} + {%{{{ "::PublishDate" | abpLocalization }}}%} + {%{{{ "::Price" | abpLocalization }}}%} + + + + + {%{{{ data.name }}}%} + {%{{{ booksType[data.type] }}}%} + {%{{{ data.publishDate | date }}}%} + {%{{{ data.price }}}%} + + +
+
+``` + +* 我们添加了图书列表页面的HTML代码. + +现在你可以在浏览器看到最终结果: + +![Book list final result](./images/bookstore-book-list.png) + +项目的文件系统结构: + +![Book list final result](./images/bookstore-angular-file-tree.png) + +在本教程中我们遵循了官方的[Angular风格指南](https://angular.io/guide/styleguide#file-tree). + +{{end}} + +### 下一章 + +参阅[第二章](part-2.md)了解创建,更新和删除图书. \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/Part-2.md b/docs/zh-Hans/Tutorials/Part-2.md new file mode 100644 index 0000000000..d12e28290b --- /dev/null +++ b/docs/zh-Hans/Tutorials/Part-2.md @@ -0,0 +1 @@ +TODO .... \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/Part-3.md b/docs/zh-Hans/Tutorials/Part-3.md new file mode 100644 index 0000000000..d12e28290b --- /dev/null +++ b/docs/zh-Hans/Tutorials/Part-3.md @@ -0,0 +1 @@ +TODO .... \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/images/bookstore-actions-buttons.png b/docs/zh-Hans/Tutorials/images/bookstore-actions-buttons.png new file mode 100644 index 0000000000..e8243fedc7 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-actions-buttons.png differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-add-create-dialog-v2.png b/docs/zh-Hans/Tutorials/images/bookstore-add-create-dialog-v2.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-add-create-dialog-v2.png rename to docs/zh-Hans/Tutorials/images/bookstore-add-create-dialog-v2.png diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-add-edit-dialog.png b/docs/zh-Hans/Tutorials/images/bookstore-add-edit-dialog.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-add-edit-dialog.png rename to docs/zh-Hans/Tutorials/images/bookstore-add-edit-dialog.png diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-add-index-page-v2.png b/docs/zh-Hans/Tutorials/images/bookstore-add-index-page-v2.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-add-index-page-v2.png rename to docs/zh-Hans/Tutorials/images/bookstore-add-index-page-v2.png diff --git a/docs/zh-Hans/Tutorials/images/bookstore-angular-file-tree.png b/docs/zh-Hans/Tutorials/images/bookstore-angular-file-tree.png new file mode 100644 index 0000000000..a3197b6457 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-angular-file-tree.png differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-appservice-tests.png b/docs/zh-Hans/Tutorials/images/bookstore-appservice-tests.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-appservice-tests.png rename to docs/zh-Hans/Tutorials/images/bookstore-appservice-tests.png diff --git a/docs/zh-Hans/Tutorials/images/bookstore-book-list-2.png b/docs/zh-Hans/Tutorials/images/bookstore-book-list-2.png new file mode 100644 index 0000000000..a460d4241b Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-book-list-2.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-book-list.png b/docs/zh-Hans/Tutorials/images/bookstore-book-list.png new file mode 100644 index 0000000000..9e6cc9e010 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-book-list.png differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-books-table-actions.png b/docs/zh-Hans/Tutorials/images/bookstore-books-table-actions.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-books-table-actions.png rename to docs/zh-Hans/Tutorials/images/bookstore-books-table-actions.png diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-books-table.png b/docs/zh-Hans/Tutorials/images/bookstore-books-table.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-books-table.png rename to docs/zh-Hans/Tutorials/images/bookstore-books-table.png diff --git a/docs/zh-Hans/Tutorials/images/bookstore-confirmation-popup.png b/docs/zh-Hans/Tutorials/images/bookstore-confirmation-popup.png new file mode 100644 index 0000000000..a80b180f1c Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-confirmation-popup.png differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-dialog-2.png b/docs/zh-Hans/Tutorials/images/bookstore-create-dialog-2.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-dialog-2.png rename to docs/zh-Hans/Tutorials/images/bookstore-create-dialog-2.png diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-dialog.png b/docs/zh-Hans/Tutorials/images/bookstore-create-dialog.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-dialog.png rename to docs/zh-Hans/Tutorials/images/bookstore-create-dialog.png diff --git a/docs/zh-Hans/Tutorials/images/bookstore-create-project-angular.png b/docs/zh-Hans/Tutorials/images/bookstore-create-project-angular.png new file mode 100644 index 0000000000..b9eb38b8b7 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-create-project-angular.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-create-project-mvc.png b/docs/zh-Hans/Tutorials/images/bookstore-create-project-mvc.png new file mode 100644 index 0000000000..f453b20279 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-create-project-mvc.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-creating-book-list-terminal.png b/docs/zh-Hans/Tutorials/images/bookstore-creating-book-list-terminal.png new file mode 100644 index 0000000000..6f19dcc7bf Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-creating-book-list-terminal.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-creating-books-module-terminal.png b/docs/zh-Hans/Tutorials/images/bookstore-creating-books-module-terminal.png new file mode 100644 index 0000000000..ec9ef4c42f Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-creating-books-module-terminal.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-database-tables-ef.png b/docs/zh-Hans/Tutorials/images/bookstore-database-tables-ef.png new file mode 100644 index 0000000000..857b10de5b Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-database-tables-ef.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-database-tables-mongodb.png b/docs/zh-Hans/Tutorials/images/bookstore-database-tables-mongodb.png new file mode 100644 index 0000000000..8d78bd9a54 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-database-tables-mongodb.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-edit-button.png b/docs/zh-Hans/Tutorials/images/bookstore-edit-button.png new file mode 100644 index 0000000000..bfc1c64797 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-edit-button.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-empty-new-book-modal.png b/docs/zh-Hans/Tutorials/images/bookstore-empty-new-book-modal.png new file mode 100644 index 0000000000..2a02802bb9 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-empty-new-book-modal.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-final-actions-dropdown.png b/docs/zh-Hans/Tutorials/images/bookstore-final-actions-dropdown.png new file mode 100644 index 0000000000..4f41829f0d Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-final-actions-dropdown.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-generate-state-books.png b/docs/zh-Hans/Tutorials/images/bookstore-generate-state-books.png new file mode 100644 index 0000000000..be7a919017 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-generate-state-books.png differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-homepage.png b/docs/zh-Hans/Tutorials/images/bookstore-homepage.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-homepage.png rename to docs/zh-Hans/Tutorials/images/bookstore-homepage.png diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-index-js-file-v2.png b/docs/zh-Hans/Tutorials/images/bookstore-index-js-file-v2.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-index-js-file-v2.png rename to docs/zh-Hans/Tutorials/images/bookstore-index-js-file-v2.png diff --git a/docs/zh-Hans/Tutorials/images/bookstore-initial-book-list-page.png b/docs/zh-Hans/Tutorials/images/bookstore-initial-book-list-page.png new file mode 100644 index 0000000000..591cffb121 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-initial-book-list-page.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-initial-books-page-with-layout.png b/docs/zh-Hans/Tutorials/images/bookstore-initial-books-page-with-layout.png new file mode 100644 index 0000000000..629ad46444 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-initial-books-page-with-layout.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-localization-files-v2.png b/docs/zh-Hans/Tutorials/images/bookstore-localization-files-v2.png new file mode 100644 index 0000000000..542cda209c Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-localization-files-v2.png differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-menu-items.png b/docs/zh-Hans/Tutorials/images/bookstore-menu-items.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-menu-items.png rename to docs/zh-Hans/Tutorials/images/bookstore-menu-items.png diff --git a/docs/zh-Hans/Tutorials/images/bookstore-migrations-applied-angular.png b/docs/zh-Hans/Tutorials/images/bookstore-migrations-applied-angular.png new file mode 100644 index 0000000000..0724e4ae8f Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-migrations-applied-angular.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-migrations-applied-mvc.png b/docs/zh-Hans/Tutorials/images/bookstore-migrations-applied-mvc.png new file mode 100644 index 0000000000..d59c0ce1d3 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-migrations-applied-mvc.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-new-book-button.png b/docs/zh-Hans/Tutorials/images/bookstore-new-book-button.png new file mode 100644 index 0000000000..8112fe1352 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-new-book-button.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-new-book-form-v2.png b/docs/zh-Hans/Tutorials/images/bookstore-new-book-form-v2.png new file mode 100644 index 0000000000..72513de6e5 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-new-book-form-v2.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-new-book-form.png b/docs/zh-Hans/Tutorials/images/bookstore-new-book-form.png new file mode 100644 index 0000000000..95de64f8d4 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-new-book-form.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-new-menu-item.png b/docs/zh-Hans/Tutorials/images/bookstore-new-menu-item.png new file mode 100644 index 0000000000..97bf7fc7c1 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-new-menu-item.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-open-package-manager-console.png b/docs/zh-Hans/Tutorials/images/bookstore-open-package-manager-console.png new file mode 100644 index 0000000000..a640eb2681 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-open-package-manager-console.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-pmc-add-book-migration-v2.png b/docs/zh-Hans/Tutorials/images/bookstore-pmc-add-book-migration-v2.png new file mode 100644 index 0000000000..2baea20236 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-pmc-add-book-migration-v2.png differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-pmc-add-book-migration.png b/docs/zh-Hans/Tutorials/images/bookstore-pmc-add-book-migration.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-pmc-add-book-migration.png rename to docs/zh-Hans/Tutorials/images/bookstore-pmc-add-book-migration.png diff --git a/docs/zh-Hans/Tutorials/images/bookstore-service-terminal-output.png b/docs/zh-Hans/Tutorials/images/bookstore-service-terminal-output.png new file mode 100644 index 0000000000..cf6145e03f Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-service-terminal-output.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-solution-structure-angular.png b/docs/zh-Hans/Tutorials/images/bookstore-solution-structure-angular.png new file mode 100644 index 0000000000..07d064a880 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-solution-structure-angular.png differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-visual-studio-solution-v3.png b/docs/zh-Hans/Tutorials/images/bookstore-solution-structure-mvc.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-visual-studio-solution-v3.png rename to docs/zh-Hans/Tutorials/images/bookstore-solution-structure-mvc.png diff --git a/docs/zh-Hans/Tutorials/images/bookstore-start-project-angular.png b/docs/zh-Hans/Tutorials/images/bookstore-start-project-angular.png new file mode 100644 index 0000000000..08abf845a8 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-start-project-angular.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-start-project-mvc.png b/docs/zh-Hans/Tutorials/images/bookstore-start-project-mvc.png new file mode 100644 index 0000000000..133dc6f131 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-start-project-mvc.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-swagger-book-dto-properties.png b/docs/zh-Hans/Tutorials/images/bookstore-swagger-book-dto-properties.png new file mode 100644 index 0000000000..66d630bb56 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-swagger-book-dto-properties.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-swagger.png b/docs/zh-Hans/Tutorials/images/bookstore-swagger.png new file mode 100644 index 0000000000..3ce36a11bc Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-swagger.png differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-test-js-proxy-getlist-network.png b/docs/zh-Hans/Tutorials/images/bookstore-test-js-proxy-getlist-network.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-test-js-proxy-getlist-network.png rename to docs/zh-Hans/Tutorials/images/bookstore-test-js-proxy-getlist-network.png diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-test-js-proxy-getlist.png b/docs/zh-Hans/Tutorials/images/bookstore-test-js-proxy-getlist.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-test-js-proxy-getlist.png rename to docs/zh-Hans/Tutorials/images/bookstore-test-js-proxy-getlist.png diff --git a/docs/zh-Hans/Tutorials/images/bookstore-test-projects-angular.png b/docs/zh-Hans/Tutorials/images/bookstore-test-projects-angular.png new file mode 100644 index 0000000000..6a8947238e Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-test-projects-angular.png differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-test-projects-v2.png b/docs/zh-Hans/Tutorials/images/bookstore-test-projects-mvc.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-test-projects-v2.png rename to docs/zh-Hans/Tutorials/images/bookstore-test-projects-mvc.png diff --git a/docs/zh-Hans/Tutorials/images/bookstore-test-projects-v2.png b/docs/zh-Hans/Tutorials/images/bookstore-test-projects-v2.png new file mode 100644 index 0000000000..8701164d75 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-test-projects-v2.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-update-database-after-book-entity.png b/docs/zh-Hans/Tutorials/images/bookstore-update-database-after-book-entity.png new file mode 100644 index 0000000000..4889f4f757 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-update-database-after-book-entity.png differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-user-management.png b/docs/zh-Hans/Tutorials/images/bookstore-user-management.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-user-management.png rename to docs/zh-Hans/Tutorials/images/bookstore-user-management.png diff --git a/docs/zh-Hans/Tutorials/images/bookstore-visual-studio-solution-v3.png b/docs/zh-Hans/Tutorials/images/bookstore-visual-studio-solution-v3.png new file mode 100644 index 0000000000..307e3516a5 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-visual-studio-solution-v3.png differ diff --git a/docs/zh-Hans/Tutorials/images/generate-proxy-command.png b/docs/zh-Hans/Tutorials/images/generate-proxy-command.png new file mode 100644 index 0000000000..f850ce07a2 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/generate-proxy-command.png differ diff --git a/docs/zh-Hans/Tutorials/images/generated-proxies.png b/docs/zh-Hans/Tutorials/images/generated-proxies.png new file mode 100644 index 0000000000..9e466e7d55 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/generated-proxies.png differ diff --git a/docs/zh-Hans/Tutorials/images/mozilla-self-signed-cert-error.png b/docs/zh-Hans/Tutorials/images/mozilla-self-signed-cert-error.png new file mode 100644 index 0000000000..c9e2fc0e65 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/mozilla-self-signed-cert-error.png differ diff --git a/docs/zh-Hans/UI/Angular/Confirmation-Service.md b/docs/zh-Hans/UI/Angular/Confirmation-Service.md new file mode 100644 index 0000000000..a1a181837c --- /dev/null +++ b/docs/zh-Hans/UI/Angular/Confirmation-Service.md @@ -0,0 +1,181 @@ +# 确认弹层 + +你可以使用@abp/ng.theme.shared包中提供 `ConfirmationService` 放置在你项目的级别来显示确认弹层 + +## 入门 + +你不必在模块或组件级别提供 `ConfirmationService`,它已经在**根**级别提供,你可以在你的组件,指令或服务直接注入并使用它. + +```js +import { ConfirmationService } from '@abp/ng.theme.shared'; + +@Component({ + /* class metadata here */ +}) +class DemoComponent { + constructor(private confirmation: ConfirmationService) {} +} +``` + +## 用法 + +你可以使用 `ConfirmationService` 的 `success`, `warn`, `error` 和 `info` 方法显示一个确认弹层. + +### 如何显示一个确认弹层 + +```js +const confirmationStatus$ = this.confirmation.success('Message', 'Title'); +``` + +- `ConfirmationService` 方法接收三个参数,分别是 `message`, `title`, 和 `options`. +- `success`, `warn`, `error`, 和 `info` 方法返回一个[RxJS Subject](https://rxjs-dev.firebaseapp.com/guide/subject)监听确认弹层关闭事件. 事件值类型是 [`Confirmation.Status`](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts#L24)枚举. + +### 如何监听关闭事件 + +你可以订阅确认弹层关闭事件,例: + +```js +import { Confirmation, ConfirmationService } from '@abp/ng.theme.shared'; + +constructor(private confirmation: ConfirmationService) {} + +this.confirmation + .warn('::WillBeDeleted', { key: '::AreYouSure', defaultValue: 'Are you sure?' }) + .subscribe((status: Confirmation.Status) => { + // your code here + }); +``` + +- `message` 和 `title` 参数接收字符串,本地化Key或本地化对象. 参阅[本地化文档](./Localization.md) +- `Confirmation.Status` 是一个枚举,具有三个属性; + - `Confirmation.Status.confirm` 是一个关闭事件值,当通过确认按钮关闭弹出窗口时触发此事件. + - `Confirmation.Status.reject` 是一个关闭事件值,当通过“取消”按钮关闭弹出窗口时触发此事件. + - `Confirmation.Status.dismiss` 是一个关闭事件值,当通过按Escape键关闭弹出窗口时触发此事件. + +如果你对确认状态不感兴趣,则不必订阅返回的observable: + +```js +this.confirmation.error('You are not authorized.', 'Error'); +``` + +### 如何显示具有给定选项的确认弹层 + +选项可以作为第三个参数传递给`success`, `warn`, `error`, 和 `info` 方法: + +```js +const options: Partial = { + hideCancelBtn: false, + hideYesBtn: false, + cancelText: 'Close', + yesText: 'Confirm', + messageLocalizationParams: ['Demo'], + titleLocalizationParams: [], +}; + +this.confirmation.warn( + 'AbpIdentity::RoleDeletionConfirmationMessage', + 'Are you sure?', + options, +); +``` + +- `hideCancelBtn` 选项为 `true` 时隐藏取消按钮. 默认值为 `false`. +- `hideYesBtn` 选项为 `true` 时隐藏确认按钮. 默认值为 `false`. +- `cancelText` 是取消按钮的文本,可以传递本地化键或本地化对象. 默认值是 `AbpUi::Cancel`. +- `yesText` 是确定按钮的文本,可以传递本地化键或本地化对象. 默认值是 `AbpUi::Yes`. +- `messageLocalizationParams`是用于消息本地化的插值参数. +- `titleLocalizationParams` 是标题本地化的插值参数. + +使用以上选项确认弹层窗口如下所示: + +![confirmation](./images/confirmation.png) + +你可以传递HTML字符串作为标题,消息或按钮文本. 例如: + +```js +const options: Partial = { + yesText: 'Yes, delete it', +}; + +this.confirmation.warn( + ` + Role Demo will be deleted +
+ Do you confirm that? + `, + 'Are you sure?', + options, +); +``` + +由于这些值现在是HTML,因此应该手动处理本地化. 参阅[LocalizationService](./Localization#using-the-localization-service)了解如何实现. + +> 注意,Angular会清除所有字符串,并且并非每个HTML字符串都可以使用. 仅显示被Angular视为"安全"的值. + +### 如何删除一个确认弹层 + +打开的确认弹出窗口可以通过 `clear` 方法手动删除: + +```js +this.confirmation.clear(); +``` + +## API + +### success + +```js +success( + message: Config.LocalizationParam, + title: Config.LocalizationParam, + options?: Partial, +): Observable +``` + +> 请参见[`Config.LocalizationParam`类型](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/core/src/lib/models/config.ts#L46)和[Confirmation名称空间](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts) + + +### warn + +```js +warn( + message: Config.LocalizationParam, + title: Config.LocalizationParam, + options?: Partial, +): Observable +``` + +### error + +```js +error( + message: Config.LocalizationParam, + title: Config.LocalizationParam, + options?: Partial, +): Observable +``` + +### info + +```js +info( + message: Config.LocalizationParam, + title: Config.LocalizationParam, + options?: Partial, +): Observable +``` + +### clear + +```js +clear( + status: Confirmation.Status = Confirmation.Status.dismiss +): void +``` + +- `status` 参数是确认关闭事件的值. + + +## 下一步是什么? + +- [Toast Overlay](./Toaster-Service.md) diff --git a/docs/zh-Hans/UI/Angular/Permission-Management.md b/docs/zh-Hans/UI/Angular/Permission-Management.md index 67ec1b787e..3a50da9591 100644 --- a/docs/zh-Hans/UI/Angular/Permission-Management.md +++ b/docs/zh-Hans/UI/Angular/Permission-Management.md @@ -76,4 +76,4 @@ const routes: Routes = [ ## 下一步是什么? -* [Config State](./Config-State.md) \ No newline at end of file +* [确认弹层](./Confirmation-Service.md) \ No newline at end of file diff --git a/docs/zh-Hans/UI/Angular/Toaster-Service.md b/docs/zh-Hans/UI/Angular/Toaster-Service.md new file mode 100644 index 0000000000..045cf9f2cf --- /dev/null +++ b/docs/zh-Hans/UI/Angular/Toaster-Service.md @@ -0,0 +1,155 @@ +# Toast Overlay + +你可以通常将@abp/ng.theme.shared包提供的 `ToasterService` 放置在你项目的根级别下以覆盖显示消息. + +## 入门 + +你不必在模块或组件级别提供 `ToasterService`,它已经在**根**级别提供,你可以在你的组件,指令或服务直接注入并使用它. + +```js +import { ToasterService } from '@abp/ng.theme.shared'; + +@Component({ + /* class metadata here */ +}) +class DemoComponent { + constructor(private toaster: ToasterService) {} +} +``` + +## 用法 + +你可以使用 `ToasterService` 的 `success`, `warn`, `error` 和 `info` 方法显示一个overlay. + +### 如何显示一个Toast Overlay + +```js +this.toast.success('Message', 'Title'); +``` + +- `ToasterService` 方法接收三个参数,分别是 `message`, `title`, 和 `options`. +- `success`, `warn`, `error`, 和 `info` 方法返回一个已打开的 toast overlay Id. 可以使用此id删除toast. + +### 如何显示具有给定选项的Toast Overlay + +选项可以作为第三个参数传递给`success`, `warn`, `error`, 和 `info` 方法: + +```js +import { Toaster, ToasterService } from '@abp/ng.theme.shared'; +//... + +constructor(private toaster: ToasterService) {} + +//... +const options: Partial = { + life: 10000, + sticky: false, + closable: true, + tapToDismiss: true, + messageLocalizationParams: ['Demo', '1'], + titleLocalizationParams: [] + }; + + this.toaster.error('AbpUi::EntityNotFoundErrorMessage', 'AbpUi::Error', options); +``` + +- `life` 选项是关闭的时间毫秒数. 默认值是 `5000`. +- `sticky` 选项为 `true` 时忽略 `life` 选项,将toast overlay留在屏幕上. 默认值是 `false`. +- `closable` 选项为 `true` 时在toast overlay上显示关闭图标. 默认值是 `true`. +- `tapToDismiss` 选项为 `true` 允许通过单击关闭toast overlay. 默认值是 `false`. +- `yesText` 是确定按钮的文本,可以传递本地化键或本地化对象. 默认值是 `AbpUi::Yes`. +- `messageLocalizationParams` 是用于消息本地化的插值参数. +- `titleLocalizationParams` 是标题本地化的插值参数. + +使用上面的选项,toast overlay看起来像这样: + +![toast](./images/toast.png) + +### 如何删除 Toast Overlay + +已打开的toast overlay可以通过手动调用 `remove` 方法传递指定的 toast `id`删除. + +```js +const toastId = this.toast.success('Message', 'Title') + +this.toast.remove(toastId); +``` + +### 如何删除所有的Toasts + +可以手动调用 `clear` 方法删除所有的已打开的toasts. + +```js +this.toast.clear(); +``` + +## API + +### success + +```js +success( + message: Config.LocalizationParam, + title: Config.LocalizationParam, + options?: Partial, +): number +``` + +- `Config` 命令空间可以从 `@abp/ng.core` 导入. +- `Toaster` 命令空间可以从 `@abp/ng.theme.shared` 导入. + +> 请参见[`Config.LocalizationParam`类型](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/core/src/lib/models/config.ts#L46)和[`Toaster` namespace](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/theme-shared/src/lib/models/toaster.ts) + +### warn + +```js +warn( + message: Config.LocalizationParam, + title: Config.LocalizationParam, + options?: Partial, +): number +``` + +### error + +```js +error( + message: Config.LocalizationParam, + title: Config.LocalizationParam, + options?: Partial, +): number +``` + +### info + +```js +info( + message: Config.LocalizationParam, + title: Config.LocalizationParam, + options?: Partial, +): number +``` + +### remove + +```js +remove(id: number): void +``` + +按给定的id移除打开的toast. + +### clear + +```js +clear(): void +``` + +删除所有打开的toasts. + +## 另请参阅 + +- [Confirmation Popup](./Confirmation-Service.md) + +## 确认弹层? + +- [Config State](./Config-State.md) diff --git a/docs/zh-Hans/UI/Angular/images/confirmation.png b/docs/zh-Hans/UI/Angular/images/confirmation.png new file mode 100644 index 0000000000..efe4c98ea7 Binary files /dev/null and b/docs/zh-Hans/UI/Angular/images/confirmation.png differ diff --git a/docs/zh-Hans/UI/Angular/images/toast.png b/docs/zh-Hans/UI/Angular/images/toast.png new file mode 100644 index 0000000000..24cdd0fe0c Binary files /dev/null and b/docs/zh-Hans/UI/Angular/images/toast.png differ diff --git a/docs/zh-Hans/UI/Common/Utils/Linked-List.md b/docs/zh-Hans/UI/Common/Utils/Linked-List.md index 871db960ab..53c356628f 100644 --- a/docs/zh-Hans/UI/Common/Utils/Linked-List.md +++ b/docs/zh-Hans/UI/Common/Utils/Linked-List.md @@ -1229,7 +1229,7 @@ find(predicate: ListIteratorFunction): ListNode | undefined 从链表中找到与给定谓词匹配的第一个节点: ```js -list.addTailMany(['a', 'b', 'b', 'c']); +list.addManyTail(['a', 'b', 'b', 'c']); // "a" <-> "b" <-> "b" <-> "c" @@ -1251,7 +1251,7 @@ findIndex(predicate: ListIteratorFunction): number 从链表中找到与给定谓词匹配的第一个节点的位置: ```js -list.addTailMany(['a', 'b', 'b', 'c']); +list.addManyTail(['a', 'b', 'b', 'c']); // "a" <-> "b" <-> "b" <-> "c" @@ -1279,7 +1279,7 @@ get(position: number): ListNode | undefined 查找并返回链表中特定位置的节点: ```js -list.addTailMany(['a', 'b', 'c']); +list.addManyTail(['a', 'b', 'c']); // "a" <-> "b" <-> "c" @@ -1303,7 +1303,7 @@ indexOf(value: T, compareFn?: ListComparisonFn): number 在链表中找到匹配给定值的第一个节点位置: ```js -list.addTailMany(['a', 'b', 'b', 'c']); +list.addManyTail(['a', 'b', 'b', 'c']); // "a" <-> "b" <-> "b" <-> "c" @@ -1325,7 +1325,7 @@ i3 === -1 你可以自定义比较器 ```js -list.addTailMany([{ x: 1 }, { x: 0 }, { x: 2 }, { x: 0 }, { x: 3 }]); +list.addManyTail([{ x: 1 }, { x: 0 }, { x: 2 }, { x: 0 }, { x: 3 }]); // {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3} @@ -1365,7 +1365,7 @@ forEach(iteratorFn: ListIteratorFn): void 从头到尾在链表中的所有节点上运行回调函数: ```js -list.addTailMany(['a', 'b', 'c']); +list.addManyTail(['a', 'b', 'c']); // "a" <-> "b" <-> "c" @@ -1381,7 +1381,7 @@ list.forEach((node, index) => console.log(node.value + index)); 链表是可迭代的. 换句话说你可以使用诸如`for ... of`之类的方法. ```js -list.addTailMany(['a', 'b', 'c']); +list.addManyTail(['a', 'b', 'c']); // "a" <-> "b" <-> "c" @@ -1405,7 +1405,7 @@ toArray(): T[] 转换链表值为数组: ```js -list.addTailMany(['a', 'b', 'c']); +list.addManyTail(['a', 'b', 'c']); // "a" <-> "b" <-> "c" @@ -1427,7 +1427,7 @@ toNodeArray(): T[] 转换链表节点为数组: ```js -list.addTailMany(['a', 'b', 'c']); +list.addManyTail(['a', 'b', 'c']); // "a" <-> "b" <-> "c" @@ -1449,7 +1449,7 @@ toString(mapperFn: ListMapperFn = JSON.stringify): string 将链表转换为节点及其关系的字符串表示形式: ```js -list.addTailMany(['a', 2, 'c', { k: 4, v: 'd' }]); +list.addManyTail(['a', 2, 'c', { k: 4, v: 'd' }]); // "a" <-> 2 <-> "c" <-> {"k":4,"v":"d"} @@ -1507,7 +1507,7 @@ export class ListNode { - `previous`引用列表中的上一个节点. ```js -list.addTailMany([ 0, 1, 2 ]); +list.addManyTail([ 0, 1, 2 ]); console.log( list.head.value, // 0 diff --git a/docs/zh-Hans/docs-nav.json b/docs/zh-Hans/docs-nav.json index 384c406475..984fc0270d 100644 --- a/docs/zh-Hans/docs-nav.json +++ b/docs/zh-Hans/docs-nav.json @@ -5,13 +5,7 @@ "items": [ { "text": "从启动模板开始", - "path": "Getting-Started-With-Startup-Templates.md", - "items": [ - { - "text": "ASP.NET Core MVC 模板", - "path": "Getting-Started-AspNetCore-MVC-Template.md" - } - ] + "path": "Getting-Started.md" }, { "text": "从空项目开始", @@ -218,8 +212,11 @@ }, { "text": "领域驱动设计", - "path": "Domain-Driven-Design.md", "items": [ + { + "text": "概述", + "path": "Domain-Driven-Design.md" + }, { "text": "领域层", "items": [ @@ -324,6 +321,14 @@ "text": "权限管理", "path": "UI/Angular/Permission-Management.md" }, + { + "text": "确认弹层", + "path": "UI/Angular/Confirmation-Service.md" + }, + { + "text": "Toast Overlay", + "path": "UI/Angular/Toaster-Service.md" + }, { "text": "配置状态", "path": "UI/Angular/Config-State.md" @@ -372,8 +377,11 @@ }, { "text": "数据访问", - "path":"Data-Access.md", "items": [ + { + "text": "概述", + "path": "Data-Access.md" + }, { "text": "连接字符串", "path": "Connection-Strings.md" @@ -457,8 +465,11 @@ }, { "text": "启动模板", - "path": "Startup-Templates/Index.md", "items": [ + { + "text": "概述", + "path": "Startup-Templates/Index.md" + }, { "text": "应用程序", "path": "Startup-Templates/Application.md" diff --git a/docs/zh-Hans/docs-params.json b/docs/zh-Hans/docs-params.json new file mode 100644 index 0000000000..23d079f9bb --- /dev/null +++ b/docs/zh-Hans/docs-params.json @@ -0,0 +1,28 @@ +{ + "parameters": [ + { + "name": "UI", + "displayName": "UI", + "values": { + "MVC": "MVC / Razor Pages", + "NG": "Angular" + } + }, + { + "name": "DB", + "displayName": "Database", + "values": { + "EF": "Entity Framework Core", + "Mongo": "MongoDB" + } + }, + { + "name": "Tiered", + "displayName": "Tiered", + "values": { + "No": "Not Tiered", + "Yes": "Tiered" + } + } + ] +} \ No newline at end of file diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index 3db0998855..5159f0a86e 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -277,7 +277,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Http.Client.Identi EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.ObjectExtending", "src\Volo.Abp.ObjectExtending\Volo.Abp.ObjectExtending.csproj", "{D1815C77-16D6-4F99-8814-69065CD89FB3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.ObjectExtending.Tests", "test\Volo.Abp.ObjectExtending.Tests\Volo.Abp.ObjectExtending.Tests.csproj", "{17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.ObjectExtending.Tests", "test\Volo.Abp.ObjectExtending.Tests\Volo.Abp.ObjectExtending.Tests.csproj", "{17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.TextTemplating", "src\Volo.Abp.TextTemplating\Volo.Abp.TextTemplating.csproj", "{9E53F91F-EACD-4191-A487-E727741F1311}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.TextTemplating.Tests", "test\Volo.Abp.TextTemplating.Tests\Volo.Abp.TextTemplating.Tests.csproj", "{251C7FD3-D313-4BCE-8068-352EC7EEA275}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -829,6 +833,14 @@ Global {17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}.Debug|Any CPU.Build.0 = Debug|Any CPU {17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}.Release|Any CPU.ActiveCfg = Release|Any CPU {17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}.Release|Any CPU.Build.0 = Release|Any CPU + {9E53F91F-EACD-4191-A487-E727741F1311}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E53F91F-EACD-4191-A487-E727741F1311}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E53F91F-EACD-4191-A487-E727741F1311}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9E53F91F-EACD-4191-A487-E727741F1311}.Release|Any CPU.Build.0 = Release|Any CPU + {251C7FD3-D313-4BCE-8068-352EC7EEA275}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {251C7FD3-D313-4BCE-8068-352EC7EEA275}.Debug|Any CPU.Build.0 = Debug|Any CPU + {251C7FD3-D313-4BCE-8068-352EC7EEA275}.Release|Any CPU.ActiveCfg = Release|Any CPU + {251C7FD3-D313-4BCE-8068-352EC7EEA275}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -970,6 +982,8 @@ Global {E1963439-2BE5-4DB5-8438-2A9A792A1ADA} = {447C8A77-E5F0-4538-8687-7383196D04EA} {D1815C77-16D6-4F99-8814-69065CD89FB3} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5} = {447C8A77-E5F0-4538-8687-7383196D04EA} + {9E53F91F-EACD-4191-A487-E727741F1311} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {251C7FD3-D313-4BCE-8068-352EC7EEA275} = {447C8A77-E5F0-4538-8687-7383196D04EA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs index 6f8ccf285a..a1601378d9 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs @@ -43,6 +43,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form await ProcessFieldsAsync(context, output); + RemoveFormGroupItemsNotInModel(context, output, list); + SetContent(context, output, list, childContent); SetFormAttributes(context, output); @@ -143,6 +145,14 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form } } + + protected virtual void RemoveFormGroupItemsNotInModel(TagHelperContext context, TagHelperOutput output, List items) + { + var models = GetModels(context, output); + + items.RemoveAll(x => models.All(m => !m.Name.Equals(x.PropertyName, StringComparison.InvariantCultureIgnoreCase))); + } + protected virtual async Task ProcessSelectGroupAsync(TagHelperContext context, TagHelperOutput output, ModelExpression model) { var abpSelectTagHelper = GetSelectGroupTagHelper(context, output, model); @@ -152,7 +162,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form protected virtual AbpTagHelper GetSelectGroupTagHelper(TagHelperContext context, TagHelperOutput output, ModelExpression model) { - return IsRadioGroup(model.ModelExplorer) ? + return IsRadioGroup(model.ModelExplorer) ? GetAbpRadioInputTagHelper(model) : GetSelectTagHelper(model); } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs index affef51b04..a2bb92a14d 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs @@ -58,7 +58,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form protected virtual async Task<(string, bool)> GetFormInputGroupAsHtmlAsync(TagHelperContext context, TagHelperOutput output) { var (inputTag, isCheckBox) = await GetInputTagHelperOutputAsync(context, output); - + var inputHtml = inputTag.Render(_encoder); var label = await GetLabelAsHtmlAsync(context, output, inputTag, isCheckBox); var info = GetInfoAsHtml(context, output, inputTag, isCheckBox); @@ -162,7 +162,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form protected virtual void AddDisabledAttribute(TagHelperOutput inputTagHelperOutput) { - if (inputTagHelperOutput.Attributes.ContainsName("disabled") == false && + if (inputTagHelperOutput.Attributes.ContainsName("disabled") == false && (TagHelper.IsDisabled || TagHelper.AspFor.ModelExplorer.GetAttribute() != null)) { inputTagHelperOutput.Attributes.Add("disabled", ""); @@ -171,7 +171,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form protected virtual void AddReadOnlyAttribute(TagHelperOutput inputTagHelperOutput) { - if (inputTagHelperOutput.Attributes.ContainsName("readonly") == false && + if (inputTagHelperOutput.Attributes.ContainsName("readonly") == false && (TagHelper.IsReadonly != false || TagHelper.AspFor.ModelExplorer.GetAttribute() != null)) { inputTagHelperOutput.Attributes.Add("readonly", ""); @@ -407,9 +407,10 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form list.Add(new FormGroupItem { HtmlContent = html, - Order = order + Order = order, + PropertyName = propertyName }); } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpRadioInputTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpRadioInputTagHelperService.cs index 6f71842b61..010cc1d075 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpRadioInputTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpRadioInputTagHelperService.cs @@ -167,9 +167,10 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form list.Add(new FormGroupItem { HtmlContent = html, - Order = order + Order = order, + PropertyName = propertyName }); } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs index b18bc6ed75..3ed4f63f82 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs @@ -125,7 +125,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form return await GetLabelAsHtmlUsingTagHelperAsync(context, output) + GetRequiredSymbol(context, output); } - + protected virtual string GetRequiredSymbol(TagHelperContext context, TagHelperOutput output) { if (!TagHelper.DisplayRequiredSymbol) @@ -221,7 +221,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form } protected virtual List GetSelectItemsFromAttribute( - SelectItems selectItemsAttribute, + SelectItems selectItemsAttribute, ModelExplorer explorer) { var selectItems = selectItemsAttribute.GetItems(explorer)?.ToList(); @@ -329,9 +329,10 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form list.Add(new FormGroupItem { HtmlContent = html, - Order = order + Order = order, + PropertyName = propertyName }); } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/FormGroupItem.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/FormGroupItem.cs index 258ba544f8..e3d33a4359 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/FormGroupItem.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/FormGroupItem.cs @@ -5,5 +5,7 @@ public string HtmlContent { get; set; } public int Order { get; set; } + + public string PropertyName { get; set; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/pl.json b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/pl-PL.json similarity index 92% rename from framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/pl.json rename to framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/pl-PL.json index 8509b7d02b..438d770a7c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/pl.json +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "GivenTenantIsNotAvailable": "Podany tenant jest niedostępny: {0}", "Tenant": "Tenant", diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs index e6a2319c72..ca8bfa9bf8 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs @@ -57,7 +57,11 @@ namespace Volo.Abp.Cli.Commands .Options .GetOrNull(Options.IncludePreviews.Short, Options.IncludePreviews.Long) != null; - var solution = Directory.GetFiles(directory, "*.sln", SearchOption.AllDirectories).FirstOrDefault(); + var solution = commandLineArgs.Options.GetOrNull(Options.SolutionName.Short, Options.SolutionName.Long); + if (solution.IsNullOrWhiteSpace()) + { + solution = Directory.GetFiles(directory, "*.sln", SearchOption.AllDirectories).FirstOrDefault(); + } if (solution != null) { @@ -101,11 +105,14 @@ namespace Volo.Abp.Cli.Commands sb.AppendLine("-p|--include-previews (if supported by the template)"); sb.AppendLine("--npm (Only updates NPM packages)"); sb.AppendLine("--nuget (Only updates Nuget packages)"); + sb.AppendLine("-sp|--solution-path (Specify the solution path)"); + sb.AppendLine("-sn|--solution-name (Specify the solution name)"); sb.AppendLine(""); sb.AppendLine("Some examples:"); sb.AppendLine(""); sb.AppendLine(" abp update"); sb.AppendLine(" abp update -p"); + sb.AppendLine(" abp update -sp \"D:\\projects\\\" -sn Acme.BookStore"); sb.AppendLine(""); sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); @@ -125,6 +132,12 @@ namespace Volo.Abp.Cli.Commands public const string Long = "solution-path"; } + public static class SolutionName + { + public const string Short = "sn"; + public const string Long = "solution-name"; + } + public static class IncludePreviews { public const string Short = "p"; diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs index 64f6a4a6f9..f27f52089a 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs @@ -10,7 +10,12 @@ namespace Volo.Abp.Localization { Check.NotNull(culture, nameof(culture)); - return Use(new CultureInfo(culture), uiCulture == null ? null : new CultureInfo(uiCulture)); + return Use( + new CultureInfo(culture), + uiCulture == null + ? null + : new CultureInfo(uiCulture) + ); } public static IDisposable Use([NotNull] CultureInfo culture, CultureInfo uiCulture = null) @@ -47,5 +52,12 @@ namespace Volo.Abp.Localization return false; } } + + public static string GetBaseCultureName(string cultureName) + { + return cultureName.Contains("-") + ? cultureName.Left(cultureName.IndexOf("-", StringComparison.Ordinal)) + : cultureName; + } } } diff --git a/framework/src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj b/framework/src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj index be953a1f26..a2f83399a0 100644 --- a/framework/src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj +++ b/framework/src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj @@ -14,24 +14,21 @@ - - - - - - + + + diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs index c2e29a7ff7..afcbaf6aef 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs @@ -1,12 +1,9 @@ -using System; -using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.BackgroundJobs; +using Volo.Abp.BackgroundJobs; using Volo.Abp.Emailing.Localization; -using Volo.Abp.Emailing.Templates; using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.Settings; +using Volo.Abp.TextTemplating; using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.Emailing @@ -15,15 +12,11 @@ namespace Volo.Abp.Emailing typeof(AbpSettingsModule), typeof(AbpVirtualFileSystemModule), typeof(AbpBackgroundJobsAbstractionsModule), - typeof(AbpLocalizationModule) + typeof(AbpLocalizationModule), + typeof(AbpTextTemplatingModule) )] public class AbpEmailingModule : AbpModule { - public override void PreConfigureServices(ServiceConfigurationContext context) - { - AutoAddDefinitionProviders(context.Services); - } - public override void ConfigureServices(ServiceConfigurationContext context) { Configure(options => @@ -43,41 +36,5 @@ namespace Volo.Abp.Emailing options.AddJob(); }); } - - private static void AutoAddDefinitionProviders(IServiceCollection services) - { - var definitionProviders = new List(); - - services.OnRegistred(context => - { - - if (typeof(IEmailTemplateDefinitionProvider).IsAssignableFrom(context.ImplementationType)) - { - definitionProviders.Add(context.ImplementationType); - } - }); - - services.Configure(options => - { - options.DefinitionProviders.AddIfNotContains(definitionProviders); - }); - } - - public override void OnApplicationInitialization(ApplicationInitializationContext context) - { - using (var scope = context.ServiceProvider.CreateScope()) - { - var emailTemplateDefinitionManager = - scope.ServiceProvider.GetRequiredService(); - - foreach (var templateDefinition in emailTemplateDefinitionManager.GetAll()) - { - foreach (var contributor in templateDefinition.Contributors) - { - contributor.Initialize(new EmailTemplateInitializationContext(templateDefinition, scope.ServiceProvider)); - } - } - } - } } } diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/AbpEmailTemplateOptions.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/AbpEmailTemplateOptions.cs deleted file mode 100644 index cb5a9d370b..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/AbpEmailTemplateOptions.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Volo.Abp.Collections; - -namespace Volo.Abp.Emailing.Templates -{ - public class AbpEmailTemplateOptions - { - public string DefaultLayout { get; set; } - - public ITypeList DefinitionProviders { get; } - - public AbpEmailTemplateOptions() - { - DefaultLayout = StandardEmailTemplates.DefaultLayout; - - DefinitionProviders = new TypeList(); - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplateProvider.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplateProvider.cs index c15012f0bf..385e0ef178 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplateProvider.cs +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplateProvider.cs @@ -1,16 +1,26 @@ -using Volo.Abp.Emailing.Templates.VirtualFiles; +using Volo.Abp.TextTemplating; namespace Volo.Abp.Emailing.Templates { - public class DefaultEmailTemplateProvider : EmailTemplateDefinitionProvider + public class DefaultEmailTemplateProvider : TemplateDefinitionProvider { - public override void Define(IEmailTemplateDefinitionContext context) + public override void Define(ITemplateDefinitionContext context) { - context.Add(new EmailTemplateDefinition(StandardEmailTemplates.DefaultLayout, defaultCultureName: "en", isLayout: true, layout: null) - .AddTemplateVirtualFiles("/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout")); + context.Add( + new TemplateDefinition( + StandardEmailTemplates.Layout, + defaultCultureName: "en", + isLayout: true + ).WithVirtualFilePath("/Volo/Abp/Emailing/Templates/Layout") + ); - context.Add(new EmailTemplateDefinition(StandardEmailTemplates.SimpleMessage, defaultCultureName: "en") - .AddTemplateVirtualFiles("/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Message")); + context.Add( + new TemplateDefinition( + StandardEmailTemplates.Message, + defaultCultureName: "en", + layout: StandardEmailTemplates.Layout + ).WithVirtualFilePath("/Volo/Abp/Emailing/Templates/Message") + ); } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Message/en.tpl b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Message/en.tpl deleted file mode 100644 index 6454b0b93a..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Message/en.tpl +++ /dev/null @@ -1 +0,0 @@ -{{message}} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplate.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplate.cs deleted file mode 100644 index ad6f8c4839..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplate.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Text; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplate - { - public EmailTemplateDefinition Definition { get; } - - public string Content => ContentBuilder.ToString(); - - protected StringBuilder ContentBuilder { get; set; } - - public EmailTemplate(string content, EmailTemplateDefinition definition) - { - ContentBuilder = new StringBuilder(content); - Definition = definition; - } - - public virtual void SetLayout(EmailTemplate layoutTemplate) - { - if (!layoutTemplate.Definition.IsLayout) - { - throw new AbpException($"Given template is not a layout template: {layoutTemplate.Definition.Name}"); - } - - var newStrBuilder = new StringBuilder(layoutTemplate.Content); - newStrBuilder.Replace("{{#content}}", ContentBuilder.ToString()); - - ContentBuilder = newStrBuilder; - } - - public virtual void SetContent(string content) - { - ContentBuilder = new StringBuilder(content); - } - - public virtual void Replace(string name, string value) - { - ContentBuilder.Replace("{{" + name + "}}", value); - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateContributorList.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateContributorList.cs deleted file mode 100644 index 44a91212ea..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateContributorList.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateContributorList : List - { - public string GetOrNull(string cultureName) - { - foreach (var contributor in this.AsQueryable().Reverse()) - { - var templateString = contributor.GetOrNull(cultureName); - if (templateString != null) - { - return templateString; - } - } - - return null; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinition.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinition.cs deleted file mode 100644 index 19f0eb2fa7..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinition.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using JetBrains.Annotations; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateDefinition - { - public const string DefaultLayoutPlaceHolder = "_"; - - public string Name { get; } - - public bool IsLayout { get; } - - public string Layout { get; set; } - - public Type LocalizationResource { get; set; } - - public EmailTemplateContributorList Contributors { get; } - - public string DefaultCultureName { get; } - - public bool SingleTemplateFile { get; } - - public EmailTemplateDefinition([NotNull] string name, Type localizationResource = null, bool isLayout = false, - string layout = DefaultLayoutPlaceHolder, string defaultCultureName = null, bool singleTemplateFile = false) - { - Name = Check.NotNullOrWhiteSpace(name, nameof(name)); - LocalizationResource = localizationResource; - Contributors = new EmailTemplateContributorList(); - IsLayout = isLayout; - Layout = layout; - DefaultCultureName = defaultCultureName; - SingleTemplateFile = singleTemplateFile; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionContext.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionContext.cs deleted file mode 100644 index 03a6c95d8b..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionContext.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Collections.Generic; -using System.Collections.Immutable; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateDefinitionContext : IEmailTemplateDefinitionContext - { - protected Dictionary EmailTemplates { get; } - - public EmailTemplateDefinitionContext(Dictionary emailTemplates) - { - EmailTemplates = emailTemplates; - } - - public virtual EmailTemplateDefinition GetOrNull(string name) - { - return EmailTemplates.GetOrDefault(name); - } - - public virtual IReadOnlyList GetAll() - { - return EmailTemplates.Values.ToImmutableList(); - } - - public virtual void Add(params EmailTemplateDefinition[] definitions) - { - if (definitions.IsNullOrEmpty()) - { - return; - } - - foreach (var definition in definitions) - { - EmailTemplates[definition.Name] = definition; - } - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionDictionary.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionDictionary.cs deleted file mode 100644 index aa36232156..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionDictionary.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateDefinitionDictionary : Dictionary - { - public EmailTemplateDefinitionDictionary Add(EmailTemplateDefinition emailTemplateDefinition) - { - if (ContainsKey(emailTemplateDefinition.Name)) - { - throw new AbpException( - "There is already an email template definition with given name: " + - emailTemplateDefinition.Name - ); - } - - this[emailTemplateDefinition.Name] = emailTemplateDefinition; - - return this; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionManager.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionManager.cs deleted file mode 100644 index 0491dc867e..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionManager.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateDefinitionManager : IEmailTemplateDefinitionManager, ISingletonDependency - { - protected Lazy> EmailTemplateDefinitions { get; } - - protected AbpEmailTemplateOptions Options { get; } - - protected IServiceProvider ServiceProvider { get; } - - public EmailTemplateDefinitionManager( - IOptions options, - IServiceProvider serviceProvider) - { - ServiceProvider = serviceProvider; - Options = options.Value; - - EmailTemplateDefinitions = - new Lazy>(CreateEmailTemplateDefinitions, true); - } - - public virtual EmailTemplateDefinition Get(string name) - { - Check.NotNull(name, nameof(name)); - - var template = GetOrNull(name); - - if (template == null) - { - throw new AbpException("Undefined template: " + name); - } - - return template; - } - - public virtual IReadOnlyList GetAll() - { - return EmailTemplateDefinitions.Value.Values.ToImmutableList(); - } - - public virtual EmailTemplateDefinition GetOrNull(string name) - { - return EmailTemplateDefinitions.Value.GetOrDefault(name); - } - - protected virtual IDictionary CreateEmailTemplateDefinitions() - { - var templates = new Dictionary(); - - using (var scope = ServiceProvider.CreateScope()) - { - var providers = Options - .DefinitionProviders - .Select(p => scope.ServiceProvider.GetRequiredService(p) as IEmailTemplateDefinitionProvider) - .ToList(); - - foreach (var provider in providers) - { - provider.Define(new EmailTemplateDefinitionContext(templates)); - } - } - - return templates; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionProvider.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionProvider.cs deleted file mode 100644 index e53505fafc..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Emailing.Templates -{ - public abstract class EmailTemplateDefinitionProvider : IEmailTemplateDefinitionProvider, ITransientDependency - { - public abstract void Define(IEmailTemplateDefinitionContext context); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateInitializationContext.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateInitializationContext.cs deleted file mode 100644 index 8cd4b95bbd..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateInitializationContext.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateInitializationContext - { - public EmailTemplateDefinition EmailTemplateDefinition { get; } - - public IServiceProvider ServiceProvider { get; } - - public EmailTemplateInitializationContext(EmailTemplateDefinition emailTemplateDefinition, - IServiceProvider serviceProvider) - { - EmailTemplateDefinition = emailTemplateDefinition; - ServiceProvider = serviceProvider; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateProvider.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateProvider.cs deleted file mode 100644 index 29fcf08664..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateProvider.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Globalization; -using System.Threading.Tasks; -using Microsoft.Extensions.Localization; -using Microsoft.Extensions.Options; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Localization; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateProvider : IEmailTemplateProvider, ITransientDependency - { - protected IEmailTemplateDefinitionManager EmailTemplateDefinitionManager; - protected ITemplateLocalizer TemplateLocalizer { get; } - protected AbpEmailTemplateOptions Options { get; } - protected IStringLocalizerFactory StringLocalizerFactory; - - public EmailTemplateProvider(IEmailTemplateDefinitionManager emailTemplateDefinitionManager, - ITemplateLocalizer templateLocalizer, IStringLocalizerFactory stringLocalizerFactory, - IOptions options) - { - EmailTemplateDefinitionManager = emailTemplateDefinitionManager; - TemplateLocalizer = templateLocalizer; - StringLocalizerFactory = stringLocalizerFactory; - Options = options.Value; - } - - public async Task GetAsync(string name) - { - return await GetAsync(name, CultureInfo.CurrentUICulture.Name); - } - - public async Task GetAsync(string name, string cultureName) - { - return await GetInternalAsync(name, cultureName); - } - - protected virtual async Task GetInternalAsync(string name, string cultureName) - { - var emailTemplateDefinition = EmailTemplateDefinitionManager.GetOrNull(name); - if (emailTemplateDefinition == null) - { - // TODO: Localized message - throw new AbpException($"email template {name} not definition"); - } - - var emailTemplateString = emailTemplateDefinition.Contributors.GetOrNull(cultureName); - if (emailTemplateString == null && emailTemplateDefinition.DefaultCultureName != null) - { - emailTemplateString = - emailTemplateDefinition.Contributors.GetOrNull(emailTemplateDefinition.DefaultCultureName); - if (emailTemplateString != null) - { - cultureName = emailTemplateDefinition.DefaultCultureName; - } - } - - if (emailTemplateString != null) - { - var emailTemplate = new EmailTemplate(emailTemplateString, emailTemplateDefinition); - - await SetLayoutAsync(emailTemplateDefinition, emailTemplate, cultureName); - - if (emailTemplateDefinition.SingleTemplateFile) - { - await LocalizeAsync(emailTemplateDefinition, emailTemplate, cultureName); - } - - return emailTemplate; - } - - // TODO: Localized message - throw new AbpException($"{cultureName} template not exist!"); - } - - protected virtual async Task SetLayoutAsync(EmailTemplateDefinition emailTemplateDefinition, - EmailTemplate emailTemplate, string cultureName) - { - var layout = emailTemplateDefinition.Layout; - if (layout.IsNullOrWhiteSpace()) - { - return; - } - - if (layout == EmailTemplateDefinition.DefaultLayoutPlaceHolder) - { - layout = Options.DefaultLayout; - } - - var layoutTemplate = await GetInternalAsync(layout, cultureName); - - emailTemplate.SetLayout(layoutTemplate); - } - - protected virtual Task LocalizeAsync(EmailTemplateDefinition emailTemplateDefinition, - EmailTemplate emailTemplate, string cultureName) - { - if (emailTemplateDefinition.LocalizationResource == null) - { - return Task.CompletedTask; - } - - var localizer = StringLocalizerFactory.Create(emailTemplateDefinition.LocalizationResource); - if (cultureName != null) - { - using (CultureHelper.Use(new CultureInfo(cultureName))) - { - emailTemplate.SetContent(TemplateLocalizer.Localize(localizer, emailTemplate.Content)); - } - } - else - { - emailTemplate.SetContent( - TemplateLocalizer.Localize(localizer, emailTemplate.Content) - ); - } - - return Task.CompletedTask; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateContributor.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateContributor.cs deleted file mode 100644 index d2c2775845..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateContributor.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Volo.Abp.Emailing.Templates -{ - public interface IEmailTemplateContributor - { - void Initialize(EmailTemplateInitializationContext context); - - string GetOrNull(string cultureName); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionContext.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionContext.cs deleted file mode 100644 index 1641562ccf..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionContext.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Volo.Abp.Emailing.Templates -{ - public interface IEmailTemplateDefinitionContext - { - EmailTemplateDefinition GetOrNull(string name); - - void Add(params EmailTemplateDefinition[] definitions); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionManager.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionManager.cs deleted file mode 100644 index 0936a2fe93..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionManager.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; -using JetBrains.Annotations; - -namespace Volo.Abp.Emailing.Templates -{ - public interface IEmailTemplateDefinitionManager - { - [NotNull] - EmailTemplateDefinition Get([NotNull] string name); - - IReadOnlyList GetAll(); - - EmailTemplateDefinition GetOrNull(string name); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionProvider.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionProvider.cs deleted file mode 100644 index 691d3874d6..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionProvider.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Volo.Abp.Emailing.Templates -{ - public interface IEmailTemplateDefinitionProvider - { - void Define(IEmailTemplateDefinitionContext context); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateProvider.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateProvider.cs deleted file mode 100644 index ab68dbe2ca..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateProvider.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Threading.Tasks; - -namespace Volo.Abp.Emailing.Templates -{ - public interface IEmailTemplateProvider - { - Task GetAsync(string name); - - Task GetAsync(string name, string cultureName); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/ITemplateRender.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/ITemplateRender.cs deleted file mode 100644 index 35ac14c8fd..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/ITemplateRender.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Threading.Tasks; - -namespace Volo.Abp.Emailing.Templates -{ - public interface ITemplateRender - { - Task RenderAsync(string template, object model = null); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout/en.tpl b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/Layout/en.tpl similarity index 84% rename from framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout/en.tpl rename to framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/Layout/en.tpl index 107fbb5230..57453a027f 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout/en.tpl +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/Layout/en.tpl @@ -4,6 +4,6 @@ - {{#content}} + {{content}} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/Message/en.tpl b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/Message/en.tpl new file mode 100644 index 0000000000..349de66b36 --- /dev/null +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/Message/en.tpl @@ -0,0 +1 @@ +{{model.message}} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/StandardEmailTemplates.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/StandardEmailTemplates.cs index 7e8cbedc68..6a60219a5e 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/StandardEmailTemplates.cs +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/StandardEmailTemplates.cs @@ -2,7 +2,7 @@ { public static class StandardEmailTemplates { - public const string DefaultLayout = "Abp.DefaultLayout"; - public const string SimpleMessage = "Abp.SimpleMessage"; + public const string Layout = "Abp.StandardEmailTemplates.Layout"; + public const string Message = "Abp.StandardEmailTemplates.Message"; } } diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/TemplateRender.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/TemplateRender.cs deleted file mode 100644 index 8c4e24017c..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/TemplateRender.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Threading.Tasks; -using Scriban; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Emailing.Templates -{ - public class TemplateRender : ITemplateRender, ITransientDependency - { - public async Task RenderAsync(string template, object model = null) - { - var scribanTemplate = Template.Parse(template); - return await scribanTemplate.RenderAsync(model); - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/EmailTemplateDefinitionExtensions.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/EmailTemplateDefinitionExtensions.cs deleted file mode 100644 index 7bee611d28..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/EmailTemplateDefinitionExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Volo.Abp.Emailing.Templates.VirtualFiles -{ - public static class EmailTemplateDefinitionExtensions - { - public static EmailTemplateDefinition AddTemplateVirtualFile( - this EmailTemplateDefinition emailTemplateDefinition, string path) - { - emailTemplateDefinition.Contributors.Add(new SingleVirtualFileEmailTemplateContributor(path)); - return emailTemplateDefinition; - } - - public static EmailTemplateDefinition AddTemplateVirtualFiles( - this EmailTemplateDefinition emailTemplateDefinition, string path) - { - emailTemplateDefinition.Contributors.Add(new MultipleVirtualFilesEmailTemplateContributor(path)); - return emailTemplateDefinition; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/MultipleVirtualFilesEmailTemplateContributor.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/MultipleVirtualFilesEmailTemplateContributor.cs deleted file mode 100644 index 2ae5f88cc5..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/MultipleVirtualFilesEmailTemplateContributor.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.FileProviders; -using Volo.Abp.VirtualFileSystem; - -namespace Volo.Abp.Emailing.Templates.VirtualFiles -{ - public class MultipleVirtualFilesEmailTemplateContributor : IEmailTemplateContributor - { - private readonly string _virtualPath; - - private IVirtualFileProvider _virtualFileProvider; - - private Dictionary _templateDictionary; - - private readonly object _syncObj = new object(); - - public MultipleVirtualFilesEmailTemplateContributor(string virtualPath) - { - _virtualPath = virtualPath; - } - - public void Initialize(EmailTemplateInitializationContext context) - { - _virtualFileProvider = context.ServiceProvider.GetRequiredService(); - } - - public string GetOrNull(string cultureName) - { - return GetTemplateDictionary().GetOrDefault(cultureName); - } - - private Dictionary GetTemplateDictionary() - { - var dictionaries = _templateDictionary; - if (dictionaries != null) - { - return dictionaries; - } - - lock (_syncObj) - { - dictionaries = _templateDictionary; - if (dictionaries != null) - { - return dictionaries; - } - - _templateDictionary = new Dictionary(); - foreach (var file in _virtualFileProvider.GetDirectoryContents(_virtualPath)) - { - if (file.IsDirectory) - { - continue; - } - - // TODO: How to normalize file names? - _templateDictionary.Add(file.Name.RemovePostFix(".tpl"), file.ReadAsString()); - } - - dictionaries = _templateDictionary; - } - - return dictionaries; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/SingleVirtualFileEmailTemplateContributor.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/SingleVirtualFileEmailTemplateContributor.cs deleted file mode 100644 index d72d18e99a..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/SingleVirtualFileEmailTemplateContributor.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.FileProviders; -using Volo.Abp.VirtualFileSystem; - -namespace Volo.Abp.Emailing.Templates.VirtualFiles -{ - public class SingleVirtualFileEmailTemplateContributor : IEmailTemplateContributor - { - private readonly string _virtualPath; - - private IVirtualFileProvider _virtualFileProvider; - - public SingleVirtualFileEmailTemplateContributor(string virtualPath) - { - _virtualPath = virtualPath; - } - - public void Initialize(EmailTemplateInitializationContext context) - { - _virtualFileProvider = context.ServiceProvider.GetRequiredService(); - } - - public string GetOrNull(string cultureName) - { - var file = _virtualFileProvider.GetFileInfo(_virtualPath); - if (file == null || !file.Exists || file.IsDirectory) - { - return null; - } - - return file.ReadAsString(); - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs index af728686fd..8db091eb30 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs @@ -32,7 +32,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory IAuditingStore auditingStore, IOptions options, IClock clock, - IJsonSerializer jsonSerializer, + IJsonSerializer jsonSerializer, IAuditingHelper auditingHelper) { _clock = clock; @@ -241,7 +241,8 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory } } - if (propertyEntry.IsModified) + var isModified = !(propertyEntry.OriginalValue?.Equals(propertyEntry.CurrentValue) ?? propertyEntry.CurrentValue == null); + if (isModified) { return true; } diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs index 79c164eb5f..53a2d7714e 100644 --- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs +++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs @@ -104,7 +104,7 @@ namespace Volo.Abp.Localization //Try to get from same language dictionary (without country code) if (cultureName.Contains("-")) //Example: "tr-TR" (length=5) { - var strLang = Resource.Contributors.GetOrNull(GetBaseCultureName(cultureName), name); + var strLang = Resource.Contributors.GetOrNull(CultureHelper.GetBaseCultureName(cultureName), name); if (strLang != null) { return strLang; @@ -168,7 +168,7 @@ namespace Volo.Abp.Localization //Overwrite all strings from the language based on country culture if (cultureName.Contains("-")) { - Resource.Contributors.Fill(GetBaseCultureName(cultureName), allStrings); + Resource.Contributors.Fill(CultureHelper.GetBaseCultureName(cultureName), allStrings); } } @@ -178,12 +178,7 @@ namespace Volo.Abp.Localization return allStrings.Values.ToImmutableList(); } - protected virtual string GetBaseCultureName(string cultureName) - { - return cultureName.Contains("-") - ? cultureName.Left(cultureName.IndexOf("-", StringComparison.Ordinal)) - : cultureName; - } + public class CultureWrapperStringLocalizer : IStringLocalizer, IStringLocalizerSupportsInheritance { diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceContributorList.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceContributorList.cs index daa6abe667..a81adf3562 100644 --- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceContributorList.cs +++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceContributorList.cs @@ -8,7 +8,7 @@ namespace Volo.Abp.Localization { public LocalizedString GetOrNull(string cultureName, string name) { - foreach (var contributor in this.AsQueryable().Reverse()) //TODO: Reverse? + foreach (var contributor in this.AsQueryable().Reverse()) { var localString = contributor.GetOrNull(cultureName, name); if (localString != null) diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/TemplateLocalizer.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/TemplateLocalizer.cs deleted file mode 100644 index 5c4220c45f..0000000000 --- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/TemplateLocalizer.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Text.RegularExpressions; -using Microsoft.Extensions.Localization; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Localization -{ - public class TemplateLocalizer : ITemplateLocalizer, ITransientDependency - { - public string Localize(IStringLocalizer localizer, string text) - { - return new Regex("\\{\\{#L:.+?\\}\\}") - .Replace( - text, - match => localizer[match.Value.Substring(5, match.Length - 7)] - ); - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/FodyWeavers.xml b/framework/src/Volo.Abp.TextTemplating/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/FodyWeavers.xsd b/framework/src/Volo.Abp.TextTemplating/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj b/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj new file mode 100644 index 0000000000..7bcc0400b5 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo.Abp.TextTemplating.csproj @@ -0,0 +1,26 @@ + + + + + + + netstandard2.0 + Volo.Abp.TextTemplating + Volo.Abp.TextTemplating + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs new file mode 100644 index 0000000000..8d76391829 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingModule.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; + +namespace Volo.Abp.TextTemplating +{ + [DependsOn( + typeof(AbpVirtualFileSystemModule), + typeof(AbpLocalizationAbstractionsModule) + )] + public class AbpTextTemplatingModule : AbpModule + { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + AutoAddProvidersAndContributors(context.Services); + } + + private static void AutoAddProvidersAndContributors(IServiceCollection services) + { + var definitionProviders = new List(); + var contentContributors = new List(); + + services.OnRegistred(context => + { + if (typeof(ITemplateDefinitionProvider).IsAssignableFrom(context.ImplementationType)) + { + definitionProviders.Add(context.ImplementationType); + } + + if (typeof(ITemplateContentContributor).IsAssignableFrom(context.ImplementationType)) + { + contentContributors.Add(context.ImplementationType); + } + }); + + services.Configure(options => + { + options.DefinitionProviders.AddIfNotContains(definitionProviders); + options.ContentContributors.AddIfNotContains(contentContributors); + }); + } + } +} diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs new file mode 100644 index 0000000000..b217094974 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/AbpTextTemplatingOptions.cs @@ -0,0 +1,16 @@ +using Volo.Abp.Collections; + +namespace Volo.Abp.TextTemplating +{ + public class AbpTextTemplatingOptions + { + public ITypeList DefinitionProviders { get; } + public ITypeList ContentContributors { get; } + + public AbpTextTemplatingOptions() + { + DefinitionProviders = new TypeList(); + ContentContributors = new TypeList(); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentContributor.cs new file mode 100644 index 0000000000..746b29a2f0 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentContributor.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.TextTemplating +{ + public interface ITemplateContentContributor + { + Task GetOrNullAsync(TemplateContentContributorContext context); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs new file mode 100644 index 0000000000..19248dc161 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateContentProvider.cs @@ -0,0 +1,22 @@ +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Volo.Abp.TextTemplating +{ + public interface ITemplateContentProvider + { + Task GetContentOrNullAsync( + [NotNull] string templateName, + [CanBeNull] string cultureName = null, + bool tryDefaults = true, + bool useCurrentCultureIfCultureNameIsNull = true + ); + + Task GetContentOrNullAsync( + [NotNull] TemplateDefinition templateDefinition, + [CanBeNull] string cultureName = null, + bool tryDefaults = true, + bool useCurrentCultureIfCultureNameIsNull = true + ); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs new file mode 100644 index 0000000000..515cee74ca --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionContext.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Volo.Abp.TextTemplating +{ + public interface ITemplateDefinitionContext + { + IReadOnlyList GetAll(string name); + + TemplateDefinition GetOrNull(string name); + + void Add(params TemplateDefinition[] definitions); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs new file mode 100644 index 0000000000..cbd2d15463 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionManager.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace Volo.Abp.TextTemplating +{ + public interface ITemplateDefinitionManager + { + [NotNull] + TemplateDefinition Get([NotNull] string name); + + [NotNull] + IReadOnlyList GetAll(); + + [CanBeNull] + TemplateDefinition GetOrNull(string name); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs new file mode 100644 index 0000000000..aeb6a3c2da --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateDefinitionProvider.cs @@ -0,0 +1,11 @@ +namespace Volo.Abp.TextTemplating +{ + public interface ITemplateDefinitionProvider + { + void PreDefine(ITemplateDefinitionContext context); + + void Define(ITemplateDefinitionContext context); + + void PostDefine(ITemplateDefinitionContext context); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs new file mode 100644 index 0000000000..b153a5130b --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/ITemplateRenderer.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Volo.Abp.TextTemplating +{ + public interface ITemplateRenderer + { + /// + /// Renders a text template. + /// + /// The template name + /// An optional model object that is used in the template + /// Culture name. Uses the if not specified + /// A dictionary which can be used to import global objects to the template + /// + Task RenderAsync( + [NotNull] string templateName, + [CanBeNull] object model = null, + [CanBeNull] string cultureName = null, + [CanBeNull] Dictionary globalContext = null + ); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs new file mode 100644 index 0000000000..773bf1a0a4 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentContributorContext.cs @@ -0,0 +1,27 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateContentContributorContext + { + [NotNull] + public TemplateDefinition TemplateDefinition { get; } + + [NotNull] + public IServiceProvider ServiceProvider { get; } + + [CanBeNull] + public string Culture { get; } + + public TemplateContentContributorContext( + [NotNull] TemplateDefinition templateDefinition, + [NotNull] IServiceProvider serviceProvider, + [CanBeNull] string culture) + { + TemplateDefinition = Check.NotNull(templateDefinition, nameof(templateDefinition)); + ServiceProvider = Check.NotNull(serviceProvider, nameof(serviceProvider)); + Culture = culture; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs new file mode 100644 index 0000000000..55a3979e21 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateContentProvider.cs @@ -0,0 +1,170 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Localization; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateContentProvider : ITemplateContentProvider, ITransientDependency + { + public IHybridServiceScopeFactory ServiceScopeFactory { get; } + public AbpTextTemplatingOptions Options { get; } + private readonly ITemplateDefinitionManager _templateDefinitionManager; + + public TemplateContentProvider( + ITemplateDefinitionManager templateDefinitionManager, + IHybridServiceScopeFactory serviceScopeFactory, + IOptions options) + { + ServiceScopeFactory = serviceScopeFactory; + Options = options.Value; + _templateDefinitionManager = templateDefinitionManager; + } + + public virtual Task GetContentOrNullAsync( + [NotNull] string templateName, + [CanBeNull] string cultureName = null, + bool tryDefaults = true, + bool useCurrentCultureIfCultureNameIsNull = true) + { + var template = _templateDefinitionManager.Get(templateName); + return GetContentOrNullAsync(template, cultureName); + } + + public virtual async Task GetContentOrNullAsync( + [NotNull] TemplateDefinition templateDefinition, + [CanBeNull] string cultureName = null, + bool tryDefaults = true, + bool useCurrentCultureIfCultureNameIsNull = true) + { + Check.NotNull(templateDefinition, nameof(templateDefinition)); + + if (!Options.ContentContributors.Any()) + { + throw new AbpException( + $"No template content contributor was registered. Use {nameof(AbpTextTemplatingOptions)} to register contributors!" + ); + } + + using (var scope = ServiceScopeFactory.CreateScope()) + { + string templateString = null; + + if (cultureName == null && useCurrentCultureIfCultureNameIsNull) + { + cultureName = CultureInfo.CurrentUICulture.Name; + } + + var contributors = CreateTemplateContentContributors(scope.ServiceProvider); + + //Try to get from the requested culture + templateString = await GetContentOrNullAsync( + contributors, + new TemplateContentContributorContext( + templateDefinition, + scope.ServiceProvider, + cultureName + ) + ); + + if (templateString != null) + { + return templateString; + } + + if (!tryDefaults) + { + return null; + } + + //Try to get from same culture without country code + if (cultureName != null && cultureName.Contains("-")) //Example: "tr-TR" + { + templateString = await GetContentOrNullAsync( + contributors, + new TemplateContentContributorContext( + templateDefinition, + scope.ServiceProvider, + CultureHelper.GetBaseCultureName(cultureName) + ) + ); + + if (templateString != null) + { + return templateString; + } + } + + if (templateDefinition.IsInlineLocalized) + { + //Try to get culture independent content + templateString = await GetContentOrNullAsync( + contributors, + new TemplateContentContributorContext( + templateDefinition, + scope.ServiceProvider, + null + ) + ); + + if (templateString != null) + { + return templateString; + } + } + else + { + //Try to get from default culture + if (templateDefinition.DefaultCultureName != null) + { + templateString = await GetContentOrNullAsync( + contributors, + new TemplateContentContributorContext( + templateDefinition, + scope.ServiceProvider, + templateDefinition.DefaultCultureName + ) + ); + + if (templateString != null) + { + return templateString; + } + } + } + } + + //Not found + return null; + } + + protected virtual ITemplateContentContributor[] CreateTemplateContentContributors(IServiceProvider serviceProvider) + { + return Options.ContentContributors + .Select(type => (ITemplateContentContributor)serviceProvider.GetRequiredService(type)) + .Reverse() + .ToArray(); + } + + protected virtual async Task GetContentOrNullAsync( + ITemplateContentContributor[] contributors, + TemplateContentContributorContext context) + { + foreach (var contributor in contributors) + { + var templateString = await contributor.GetOrNullAsync(context); + if (templateString != null) + { + return templateString; + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs new file mode 100644 index 0000000000..db2ccf6f63 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using Volo.Abp.Localization; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateDefinition + { + public const int MaxNameLength = 128; + + [NotNull] + public string Name { get; } + + [NotNull] + public ILocalizableString DisplayName + { + get => _displayName; + set + { + Check.NotNull(value, nameof(value)); + _displayName = value; + } + } + private ILocalizableString _displayName; + + public bool IsLayout { get; } + + [CanBeNull] + public string Layout { get; set; } + + [CanBeNull] + public Type LocalizationResource { get; set; } + + public bool IsInlineLocalized { get; set; } + + [CanBeNull] + public string DefaultCultureName { get; } + + /// + /// Gets/sets a key-value on the . + /// + /// Name of the property + /// + /// Returns the value in the dictionary by given . + /// Returns null if given is not present in the dictionary. + /// + [CanBeNull] + public object this[string name] + { + get => Properties.GetOrDefault(name); + set => Properties[name] = value; + } + + /// + /// Can be used to get/set custom properties for this feature. + /// + [NotNull] + public Dictionary Properties { get; } + + public TemplateDefinition( + [NotNull] string name, + [CanBeNull] Type localizationResource = null, + [CanBeNull] ILocalizableString displayName = null, + bool isLayout = false, + string layout = null, + string defaultCultureName = null) + { + Name = Check.NotNullOrWhiteSpace(name, nameof(name), MaxNameLength); + LocalizationResource = localizationResource; + DisplayName = displayName ?? new FixedLocalizableString(Name); + IsLayout = isLayout; + Layout = layout; + DefaultCultureName = defaultCultureName; + Properties = new Dictionary(); + } + + /// + /// Sets a property in the dictionary. + /// This is a shortcut for nested calls on this object. + /// + public virtual TemplateDefinition WithProperty(string key, object value) + { + Properties[key] = value; + return this; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs new file mode 100644 index 0000000000..04c3876531 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionContext.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateDefinitionContext : ITemplateDefinitionContext + { + protected Dictionary Templates { get; } + + public TemplateDefinitionContext(Dictionary templates) + { + Templates = templates; + } + + public IReadOnlyList GetAll(string name) + { + return Templates.Values.ToImmutableList(); + } + + public virtual TemplateDefinition GetOrNull(string name) + { + return Templates.GetOrDefault(name); + } + + public virtual IReadOnlyList GetAll() + { + return Templates.Values.ToImmutableList(); + } + + public virtual void Add(params TemplateDefinition[] definitions) + { + if (definitions.IsNullOrEmpty()) + { + return; + } + + foreach (var definition in definitions) + { + Templates[definition.Name] = definition; + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs new file mode 100644 index 0000000000..0d17e9969c --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionExtensions.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using JetBrains.Annotations; +using Volo.Abp.TextTemplating.VirtualFiles; + +namespace Volo.Abp.TextTemplating +{ + public static class TemplateDefinitionExtensions + { + public static TemplateDefinition WithVirtualFilePath( + [NotNull] this TemplateDefinition templateDefinition, + [NotNull] string virtualPath) + { + Check.NotNull(templateDefinition, nameof(templateDefinition)); + + return templateDefinition.WithProperty( + VirtualFileTemplateContentContributor.VirtualPathPropertyName, + virtualPath + ); + } + + public static string GetVirtualFilePathOrNull( + [NotNull] this TemplateDefinition templateDefinition) + { + Check.NotNull(templateDefinition, nameof(templateDefinition)); + + return templateDefinition + .Properties + .GetOrDefault(VirtualFileTemplateContentContributor.VirtualPathPropertyName) as string; + } + } +} diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs new file mode 100644 index 0000000000..11081f6dff --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionManager.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; +using Volo.Abp.VirtualFileSystem; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateDefinitionManager : ITemplateDefinitionManager, ISingletonDependency + { + protected Lazy> TemplateDefinitions { get; } + + protected AbpTextTemplatingOptions Options { get; } + + protected IServiceProvider ServiceProvider { get; } + + public TemplateDefinitionManager( + IOptions options, + IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + Options = options.Value; + + TemplateDefinitions = + new Lazy>(CreateTextTemplateDefinitions, true); + } + + public virtual TemplateDefinition Get(string name) + { + Check.NotNull(name, nameof(name)); + + var template = GetOrNull(name); + + if (template == null) + { + throw new AbpException("Undefined template: " + name); + } + + return template; + } + + public virtual IReadOnlyList GetAll() + { + return TemplateDefinitions.Value.Values.ToImmutableList(); + } + + public virtual TemplateDefinition GetOrNull(string name) + { + return TemplateDefinitions.Value.GetOrDefault(name); + } + + protected virtual IDictionary CreateTextTemplateDefinitions() + { + var templates = new Dictionary(); + + using (var scope = ServiceProvider.CreateScope()) + { + var providers = Options + .DefinitionProviders + .Select(p => scope.ServiceProvider.GetRequiredService(p) as ITemplateDefinitionProvider) + .ToList(); + + var context = new TemplateDefinitionContext(templates); + + foreach (var provider in providers) + { + provider.PreDefine(context); + } + + foreach (var provider in providers) + { + provider.Define(context); + } + + foreach (var provider in providers) + { + provider.PostDefine(context); + } + + SetIsInlineLocalized( + templates, + scope.ServiceProvider + ); + } + + return templates; + } + + protected virtual void SetIsInlineLocalized( + Dictionary templates, + IServiceProvider serviceProvider) + { + var virtualFileProvider = serviceProvider.GetRequiredService(); + + foreach (var templateDefinition in templates.Values) + { + var virtualPath = templateDefinition.GetVirtualFilePathOrNull(); + if (virtualPath == null) + { + continue; + } + + var fileInfo = virtualFileProvider.GetFileInfo(virtualPath); + if (!fileInfo.Exists) + { + throw new AbpException("Could not find a file/folder at the location: " + virtualPath); + } + + templateDefinition.IsInlineLocalized = !fileInfo.IsDirectory; + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs new file mode 100644 index 0000000000..924a6dfdd8 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinitionProvider.cs @@ -0,0 +1,19 @@ +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.TextTemplating +{ + public abstract class TemplateDefinitionProvider : ITemplateDefinitionProvider, ITransientDependency + { + public virtual void PreDefine(ITemplateDefinitionContext context) + { + + } + + public abstract void Define(ITemplateDefinitionContext context); + + public virtual void PostDefine(ITemplateDefinitionContext context) + { + + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs new file mode 100644 index 0000000000..f51e8447b0 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.Extensions.Localization; +using Scriban; +using Scriban.Runtime; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Localization; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateRenderer : ITemplateRenderer, ITransientDependency + { + private readonly ITemplateContentProvider _templateContentProvider; + private readonly ITemplateDefinitionManager _templateDefinitionManager; + private readonly IStringLocalizerFactory _stringLocalizerFactory; + + public TemplateRenderer( + ITemplateContentProvider templateContentProvider, + ITemplateDefinitionManager templateDefinitionManager, + IStringLocalizerFactory stringLocalizerFactory) + { + _templateContentProvider = templateContentProvider; + _templateDefinitionManager = templateDefinitionManager; + _stringLocalizerFactory = stringLocalizerFactory; + } + + public virtual async Task RenderAsync( + [NotNull] string templateName, + [CanBeNull] object model = null, + [CanBeNull] string cultureName = null, + [CanBeNull] Dictionary globalContext = null) + { + Check.NotNullOrWhiteSpace(templateName, nameof(templateName)); + + if (globalContext == null) + { + globalContext = new Dictionary(); + } + + if (cultureName == null) + { + return await RenderInternalAsync( + templateName, + globalContext, + model + ); + } + else + { + using (CultureHelper.Use(cultureName)) + { + return await RenderInternalAsync( + templateName, + globalContext, + model + ); + } + } + } + + protected virtual async Task RenderInternalAsync( + string templateName, + Dictionary globalContext, + object model = null) + { + var templateDefinition = _templateDefinitionManager.Get(templateName); + + var renderedContent = await RenderSingleTemplateAsync( + templateDefinition, + globalContext, + model + ); + + if (templateDefinition.Layout != null) + { + globalContext["content"] = renderedContent; + renderedContent = await RenderInternalAsync( + templateDefinition.Layout, + globalContext + ); + } + + return renderedContent; + } + + protected virtual async Task RenderSingleTemplateAsync( + TemplateDefinition templateDefinition, + Dictionary globalContext, + object model = null) + { + var rawTemplateContent = await _templateContentProvider + .GetContentOrNullAsync( + templateDefinition + ); + + return await RenderTemplateContentWithScribanAsync( + templateDefinition, + rawTemplateContent, + globalContext, + model + ); + } + + protected virtual async Task RenderTemplateContentWithScribanAsync( + TemplateDefinition templateDefinition, + string templateContent, + Dictionary globalContext, + object model = null) + { + var context = CreateScribanTemplateContext( + templateDefinition, + globalContext, + model + ); + + return await Template + .Parse(templateContent) + .RenderAsync(context); + } + + protected virtual TemplateContext CreateScribanTemplateContext( + TemplateDefinition templateDefinition, + Dictionary globalContext, + object model = null) + { + var context = new TemplateContext(); + + var scriptObject = new ScriptObject(); + + scriptObject.Import(globalContext); + + if (model != null) + { + scriptObject["model"] = model; + } + + if (templateDefinition.LocalizationResource != null) + { + var localizer = _stringLocalizerFactory.Create(templateDefinition.LocalizationResource); + scriptObject.Import( + "L", + new Func( + name => localizer[name] + ) + ); + } + + context.PushGlobal(scriptObject); + + return context; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs new file mode 100644 index 0000000000..cde913f307 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/FileInfoLocalizedTemplateContentReader.cs @@ -0,0 +1,25 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.FileProviders; + +namespace Volo.Abp.TextTemplating.VirtualFiles +{ + public class FileInfoLocalizedTemplateContentReader : ILocalizedTemplateContentReader + { + private string _content; + + public async Task ReadContentsAsync(IFileInfo fileInfo) + { + _content = await fileInfo.ReadAsStringAsync(); + } + + public string GetContentOrNull(string culture) + { + if (culture == null) + { + return _content; + } + + return null; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs new file mode 100644 index 0000000000..7d837d731d --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReader.cs @@ -0,0 +1,9 @@ +using JetBrains.Annotations; + +namespace Volo.Abp.TextTemplating.VirtualFiles +{ + public interface ILocalizedTemplateContentReader + { + public string GetContentOrNull([CanBeNull] string culture); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReaderFactory.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReaderFactory.cs new file mode 100644 index 0000000000..2d4d9ef2d5 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/ILocalizedTemplateContentReaderFactory.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.TextTemplating.VirtualFiles +{ + public interface ILocalizedTemplateContentReaderFactory + { + Task CreateAsync(TemplateDefinition templateDefinition); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs new file mode 100644 index 0000000000..b30e5771f4 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/LocalizedTemplateContentReaderFactory.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.VirtualFileSystem; + +namespace Volo.Abp.TextTemplating.VirtualFiles +{ + public class LocalizedTemplateContentReaderFactory : ILocalizedTemplateContentReaderFactory, ISingletonDependency + { + private readonly IVirtualFileProvider _virtualFileProvider; + private readonly Dictionary _readerCache; + private readonly ReaderWriterLockSlim _lock; + + public LocalizedTemplateContentReaderFactory(IVirtualFileProvider virtualFileProvider) + { + _virtualFileProvider = virtualFileProvider; + _readerCache = new Dictionary(); + _lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); + } + + public async Task CreateAsync(TemplateDefinition templateDefinition) + { + _lock.EnterUpgradeableReadLock(); + + try + { + var reader = _readerCache.GetOrDefault(templateDefinition.Name); + if (reader != null) + { + return reader; + } + + _lock.EnterWriteLock(); + + try + { + reader = await CreateInternalAsync(templateDefinition); + _readerCache[templateDefinition.Name] = reader; + return reader; + } + finally + { + _lock.ExitWriteLock(); + } + } + finally + { + _lock.ExitUpgradeableReadLock(); + } + } + + protected virtual async Task CreateInternalAsync( + TemplateDefinition templateDefinition) + { + var virtualPath = templateDefinition.GetVirtualFilePathOrNull(); + if (virtualPath == null) + { + return NullLocalizedTemplateContentReader.Instance; + } + + var fileInfo = _virtualFileProvider.GetFileInfo(virtualPath); + if (!fileInfo.Exists) + { + throw new AbpException("Could not find a file/folder at the location: " + virtualPath); + } + + if (fileInfo.IsDirectory) + { + var folderReader = new VirtualFolderLocalizedTemplateContentReader(); + await folderReader.ReadContentsAsync(_virtualFileProvider, virtualPath); + return folderReader; + } + else //File + { + var singleFileReader = new FileInfoLocalizedTemplateContentReader(); + await singleFileReader.ReadContentsAsync(fileInfo); + return singleFileReader; + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs new file mode 100644 index 0000000000..475559c634 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/NullLocalizedTemplateContentReader.cs @@ -0,0 +1,17 @@ +namespace Volo.Abp.TextTemplating.VirtualFiles +{ + public class NullLocalizedTemplateContentReader : ILocalizedTemplateContentReader + { + public static NullLocalizedTemplateContentReader Instance { get; } = new NullLocalizedTemplateContentReader(); + + private NullLocalizedTemplateContentReader() + { + + } + + public string GetContentOrNull(string culture) + { + return null; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs new file mode 100644 index 0000000000..ede33a3652 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContentContributor.cs @@ -0,0 +1,28 @@ +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.TextTemplating.VirtualFiles +{ + public class VirtualFileTemplateContentContributor : ITemplateContentContributor, ITransientDependency + { + public const string VirtualPathPropertyName = "VirtualPath"; + + private readonly ILocalizedTemplateContentReaderFactory _localizedTemplateContentReaderFactory; + + public VirtualFileTemplateContentContributor( + ILocalizedTemplateContentReaderFactory localizedTemplateContentReaderFactory) + { + _localizedTemplateContentReaderFactory = localizedTemplateContentReaderFactory; + } + + public virtual async Task GetOrNullAsync(TemplateContentContributorContext context) + { + var localizedReader = await _localizedTemplateContentReaderFactory + .CreateAsync(context.TemplateDefinition); + + return localizedReader.GetContentOrNull( + context.Culture + ); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs new file mode 100644 index 0000000000..638a7db190 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/VirtualFiles/VirtualFolderLocalizedTemplateContentReader.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.FileProviders; +using Volo.Abp.VirtualFileSystem; + +namespace Volo.Abp.TextTemplating.VirtualFiles +{ + public class VirtualFolderLocalizedTemplateContentReader : ILocalizedTemplateContentReader + { + private Dictionary _dictionary; + + public async Task ReadContentsAsync( + IVirtualFileProvider virtualFileProvider, + string virtualPath) + { + _dictionary = new Dictionary(); + + var directoryInfo = virtualFileProvider.GetFileInfo(virtualPath); + if (!directoryInfo.IsDirectory) + { + throw new AbpException("Given virtual path is not a folder: " + virtualPath); + } + + foreach (var file in virtualFileProvider.GetDirectoryContents(virtualPath)) + { + if (file.IsDirectory) + { + continue; + } + + _dictionary.Add(file.Name.RemovePostFix(".tpl"), await file.ReadAsStringAsync()); + } + } + + public string GetContentOrNull(string cultureName) + { + if (cultureName == null) + { + return null; + } + + return _dictionary.GetOrDefault(cultureName); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/pl.json b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/pl-PL.json similarity index 72% rename from framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/pl.json rename to framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/pl-PL.json index 557d01eb31..d8edcb1438 100644 --- a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/pl.json +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Menu:Administration": "Administracja" } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json similarity index 98% rename from framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl.json rename to framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json index cdaf556168..1be12a51cf 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "InternalServerErrorMessage": "Błąd wewnętrzny serwera podczas przetwarzania żądania!", "ValidationErrorMessage": "Twoje żądanie jest niepoprawnie!", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pl.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pl-PL.json similarity index 98% rename from framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pl.json rename to framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pl-PL.json index eef572eb13..93145f0a3a 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pl.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "'{0}' and '{1}' do not match.": "'{0}' i '{1}' nie są takie same.", "The {0} field is not a valid credit card number.": "Pole {0} nie jest poprawnym numerem karty kredytowej.", diff --git a/framework/src/Volo.Abp.VirtualFileSystem/Microsoft/Extensions/FileProviders/AbpFileInfoExtensions.cs b/framework/src/Volo.Abp.VirtualFileSystem/Microsoft/Extensions/FileProviders/AbpFileInfoExtensions.cs index 7d3505d616..8a6d7d57a1 100644 --- a/framework/src/Volo.Abp.VirtualFileSystem/Microsoft/Extensions/FileProviders/AbpFileInfoExtensions.cs +++ b/framework/src/Volo.Abp.VirtualFileSystem/Microsoft/Extensions/FileProviders/AbpFileInfoExtensions.cs @@ -18,6 +18,14 @@ namespace Microsoft.Extensions.FileProviders return fileInfo.ReadAsString(Encoding.UTF8); } + /// + /// Reads file content as string using encoding. + /// + public static Task ReadAsStringAsync([NotNull] this IFileInfo fileInfo) + { + return fileInfo.ReadAsStringAsync(Encoding.UTF8); + } + /// /// Reads file content as string using the given . /// @@ -34,6 +42,22 @@ namespace Microsoft.Extensions.FileProviders } } + /// + /// Reads file content as string using the given . + /// + public static async Task ReadAsStringAsync([NotNull] this IFileInfo fileInfo, Encoding encoding) + { + Check.NotNull(fileInfo, nameof(fileInfo)); + + using (var stream = fileInfo.CreateReadStream()) + { + using (var streamReader = new StreamReader(stream, encoding, true)) + { + return await streamReader.ReadToEndAsync(); + } + } + } + /// /// Reads file content as byte[]. /// diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/pl.json b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/pl-PL.json similarity index 79% rename from framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/pl.json rename to framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/pl-PL.json index c794c9c123..5370ccdf57 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/pl.json +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "BirthDate": "Data urodzenia", "Value1": "Wartość jeden" diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithDisableAuditingAndPropertyHasAudited.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithDisableAuditingAndPropertyHasAudited.cs index 28a98bac8e..f9fcc878b1 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithDisableAuditingAndPropertyHasAudited.cs +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithDisableAuditingAndPropertyHasAudited.cs @@ -11,16 +11,20 @@ namespace Volo.Abp.Auditing.App.Entities } - public AppEntityWithDisableAuditingAndPropertyHasAudited(Guid id, string name, string name2) + public AppEntityWithDisableAuditingAndPropertyHasAudited(Guid id, string name, string name2, string name3) : base(id) { Name = name; Name2 = name2; + Name3 = name3; } [Audited] public string Name { get; set; } public string Name2 { get; set; } + + [Audited] + public string Name3 { get; set; } } } diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs index 502ba6dcc8..2887eb8510 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs @@ -7,6 +7,7 @@ using NSubstitute; using Volo.Abp.Auditing.App.Entities; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Repositories; +using Volo.Abp.Uow; using Xunit; namespace Volo.Abp.Auditing @@ -15,10 +16,12 @@ namespace Volo.Abp.Auditing { private IAuditingStore _auditingStore; private IAuditingManager _auditingManager; + private IUnitOfWorkManager _unitOfWorkManager; public Auditing_Tests() { _auditingManager = GetRequiredService(); + _unitOfWorkManager = GetRequiredService(); } protected override void AfterAddApplication(IServiceCollection services) @@ -158,18 +161,46 @@ namespace Volo.Abp.Auditing using (var scope = _auditingManager.BeginScope()) { var repository = ServiceProvider.GetRequiredService>(); - await repository.InsertAsync(new AppEntityWithDisableAuditingAndPropertyHasAudited(Guid.NewGuid(), "test name", "test name2")); + await repository.InsertAsync(new AppEntityWithDisableAuditingAndPropertyHasAudited(Guid.NewGuid(), "test name", "test name2", "test name3")); await scope.SaveAsync(); } #pragma warning disable 4014 _auditingStore.Received().SaveAsync(Arg.Is(x => - x.EntityChanges.Count == 1 && x.EntityChanges[0].PropertyChanges.Count == 1 && - x.EntityChanges[0].PropertyChanges[0].PropertyName == - nameof(AppEntityWithDisableAuditingAndPropertyHasAudited.Name))); + x.EntityChanges.Count == 1 && x.EntityChanges[0].PropertyChanges.Count == 2 && + x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithDisableAuditingAndPropertyHasAudited.Name) && + x.EntityChanges[0].PropertyChanges[1].PropertyName == nameof(AppEntityWithDisableAuditingAndPropertyHasAudited.Name3))); #pragma warning restore 4014 } + [Fact] + public virtual async Task Should_Write_AuditLog_For_Entity_That_Property_Has_Audited_Attribute_And_Has_Changed_Even_Entity_Has_DisableAuditing_Attribute() + { + var entityId = Guid.NewGuid(); + var repository = ServiceProvider.GetRequiredService>(); + await repository.InsertAsync(new AppEntityWithDisableAuditingAndPropertyHasAudited(entityId, "test name", "test name2", "test name3")); + + using (var scope = _auditingManager.BeginScope()) + { + using (var uow = _unitOfWorkManager.Begin()) + { + var entity = await repository.GetAsync(entityId); + entity.Name = "new name1"; + + await repository.UpdateAsync(entity); + + await uow.CompleteAsync(); + } + + await scope.SaveAsync(); + } + +#pragma warning disable 4014 + _auditingStore.Received().SaveAsync(Arg.Is(x => + x.EntityChanges.Count == 1 && x.EntityChanges[0].PropertyChanges.Count == 1 && + x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithDisableAuditingAndPropertyHasAudited.Name))); +#pragma warning restore 4014 + } [Fact] public virtual async Task Should_Write_AuditLog_If_There_No_Action_And_No_EntityChanges() diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo.Abp.Emailing.Tests.csproj b/framework/test/Volo.Abp.Emailing.Tests/Volo.Abp.Emailing.Tests.csproj index a86ea9f051..030eddb65a 100644 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo.Abp.Emailing.Tests.csproj +++ b/framework/test/Volo.Abp.Emailing.Tests/Volo.Abp.Emailing.Tests.csproj @@ -7,13 +7,6 @@ - - - - - - - diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/AbpEmailingTestModule.cs b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/AbpEmailingTestModule.cs index e4cdfe0556..7f1175bcbb 100644 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/AbpEmailingTestModule.cs +++ b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/AbpEmailingTestModule.cs @@ -1,6 +1,4 @@ using Volo.Abp.Autofac; -using Volo.Abp.Emailing.Localization; -using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.VirtualFileSystem; @@ -18,14 +16,6 @@ namespace Volo.Abp.Emailing { options.FileSets.AddEmbedded(); }); - - Configure(options => - { - options.Resources - .Add() - .AddVirtualJson("/Volo/Abp/Emailing/Localization"); - }); - } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateRender_Tests.cs b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateRender_Tests.cs deleted file mode 100644 index 24d7298f6c..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateRender_Tests.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Shouldly; -using Volo.Abp.Emailing.Templates; -using Volo.Abp.Testing; -using Xunit; - -namespace Volo.Abp.Emailing -{ - public class EmailTemplateRender_Tests : AbpIntegratedTest - { - protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) - { - options.UseAutofac(); - } - - private readonly ITemplateRender _templateRender; - - public EmailTemplateRender_Tests() - { - _templateRender = GetRequiredService(); - } - - [Fact] - public async Task RenderAsync() - { - var template = "Hello {{email}} {{ for order in orders }}{{ order.id }}:{{ order.name }},{{ end }}"; - - var model = new ModelClass - { - Email = "john@abp.io", - Orders = new List - { - new ModelClass.Order - { - Id = "1", - Name = "iphone" - }, - new ModelClass.Order - { - Id = "2", - Name = "ipad" - } - } - }; - - var result = await _templateRender.RenderAsync(template, model); - result.ShouldBe("Hello john@abp.io 1:iphone,2:ipad,"); - } - - public class ModelClass - { - public string Email { get; set; } - - public List Orders { get; set; } - - public class Order - { - public string Id { get; set; } - - public string Name { get; set; } - } - } - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateStore_Tests.cs b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateStore_Tests.cs deleted file mode 100644 index 1e4ea8c090..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateStore_Tests.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Threading.Tasks; -using Shouldly; -using Volo.Abp.Emailing.Templates; -using Volo.Abp.Testing; -using Xunit; - -namespace Volo.Abp.Emailing -{ - public class EmailTemplateStore_Tests : AbpIntegratedTest - { - protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) - { - options.UseAutofac(); - } - - private readonly IEmailTemplateProvider _emailTemplateProvider; - - public EmailTemplateStore_Tests() - { - _emailTemplateProvider = GetRequiredService(); - } - - [Fact] - public async Task Should_Get_Registered_Template() - { - var template = await _emailTemplateProvider.GetAsync("template1", "tr"); - template.Content.ShouldContain("Lütfen aşağıdaki bağlantıya tıklayarak e-posta adresinizi onaylayın."); - } - - [Fact] - public async Task Should_Get_Default_Culture_Template() - { - var template = await _emailTemplateProvider.GetAsync("template1", "zh-Hans"); - template.Content.ShouldContain("Please confirm your email address by clicking the link below."); - } - - [Fact] - public async Task Should_Get_Registered_Template_With_Layout() - { - var template = await _emailTemplateProvider.GetAsync("template2", "en"); - - template.Content.ShouldContain($"{Environment.NewLine} " + "Please confirm your email address by clicking the link below."); - } - - - [Fact] - public async Task Should_Get_Registered_Template_With_Localize() - { - var template = await _emailTemplateProvider.GetAsync("template3", "tr"); - template.Content.ShouldContain("Merhaba Abp"); - } - } -} diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/AbpEmailingTestResource.cs b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/AbpEmailingTestResource.cs deleted file mode 100644 index bdc75f9f2f..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/AbpEmailingTestResource.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Volo.Abp.Emailing.Localization -{ - public class AbpEmailingTestResource - { - } -} diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/cs.json b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/cs.json deleted file mode 100644 index 5f6e35488e..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/cs.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "culture": "cs", - "texts": { - "hello": "ahoj" - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl.json b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl-PL.json similarity index 63% rename from framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl.json rename to framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl-PL.json index 6dd6654aa1..3b4ee39a1e 100644 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl.json +++ b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "hello": "witaj" } diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pt-BR.json b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pt-BR.json deleted file mode 100644 index 0e33dee138..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/pt-BR.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "culture": "pt-BR", - "texts": { - "hello": "Ol" - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/vi.json b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/vi.json deleted file mode 100644 index 8261599d78..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/vi.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "culture": "vi", - "texts": { - "hello": "xin chào" - } -} diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/zh-Hant.json b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/zh-Hant.json deleted file mode 100644 index dd12964f70..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/zh-Hant.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "culture": "zh-Hant", - "texts": { - "hello": "哈囉" - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestEmailTemplateProvider.cs b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestEmailTemplateProvider.cs deleted file mode 100644 index 60c9a2ccf3..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestEmailTemplateProvider.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Volo.Abp.Emailing.Localization; -using Volo.Abp.Emailing.Templates; -using Volo.Abp.Emailing.Templates.VirtualFiles; - -namespace Volo.Abp.Emailing -{ - public class TestEmailTemplateProvider : EmailTemplateDefinitionProvider - { - public override void Define(IEmailTemplateDefinitionContext context) - { - var template1 = new EmailTemplateDefinition("template1", defaultCultureName: "en", layout: null) - .AddTemplateVirtualFiles("/Volo/Abp/Emailing/TestTemplates/Template1"); - context.Add(template1); - - var template2 = new EmailTemplateDefinition("template2", layout: StandardEmailTemplates.DefaultLayout) - .AddTemplateVirtualFiles("/Volo/Abp/Emailing/TestTemplates/Template2"); - context.Add(template2); - - var template3 = new EmailTemplateDefinition("template3", layout: null, singleTemplateFile: true, localizationResource: typeof(AbpEmailingTestResource)) - .AddTemplateVirtualFile("/Volo/Abp/Emailing/TestTemplates/Template3/Template.tpl"); - context.Add(template3); - } - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/en.tpl b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/en.tpl deleted file mode 100644 index 49a951e8c0..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/en.tpl +++ /dev/null @@ -1,4 +0,0 @@ -Please confirm your email address by clicking the link below. -We may need to send you critical information about our service and it is important that we have an accurate email address. - -Confirm email address \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/tr.tpl b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/tr.tpl deleted file mode 100644 index 3e572aedbb..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/tr.tpl +++ /dev/null @@ -1,4 +0,0 @@ -Lütfen aşağıdaki bağlantıya tıklayarak e-posta adresinizi onaylayın. -Size hizmetimizle ilgili kritik bilgileri göndermemiz gerekebilir ve doğru bir e-posta adresimizin olması önemlidir. - -E-posta adresini onayla \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template2/en.tpl b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template2/en.tpl deleted file mode 100644 index 49a951e8c0..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template2/en.tpl +++ /dev/null @@ -1,4 +0,0 @@ -Please confirm your email address by clicking the link below. -We may need to send you critical information about our service and it is important that we have an accurate email address. - -Confirm email address \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template3/Template.tpl b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template3/Template.tpl deleted file mode 100644 index f30f512848..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template3/Template.tpl +++ /dev/null @@ -1 +0,0 @@ -{{#L:hello}} Abp \ No newline at end of file diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TemplateLocalizer_Tests.cs b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TemplateLocalizer_Tests.cs deleted file mode 100644 index 4b31554637..0000000000 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TemplateLocalizer_Tests.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Localization; -using Shouldly; -using Volo.Abp.Localization.TestResources.Source; -using Volo.Abp.Modularity; -using Volo.Abp.Testing; -using Volo.Abp.VirtualFileSystem; -using Xunit; - -namespace Volo.Abp.Localization -{ - public class TemplateLocalizer_Tests : AbpIntegratedTest - { - private readonly ITemplateLocalizer _templateLocalizer; - private readonly IStringLocalizer _testResource; - - public TemplateLocalizer_Tests() - { - _testResource = GetRequiredService>(); - _templateLocalizer = GetRequiredService(); - } - - [Fact] - public void Should_Localize() - { - using (CultureHelper.Use("en")) - { - _templateLocalizer.Localize(_testResource, "

{{#L:CarPlural}} {{#L:Universe}}

") - .ShouldBe("

Cars Universe

"); - } - } - - [Fact] - public void Should_Work_Even_If_No_Text_To_Localize() - { - using (CultureHelper.Use("en")) - { - _templateLocalizer.Localize(_testResource, "

test

") - .ShouldBe("

test

"); - } - } - - [DependsOn(typeof(AbpTestBaseModule))] - [DependsOn(typeof(AbpLocalizationModule))] - public class TestModule : AbpModule - { - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - options.FileSets.AddEmbedded(); - }); - - Configure(options => - { - options.Resources - .Add("en") - .AddVirtualJson("/Volo/Abp/Localization/TestResources/Source"); - }); - } - } - } -} diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/pl.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/pl-PL.json similarity index 79% rename from framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/pl.json rename to framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/pl-PL.json index 5e343cf2f9..a25b79d6d8 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/pl.json +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "USA": "Stany Zjednoczone Ameryki", "Brazil": "Brazylia" diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/pl.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/pl-PL.json similarity index 86% rename from framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/pl.json rename to framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/pl-PL.json index 1d0bfdf66a..cce4cfd722 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/pl.json +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "ThisFieldIsRequired": "To pole jest wymagane", "MaxLenghtErrorMessage": "To pole może mieć maksymalnie '{0}' znaków" diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/pl.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/pl-PL.json similarity index 92% rename from framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/pl.json rename to framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/pl-PL.json index d014bc7b30..e208fb12b3 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/pl.json +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Hello {0}.": "Witaj {0}.", "Car": "Samochód", diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/pl.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/pl-PL.json similarity index 69% rename from framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/pl.json rename to framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/pl-PL.json index 2935790010..5b754a7ae1 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/pl.json +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "SeeYou": "Do zobaczenia" } diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj b/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj new file mode 100644 index 0000000000..0d230552e8 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj @@ -0,0 +1,25 @@ + + + + + + netcoreapp3.1 + + + + + + + + + + + + + + + + + + + diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingOptions_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingOptions_Tests.cs new file mode 100644 index 0000000000..294596c693 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingOptions_Tests.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Options; +using Shouldly; +using Xunit; + +namespace Volo.Abp.TextTemplating +{ + public class AbpTextTemplatingOptions_Tests : AbpTextTemplatingTestBase + { + private readonly AbpTextTemplatingOptions _options; + + public AbpTextTemplatingOptions_Tests() + { + _options = GetRequiredService>().Value; + } + + [Fact] + public void Should_Auto_Add_TemplateDefinitionProviders_To_Options() + { + _options + .DefinitionProviders + .ShouldContain(typeof(TestTemplateDefinitionProvider)); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestBase.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestBase.cs new file mode 100644 index 0000000000..ca5dc20445 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestBase.cs @@ -0,0 +1,12 @@ +using Volo.Abp.Testing; + +namespace Volo.Abp.TextTemplating +{ + public abstract class AbpTextTemplatingTestBase : AbpIntegratedTest + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs new file mode 100644 index 0000000000..8e8b69e116 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs @@ -0,0 +1,32 @@ +using Volo.Abp.Autofac; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.TextTemplating.Localization; +using Volo.Abp.VirtualFileSystem; + +namespace Volo.Abp.TextTemplating +{ + [DependsOn( + typeof(AbpTextTemplatingModule), + typeof(AbpTestBaseModule), + typeof(AbpAutofacModule), + typeof(AbpLocalizationModule) + )] + public class AbpTextTemplatingTestModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded("Volo.Abp.TextTemplating"); + }); + + Configure(options => + { + options.Resources + .Add("en") + .AddVirtualJson("/Localization"); + }); + } + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/TestLocalizationSource.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/TestLocalizationSource.cs new file mode 100644 index 0000000000..a4e53e3861 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/TestLocalizationSource.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.TextTemplating.Localization +{ + public class TestLocalizationSource + { + } +} diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/en.json b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/en.json similarity index 52% rename from framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/en.json rename to framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/en.json index 38f7cf7e5b..a0dff7e930 100644 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/en.json +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/en.json @@ -1,6 +1,6 @@ { "culture": "en", "texts": { - "hello": "hello" - } + "HelloText": "Hello" + } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/tr.json b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/tr.json similarity index 50% rename from framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/tr.json rename to framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/tr.json index 6c3c94cfdf..d09f02cd8f 100644 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/tr.json +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/tr.json @@ -1,6 +1,6 @@ { "culture": "tr", "texts": { - "hello": "Merhaba" - } + "HelloText": "Merhaba" + } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl new file mode 100644 index 0000000000..8bcab86d0f --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl @@ -0,0 +1 @@ +{{L "HelloText"}}. Please click to the following link to get an email to reset your password! \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/TestTemplateLayout1.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/TestTemplateLayout1.tpl new file mode 100644 index 0000000000..a780e210b0 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/TestTemplateLayout1.tpl @@ -0,0 +1 @@ +*BEGIN*{{content}}*END* \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl new file mode 100644 index 0000000000..1746eed52b --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl @@ -0,0 +1 @@ +Welcome {{model.name}} to the abp.io! \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl new file mode 100644 index 0000000000..581016bc4d --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl @@ -0,0 +1 @@ +Merhaba {{model.name}}, abp.io'ya hoşgeldiniz! \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs new file mode 100644 index 0000000000..51c78cc13e --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateDefinitionTests.cs @@ -0,0 +1,41 @@ +using Shouldly; +using Xunit; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateDefinitionTests : AbpTextTemplatingTestBase + { + private readonly ITemplateDefinitionManager _templateDefinitionManager; + + public TemplateDefinitionTests() + { + _templateDefinitionManager = GetRequiredService(); + } + + [Fact] + public void Should_Retrieve_Template_Definition_By_Name() + { + var welcomeEmailTemplate = _templateDefinitionManager.Get(TestTemplates.WelcomeEmail); + welcomeEmailTemplate.Name.ShouldBe(TestTemplates.WelcomeEmail); + welcomeEmailTemplate.IsInlineLocalized.ShouldBeFalse(); + + var forgotPasswordEmailTemplate = _templateDefinitionManager.Get(TestTemplates.ForgotPasswordEmail); + forgotPasswordEmailTemplate.Name.ShouldBe(TestTemplates.ForgotPasswordEmail); + forgotPasswordEmailTemplate.IsInlineLocalized.ShouldBeTrue(); + } + + [Fact] + public void Should_Get_Null_If_Template_Not_Found() + { + var definition = _templateDefinitionManager.GetOrNull("undefined-template"); + definition.ShouldBeNull(); + } + + [Fact] + public void Should_Retrieve_All_Template_Definitions() + { + var definitions = _templateDefinitionManager.GetAll(); + definitions.Count.ShouldBeGreaterThan(1); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs new file mode 100644 index 0000000000..14b3df3486 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs @@ -0,0 +1,108 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace Volo.Abp.TextTemplating +{ + public class TemplateRenderer_Tests : AbpTextTemplatingTestBase + { + private readonly ITemplateRenderer _templateRenderer; + + public TemplateRenderer_Tests() + { + _templateRenderer = GetRequiredService(); + } + + [Fact] + public async Task Should_Get_Rendered_Localized_Template_Content_With_Different_Cultures() + { + (await _templateRenderer.RenderAsync( + TestTemplates.WelcomeEmail, + model: new + { + name = "John" + }, + cultureName: "en" + )).ShouldBe("Welcome John to the abp.io!"); + + (await _templateRenderer.RenderAsync( + TestTemplates.WelcomeEmail, + model: new + { + name = "John" + }, + cultureName: "tr" + )).ShouldBe("Merhaba John, abp.io'ya hoşgeldiniz!"); + + //"en-US" fallbacks to "en" since "en-US" doesn't exists and "en" is the fallback culture + (await _templateRenderer.RenderAsync( + TestTemplates.WelcomeEmail, + model: new + { + name = "John" + }, + cultureName: "en-US" + )).ShouldBe("Welcome John to the abp.io!"); + + //"fr" fallbacks to "en" since "fr" doesn't exists and "en" is the default culture + (await _templateRenderer.RenderAsync( + TestTemplates.WelcomeEmail, + model: new + { + Name = "John" //Intentionally written as PascalCase since Scriban supports it + }, + cultureName: "fr" + )).ShouldBe("Welcome John to the abp.io!"); + } + + [Fact] + public async Task Should_Get_Rendered_Localized_Template_Content_With_Stronly_Typed_Model() + { + (await _templateRenderer.RenderAsync( + TestTemplates.WelcomeEmail, + model: new WelcomeEmailModel("John"), + cultureName: "en" + )).ShouldBe("Welcome John to the abp.io!"); + } + + [Fact] + public async Task Should_Get_Rendered_Localized_Template_Content_With_Dictionary_Model() + { + (await _templateRenderer.RenderAsync( + TestTemplates.WelcomeEmail, + model: new Dictionary() { { "name", "John" } }, + cultureName: "en" + )).ShouldBe("Welcome John to the abp.io!"); + } + + [Fact] + public async Task Should_Get_Rendered_Inline_Localized_Template() + { + (await _templateRenderer.RenderAsync( + TestTemplates.ForgotPasswordEmail, + cultureName: "en" + )).ShouldBe("*BEGIN*Hello. Please click to the following link to get an email to reset your password!*END*"); + + (await _templateRenderer.RenderAsync( + TestTemplates.ForgotPasswordEmail, + cultureName: "tr" + )).ShouldBe("*BEGIN*Merhaba. Please click to the following link to get an email to reset your password!*END*"); + } + + private class WelcomeEmailModel + { + public string Name { get; set; } + + public WelcomeEmailModel() + { + + } + + public WelcomeEmailModel(string name) + { + Name = name; + } + } + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs new file mode 100644 index 0000000000..05df1378b2 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs @@ -0,0 +1,32 @@ +using Volo.Abp.TextTemplating.Localization; + +namespace Volo.Abp.TextTemplating +{ + public class TestTemplateDefinitionProvider : TemplateDefinitionProvider + { + public override void Define(ITemplateDefinitionContext context) + { + context.Add( + new TemplateDefinition( + TestTemplates.WelcomeEmail, + defaultCultureName: "en" + ).WithVirtualFilePath("/SampleTemplates/WelcomeEmail") + ); + + context.Add( + new TemplateDefinition( + TestTemplates.ForgotPasswordEmail, + localizationResource: typeof(TestLocalizationSource), + layout: TestTemplates.TestTemplateLayout1 + ).WithVirtualFilePath("/SampleTemplates/ForgotPasswordEmail.tpl") + ); + + context.Add( + new TemplateDefinition( + TestTemplates.TestTemplateLayout1, + isLayout: true + ).WithVirtualFilePath("/SampleTemplates/TestTemplateLayout1.tpl") + ); + } + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs new file mode 100644 index 0000000000..a2b605c213 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplates.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.TextTemplating +{ + public static class TestTemplates + { + public const string WelcomeEmail = "WelcomeEmail"; + public const string ForgotPasswordEmail = "ForgotPasswordEmail"; + public const string TestTemplateLayout1 = "TestTemplateLayout1"; + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs new file mode 100644 index 0000000000..da2d4179de --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs @@ -0,0 +1,49 @@ +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace Volo.Abp.TextTemplating.VirtualFiles +{ + //TODO: Make tests running again! + //public class VirtualFileTemplateContributor_Tests : AbpTextTemplatingTestBase + //{ + // [Fact] + // public async Task Should_Get_Localized_Content_By_Culture() + // { + // var contributor = new VirtualFileTemplateContentContributor( + // "/SampleTemplates/WelcomeEmail" + // ); + + // contributor.Initialize( + // new TemplateContentContributorInitializationContext( + // new TemplateDefinition("Test"), + // ServiceProvider + // ) + // ); + + // (await contributor + // .GetOrNullAsync("en")).ShouldBe("Welcome {{model.name}} to the abp.io!"); + + // (await contributor + // .GetOrNullAsync("tr")).ShouldBe("Merhaba {{model.name}}, abp.io'ya hoşgeldiniz!"); + // } + + // [Fact] + // public async Task Should_Get_Non_Localized_Template_Content() + // { + // var contributor = new VirtualFileTemplateContentContributor( + // "/SampleTemplates/ForgotPasswordEmail.tpl" + // ); + + // contributor.Initialize( + // new TemplateContentContributorInitializationContext( + // new TemplateDefinition("Test"), + // ServiceProvider + // ) + // ); + + // (await contributor + // .GetOrNullAsync()).ShouldBe("{{l \"HelloText\"}}. Please click to the following link to get an email to reset your password!"); + // } + //} +} diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/pl.json b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/pl-PL.json similarity index 98% rename from modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/pl.json rename to modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/pl-PL.json index b661af8050..032ac41cfa 100644 --- a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/pl.json +++ b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "UserName": "Nazwa użytkownika", "EmailAddress": "Adres email", diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs index e194c1544d..60f55497da 100644 --- a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs @@ -29,12 +29,12 @@ namespace Volo.Abp.Account.Web.Pages.Account public IdentityServerSupportedLoginModel( IAuthenticationSchemeProvider schemeProvider, - IOptions accountOptions, - IIdentityServerInteractionService interaction, - IClientStore clientStore, + IOptions accountOptions, + IIdentityServerInteractionService interaction, + IClientStore clientStore, IEventService identityServerEvents) :base( - schemeProvider, + schemeProvider, accountOptions) { Interaction = interaction; @@ -95,7 +95,6 @@ namespace Volo.Abp.Account.Web.Pages.Account return Page(); } - [UnitOfWork] //TODO: Will be removed when we implement action filter public override async Task OnPostAsync(string action) { if (action == "Cancel") @@ -116,7 +115,7 @@ namespace Volo.Abp.Account.Web.Pages.Account ValidateModel(); ExternalProviders = await GetExternalProviders(); - + EnableLocalLogin = await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin); await ReplaceEmailToUsernameOfInputIfNeeds(); @@ -166,7 +165,6 @@ namespace Volo.Abp.Account.Web.Pages.Account return RedirectSafely(ReturnUrl, ReturnUrlHash); } - [UnitOfWork] public override async Task OnPostExternalLogin(string provider) { if (AccountOptions.WindowsAuthenticationSchemeName == provider) diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/pl.json b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/pl-PL.json similarity index 93% rename from modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/pl.json rename to modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/pl-PL.json index 8806854643..05ba8451c6 100644 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/pl.json +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Permission:Blogging": "Blog", "Permission:Blogs": "Blogi", diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pl.json b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pl-PL.json similarity index 98% rename from modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pl.json rename to modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pl-PL.json index 15e0c7eb0d..3795ab93d3 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pl.json +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Menu:Blogs": "Blogi", "Menu:BlogManagement": "Zarządzanie blogiem", diff --git a/modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/pl.json b/modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/pl-PL.json similarity index 91% rename from modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/pl.json rename to modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/pl-PL.json index f131f9e139..ca06e49ef5 100644 --- a/modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/pl.json +++ b/modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "WelcomeVoloDocs": "Witaj w VoloDocs!", "NoProjectWarning": "Nie ma jeszcze zdefiniowanego projektu!", diff --git a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/pl.json b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/pl-PL.json similarity index 98% rename from modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/pl.json rename to modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/pl-PL.json index be91b22800..80922afc71 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/pl.json +++ b/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Permission:DocumentManagement": "Zarządzanie dokumentacją", "Permission:Projects": "Projekty", diff --git a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs index 02a7c171bd..430cd9ead2 100644 --- a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs +++ b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs @@ -7,8 +7,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Nest; -using Newtonsoft.Json; using Volo.Abp; using Volo.Abp.Caching; using Volo.Docs.Caching; diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentSource.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentSource.cs index cc0f03a60a..f97eddf0e2 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentSource.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentSource.cs @@ -87,6 +87,11 @@ namespace Volo.Docs.FileSystem.Documents { throw new SecurityException("Can not get a resource file out of the project folder!"); } + + if (!File.Exists(path)) + { + throw new DocumentNotFoundException(path); + } } } } diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs index a6285389de..ef6f6e1ebb 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs @@ -48,68 +48,155 @@ namespace Volo.Docs.GitHub.Documents fileName = documentName.Substring(documentName.LastIndexOf('/') + 1); } - var fileCommits = await GetFileCommitsAsync(project, version, project.GetGitHubInnerUrl(languageCode, documentName)); - - var documentCreationTime = fileCommits.LastOrDefault()?.Commit.Author.Date.DateTime ?? DateTime.MinValue; - - var lastSignificantUpdateTime = !isNavigationDocument && !isParameterDocument && version == project.LatestVersionBranchName ? - await GetLastSignificantUpdateTime( - fileCommits, - project, - project.GetGitHubInnerUrl(languageCode, documentName), - lastKnownSignificantUpdateTime, - documentCreationTime - ) ?? lastKnownSignificantUpdateTime - : null; + var content = await DownloadWebContentAsStringAsync(rawDocumentUrl, token, userAgent); + var commits = await GetGitHubCommitsOrNull(project, documentName, languageCode, version); + + var documentCreationTime = GetFirstCommitDate(commits); + var lastUpdateTime = GetLastCommitDate(commits); + var lastSignificantUpdateTime = await GetLastKnownSignificantUpdateTime(project, documentName, languageCode, version, lastKnownSignificantUpdateTime, isNavigationDocument, isParameterDocument, commits, documentCreationTime); - var document = new Document(GuidGenerator.Create(), + var document = new Document + ( + GuidGenerator.Create(), project.Id, documentName, version, languageCode, fileName, - await DownloadWebContentAsStringAsync(rawDocumentUrl, token, userAgent), + content, project.Format, editLink, rootUrl, rawRootUrl, localDirectory, documentCreationTime, - fileCommits.FirstOrDefault()?.Commit.Author.Date.DateTime ?? DateTime.MinValue, + lastUpdateTime, DateTime.Now, - lastSignificantUpdateTime); + lastSignificantUpdateTime + ); + + if (isNavigationDocument || isParameterDocument) + { + return document; + } + + var authors = GetAuthors(commits); + foreach (var author in authors) + { + document.AddContributor(author.Login, author.HtmlUrl, author.AvatarUrl); + } - var authors = fileCommits + return document; + } + + private async Task GetLastKnownSignificantUpdateTime( + Project project, + string documentName, + string languageCode, + string version, + DateTime? lastKnownSignificantUpdateTime, + bool isNavigationDocument, + bool isParameterDocument, + IReadOnlyList commits, + DateTime documentCreationTime) + { + return !isNavigationDocument && !isParameterDocument && version == project.LatestVersionBranchName + ? await GetLastSignificantUpdateTime( + commits, + project, + project.GetGitHubInnerUrl(languageCode, documentName), + lastKnownSignificantUpdateTime, + documentCreationTime + ) ?? lastKnownSignificantUpdateTime + : null; + } + + private static List GetAuthors(IReadOnlyList commits) + { + if (commits == null || !commits.Any()) + { + return new List(); + } + + return commits .Where(x => x.Author != null) .Select(x => x.Author) .GroupBy(x => x.Id) .OrderByDescending(x => x.Count()) - .Select(x => x.FirstOrDefault()).ToList(); + .Select(x => x.FirstOrDefault()) + .ToList(); + } + + private static DateTime GetLastCommitDate(IReadOnlyList commits) + { + return GetCommitDate(commits, false); + } - if (!isNavigationDocument && !isParameterDocument) + private static DateTime GetFirstCommitDate(IReadOnlyList commits) + { + return GetCommitDate(commits, true); + } + + private static DateTime GetCommitDate(IReadOnlyList commits, bool isFirstCommit) + { + if (commits == null) { - foreach (var author in authors) - { - document.AddContributor(author.Login, author.HtmlUrl, author.AvatarUrl); - } + return DateTime.MinValue; } - return document; + var gitHubCommit = isFirstCommit ? + commits.LastOrDefault() : //first commit + commits.FirstOrDefault(); //last commit + + if (gitHubCommit == null) + { + return DateTime.MinValue; + } + + if (gitHubCommit.Commit == null) + { + return DateTime.MinValue; + } + + if (gitHubCommit.Commit.Author == null) + { + return DateTime.MinValue; + } + + return gitHubCommit.Commit.Author.Date.DateTime; + } + + private async Task> GetGitHubCommitsOrNull(Project project, string documentName, string languageCode, string version) + { + /* + * Getting file commits usually throws "Resource temporarily unavailable" or "Network is unreachable" + * This is a trival information and running this inside try-catch is safer. + */ + + try + { + return await GetFileCommitsAsync(project, version, project.GetGitHubInnerUrl(languageCode, documentName)); + } + catch (Exception e) + { + Logger.LogError(e.ToString()); + return null; + } } private async Task GetLastSignificantUpdateTime( - IReadOnlyList fileCommits, + IReadOnlyList commits, Project project, string fileName, DateTime? lastKnownSignificantUpdateTime, DateTime documentCreationTime) { - if (!fileCommits.Any()) + if (commits == null || !commits.Any()) { return null; } - var fileCommitsAfterCreation = fileCommits.Take(fileCommits.Count - 1); + var fileCommitsAfterCreation = commits.Take(commits.Count - 1); var commitsToEvaluate = (lastKnownSignificantUpdateTime != null ? fileCommitsAfterCreation.Where(c => c.Commit.Author.Date.DateTime > lastKnownSignificantUpdateTime) @@ -117,7 +204,6 @@ namespace Volo.Docs.GitHub.Documents foreach (var gitHubCommit in commitsToEvaluate) { - var fullCommit = await _githubRepositoryManager.GetSingleCommitsAsync( GetOwnerNameFromUrl(project.GetGitHubUrl()), GetRepositoryNameFromUrl(project.GetGitHubUrl()), diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/en.json b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/en.json index 998327cd90..802e05367c 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/en.json +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/en.json @@ -16,6 +16,7 @@ "Projects": "Project(s)", "NoProjectWarning": "There are no projects yet!", "DocumentNotFound": "Oops, the requested document was not found!", + "ProjectNotFound": "Oops, the requested project was not found!", "NavigationDocumentNotFound": "This version does not have a navigation document!", "DocumentNotFoundInSelectedLanguage": "Document in the language you wanted is not found. Document in the default language is shown.", "FilterTopics": "Filter topics", diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/pl.json b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/pl-PL.json similarity index 95% rename from modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/pl.json rename to modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/pl-PL.json index 5ba3dd6c10..44bd398458 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/pl.json +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Documents": "Dokumenty", "BackToWebsite": "Powrót do strony", diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hans.json b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hans.json index ef4e95f3dc..8f3a3e44fb 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hans.json +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hans.json @@ -1,6 +1,6 @@ { "culture": "zh-Hans", - "texts": { + "texts": { "Documents": "文档", "BackToWebsite": "返回主网站", "Contributors": "贡献者", @@ -14,6 +14,7 @@ "Projects": "项目", "NoProjectWarning": "还没有项目!", "DocumentNotFound": "找不到请求的文档!", + "ProjectNotFound": "找不到请求的项目!", "NavigationDocumentNotFound": "这个版本没有导航文件!", "DocumentNotFoundInSelectedLanguage": "本文档不适用于所选语言, 将以默认语言显示文档.", "FilterTopics": "过滤主题", diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hant.json b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hant.json index 6a2f4d8799..dea171863c 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hant.json +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/zh-Hant.json @@ -14,6 +14,7 @@ "Projects": "專案", "NoProjectWarning": "沒有專案!", "DocumentNotFound": "找不到要求的文件!", + "ProjectNotFound": "找不到請求的項目!", "NavigationDocumentNotFound": "這個版本沒有導覽文件!", "DocumentNotFoundInSelectedLanguage": "本文件不適用於所選語系,將以預設語系顯示.", "FilterTopics": "過濾主題", diff --git a/modules/docs/src/Volo.Docs.Web/HtmlConverting/HtmlNormalizer.cs b/modules/docs/src/Volo.Docs.Web/HtmlConverting/HtmlNormalizer.cs index 6f0a680ce2..1689a209fe 100644 --- a/modules/docs/src/Volo.Docs.Web/HtmlConverting/HtmlNormalizer.cs +++ b/modules/docs/src/Volo.Docs.Web/HtmlConverting/HtmlNormalizer.cs @@ -35,5 +35,51 @@ namespace Volo.Docs.HtmlConverting { return Regex.Replace(content, "", "", RegexOptions.IgnoreCase); } + + /// + /// Wraps an image with a tag that's clickable to open the image in a new browser tab. + /// + public static string WrapImagesWithinAnchors(string html) + { + try + { + return Regex.Replace(html, "", match => + { + var link = match.Groups[1].Value; + var imgTag = match.Groups[0].Value; + var title = GetTitleFromTag(imgTag); + + return $"\"{title}\""; + }); + + } + catch + { + // ignored + return html; + } + } + + private static string GetTitleFromTag(string imgTag) + { + if (string.IsNullOrWhiteSpace(imgTag)) + { + return null; + } + + var match = Regex.Match(imgTag, @"\stitle\s?\=\s?(\""|')(.+?)(\""|')", RegexOptions.Multiline); + if (match.Success && match.Groups.Count > 2) + { + return match.Groups[2].ToString().Trim(); + } + + match = Regex.Match(imgTag, @"\salt\s*\=\s*(\""|')(.*?)(\""|')", RegexOptions.Multiline); + if (match.Success && match.Groups.Count > 2) + { + return match.Groups[2].ToString().Trim(); + } + + return null; + } } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Web/Markdown/MarkdownDocumentToHtmlConverter.cs b/modules/docs/src/Volo.Docs.Web/Markdown/MarkdownDocumentToHtmlConverter.cs index 12dec9b304..9942c52309 100644 --- a/modules/docs/src/Volo.Docs.Web/Markdown/MarkdownDocumentToHtmlConverter.cs +++ b/modules/docs/src/Volo.Docs.Web/Markdown/MarkdownDocumentToHtmlConverter.cs @@ -44,7 +44,10 @@ namespace Volo.Docs.Markdown languageCode ); - return _markdownConverter.ConvertToHtml(content); + var html = _markdownConverter.ConvertToHtml(content); + + return html; + // return HtmlNormalizer.WrapImagesWithinAnchors(html); } protected virtual string NormalizeLinks( diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml index b7db40f1fe..7f521de34b 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml @@ -5,12 +5,12 @@ @using Volo.Abp.AspNetCore.Mvc.UI.Packages.Clipboard @using Volo.Abp.AspNetCore.Mvc.UI.Packages.MalihuCustomScrollbar @using Volo.Abp.AspNetCore.Mvc.UI.Packages.Popper -@using Volo.Docs.Pages.Documents.Shared.DocumentNotFoundComponent @using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs @using Volo.Abp.AspNetCore.Mvc.UI.Theming @using Volo.Docs @using Volo.Docs.Localization @using Volo.Docs.Pages.Documents.Project +@using Volo.Docs.Pages.Documents.Shared.ErrorComponent @inject IThemeManager ThemeManager @inject IPageLayout PageLayout @inject IHtmlLocalizer L @@ -39,9 +39,10 @@ + } -@if (Model.DocumentFound) +@if (Model.LoadSuccess) {
@@ -152,11 +153,11 @@
-
@@ -171,11 +172,11 @@ - @@ -225,8 +226,8 @@ @if (!string.IsNullOrEmpty(Model.Document.EditLink)) { - - @(L["Edit"]) + + @(L["Edit"]) (@L["LastEditTime"]: @Model.Document.LastUpdatedTime.ToShortDateString()) } @@ -348,14 +349,31 @@ } else { - @(await Component.InvokeAsync(new - { - model = new DocumentNotFoundPageModel + if (!Model.ProjectFound) + { + @(await Component.InvokeAsync(new + { + model = new ErrorPageModel + { + RedirectUrl = Model.DocumentsUrlPrefix, + ErrorCode = "404", + ErrorMessage = L.GetString("ProjectNotFound") + } + })) + } + + if (!Model.DocumentFound) + { + @(await Component.InvokeAsync(new { - ProjectName = Model.ProjectName, - DocumentName = Model.DocumentName, - LanguageCode = Model.LanguageCode, - Version = Model.Version, - } - })) -} \ No newline at end of file + model = new ErrorPageModel + { + RedirectUrl = Model.DocumentsUrlPrefix + Model.LanguageCode + "/" + Model.ProjectName + "/" + + (Model.LatestVersionInfo.IsSelected ? DocsAppConsts.Latest : Model.Version), + ErrorCode = "404", + ErrorMessage = L.GetString("DocumentNotFound"), + AutoRedirect = !Model.DocumentName.IsNullOrWhiteSpace() + } + })) + } +} diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs index 04f8614f56..0acd12531f 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs @@ -7,13 +7,11 @@ using System.Threading.Tasks; using System.Web; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Volo.Abp; -using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; using Volo.Abp.Domain.Entities; -using Volo.Abp.Localization; using Volo.Docs.Documents; using Volo.Docs.HtmlConverting; using Volo.Docs.Models; @@ -37,6 +35,10 @@ namespace Volo.Docs.Pages.Documents.Project public bool DocumentFound { get; set; } = true; + public bool ProjectFound { get; set; } = true; + + public bool LoadSuccess => DocumentFound && ProjectFound; + public string DefaultLanguageCode { get; set; } public ProjectDto Project { get; set; } @@ -120,7 +122,8 @@ namespace Volo.Docs.Pages.Documents.Project catch (EntityNotFoundException e) { Logger.LogWarning(e.Message); - return NotFound(); + ProjectFound = false; + return Page(); } if (ShowProjectsCombobox) @@ -344,43 +347,13 @@ namespace Volo.Docs.Pages.Documents.Project try { - if (DocumentName.IsNullOrWhiteSpace()) - { - Document = await _documentAppService.GetDefaultAsync( - new GetDefaultDocumentInput - { - ProjectId = Project.Id, - LanguageCode = LanguageCode, - Version = Version - } - ); - } - else - { - Document = await _documentAppService.GetAsync( - new GetDocumentInput - { - ProjectId = Project.Id, - Name = DocumentNameWithExtension, - LanguageCode = LanguageCode, - Version = Version - } - ); - } + Document = await GetSpecificDocumentOrDefaultAsync(LanguageCode); } catch (DocumentNotFoundException) { if (LanguageCode != DefaultLanguageCode) { - Document = await _documentAppService.GetAsync( - new GetDocumentInput - { - ProjectId = Project.Id, - Name = DocumentNameWithExtension, - LanguageCode = DefaultLanguageCode, - Version = Version - } - ); + Document = await GetSpecificDocumentOrDefaultAsync(DefaultLanguageCode); DocumentLanguageIsDifferent = true; } @@ -427,6 +400,8 @@ namespace Volo.Docs.Pages.Documents.Project Document.LocalDirectory ); + content = HtmlNormalizer.WrapImagesWithinAnchors(content); + //todo find a way to make it on client in prismJS configuration (eg: map C# => csharp) content = HtmlNormalizer.ReplaceCodeBlocksLanguage( content, @@ -542,6 +517,33 @@ namespace Volo.Docs.Pages.Documents.Project } + private async Task GetSpecificDocumentOrDefaultAsync(string languageCode) + { + if (DocumentName.IsNullOrWhiteSpace()) + { + return await _documentAppService.GetDefaultAsync( + new GetDefaultDocumentInput + { + ProjectId = Project.Id, + LanguageCode = languageCode, + Version = Version + } + ); + } + else + { + return await _documentAppService.GetAsync( + new GetDocumentInput + { + ProjectId = Project.Id, + Name = DocumentNameWithExtension, + LanguageCode = languageCode, + Version = Version + } + ); + } + } + public async Task SetDocumentPreferencesAsync() { var projectParameters = await _documentAppService.GetParametersAsync( diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/index.js b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/index.js index 1b3d5ac7c3..71fb550539 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/index.js +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/index.js @@ -203,31 +203,31 @@ 'YandexBot', 'msnbot', 'Rambler', - 'Yahoo', - 'AbachoBOT', - 'accoona', + 'Yahoo', + 'AbachoBOT', + 'accoona', 'AcoiRobot', 'ASPSeek', - 'CrocCrawler', + 'CrocCrawler', 'Dumbot', - 'FAST-WebCrawler', - 'GeonaBot', + 'FAST-WebCrawler', + 'GeonaBot', 'Gigabot', - 'Lycos', - 'MSRBOT', + 'Lycos', + 'MSRBOT', 'Scooter', 'AltaVista', - 'IDBot', - 'eStyle', - 'Scrubby', - 'Slurp', - 'DuckDuckBot', - 'Baiduspider', - 'VoilaBot', - 'ExaLead', - 'Search Dog', - 'MSN Bot' , - 'BingBot' + 'IDBot', + 'eStyle', + 'Scrubby', + 'Slurp', + 'DuckDuckBot', + 'Baiduspider', + 'VoilaBot', + 'ExaLead', + 'Search Dog', + 'MSN Bot', + 'BingBot' ]; var agent = navigator.userAgent; @@ -276,9 +276,8 @@ if (queryStrings.length <= 0) { returnList.push(key + "=" + $(this).val()); - - } - else { + + } else { for (var k = 0; k < queryStrings.length; k++) { returnList.push(key + "=" + $(this).val() + "&" + queryStrings[k]); } @@ -291,12 +290,12 @@ var queryStrings = getQueryStringsFromComboboxes(0); for (var i = 0; i < queryStrings.length; i++) { - html += "" + queryStrings[i]+" " + html += "" + queryStrings[i] + " " } $("#crawler_link").html(html); }; - + initNavigationFilter("sidebar-scroll"); initAnchorTags(".docs-page .docs-body"); @@ -309,5 +308,4 @@ }); -})(jQuery); - +})(jQuery); \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/DocumentNotFoundPageModel.cs b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/DocumentNotFoundPageModel.cs deleted file mode 100644 index 72b2b97584..0000000000 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/DocumentNotFoundPageModel.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Volo.Docs.Pages.Documents.Shared.DocumentNotFoundComponent -{ - public class DocumentNotFoundPageModel - { - public string ProjectName { get; set; } - - public string LanguageCode { get; set; } - - public string Version { get; set; } - - public string DocumentName { get; set; } - - public string DocumentsUrlPrefix { get; set; } - } -} diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/DocumentNotFoundViewComponent.cs b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/DocumentNotFoundViewComponent.cs deleted file mode 100644 index 7aeed9beee..0000000000 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/DocumentNotFoundViewComponent.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; -using Volo.Abp.AspNetCore.Mvc; - -namespace Volo.Docs.Pages.Documents.Shared.DocumentNotFoundComponent -{ - public class DocumentNotFoundViewComponent : AbpViewComponent - { - private readonly DocsUiOptions _options; - - public DocumentNotFoundViewComponent(IOptions options) - { - _options = options.Value; - } - public IViewComponentResult Invoke(DocumentNotFoundPageModel model, string defaultErrorMessageKey) - { - model.DocumentsUrlPrefix = _options.RoutePrefix; - - return View("~/Pages/Documents/Shared/DocumentNotFoundComponent/Default.cshtml", model); - } - } -} diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/Default.cshtml b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/Default.cshtml similarity index 55% rename from modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/Default.cshtml rename to modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/Default.cshtml index 604ff05da7..4aef93c593 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/DocumentNotFoundComponent/Default.cshtml +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/Default.cshtml @@ -1,17 +1,19 @@ -@using Volo.Docs.Pages.Documents.Shared.DocumentNotFoundComponent -@model DocumentNotFoundPageModel +@model Volo.Docs.Pages.Documents.Shared.ErrorComponent.ErrorPageModel @{ }
-
- 404 +
+ @Model.ErrorCode

- "@Model.DocumentName" not found in @Model.ProjectName documents, with @Model.Version version and @Model.LanguageCode language. + @Model.ErrorMessage

-
- - Go Back - +
+ @if (Model.AutoRedirect) + { + + Go Back (3) + + }
@@ -20,7 +22,7 @@ h1 { font-size: 1.75em; line-height: 1.75; - color: #777; + color: #777; font-weight: normal; } @@ -37,7 +39,7 @@ transform: translate(-50%, -50%); text-align: center; } - + .notfound-404 { font-size: 300px; font-weight: 700; @@ -49,5 +51,5 @@ margin-bottom: -150px; z-index: -1; position: relative; - } - \ No newline at end of file + } + diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/ErrorPageModel.cs b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/ErrorPageModel.cs new file mode 100644 index 0000000000..62a64fc9d6 --- /dev/null +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/ErrorPageModel.cs @@ -0,0 +1,13 @@ +namespace Volo.Docs.Pages.Documents.Shared.ErrorComponent +{ + public class ErrorPageModel + { + public string ErrorMessage { get; set; } + + public string ErrorCode { get; set; } + + public string RedirectUrl { get; set; } + + public bool AutoRedirect { get; set; } = true; + } +} diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/ErrorViewComponent.cs b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/ErrorViewComponent.cs new file mode 100644 index 0000000000..493b6d896a --- /dev/null +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/ErrorViewComponent.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Volo.Abp.AspNetCore.Mvc; + +namespace Volo.Docs.Pages.Documents.Shared.ErrorComponent +{ + public class ErrorViewComponent : AbpViewComponent + { + public IViewComponentResult Invoke(ErrorPageModel model) + { + return View("~/Pages/Documents/Shared/ErrorComponent/Default.cshtml", model); + } + } +} diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/error.js b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/error.js new file mode 100644 index 0000000000..edc010aa9f --- /dev/null +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/ErrorComponent/error.js @@ -0,0 +1,20 @@ +(function ($) { + + $(function () { + var errorPageRedirect = function () { + var second = 3; + var close = setInterval(() => { + $("#ErrorRedirectSeconds").text(`(${--second})`); + if (second === 0) { + clearInterval(close); + $("#ErrorRedirect")[0].click(); + } + }, 1000); + } + + if (document.getElementById("ErrorRedirect")) { + errorPageRedirect(); + } + }); + +})(jQuery); diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/pl.json b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/pl-PL.json similarity index 66% rename from modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/pl.json rename to modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/pl-PL.json index d714a0d499..86d5f08258 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/pl.json +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Features": "Funkcje" } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl-PL.json similarity index 99% rename from modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl.json rename to modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl-PL.json index 3a0896147c..54d137988d 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Menu:IdentityManagement": "Zarządzanie tożsamością", "Users": "Użytkownicy", diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json similarity index 91% rename from modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl.json rename to modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json index e1043feea7..b552012c1b 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl.json +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Permissions": "Uprawnienia", "OnlyProviderPermissons": "Tylko ten dostawca", diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pl.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pl-PL.json similarity index 67% rename from modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pl.json rename to modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pl-PL.json index 4aa474d82b..6c29a15453 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pl.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Settings": "Ustawienia" } diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pl.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pl-PL.json similarity index 97% rename from modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pl.json rename to modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pl-PL.json index 18891c3633..be3af86d22 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pl.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Menu:TenantManagement": "Zarządzanie tenantami", "Tenants": "Tenanty", diff --git a/npm/ng-packs/package.json b/npm/ng-packs/package.json index 93715f2a8e..d141817a6e 100644 --- a/npm/ng-packs/package.json +++ b/npm/ng-packs/package.json @@ -22,19 +22,19 @@ "generate:changelog": "conventional-changelog -p angular -i CHANGELOG.md -s" }, "devDependencies": { - "@abp/ng.account": "^2.6.1", - "@abp/ng.account.config": "^2.6.1", - "@abp/ng.core": "^2.6.1", - "@abp/ng.feature-management": "^2.6.1", - "@abp/ng.identity": "^2.6.1", - "@abp/ng.identity.config": "^2.6.1", - "@abp/ng.permission-management": "^2.6.1", - "@abp/ng.setting-management": "^2.6.1", - "@abp/ng.setting-management.config": "^2.6.1", - "@abp/ng.tenant-management": "^2.6.1", - "@abp/ng.tenant-management.config": "^2.6.1", - "@abp/ng.theme.basic": "^2.6.1", - "@abp/ng.theme.shared": "^2.6.1", + "@abp/ng.account": "~2.6.2", + "@abp/ng.account.config": "~2.6.2", + "@abp/ng.core": "^2.6.2", + "@abp/ng.feature-management": "^2.6.2", + "@abp/ng.identity": "~2.6.2", + "@abp/ng.identity.config": "~2.6.2", + "@abp/ng.permission-management": "^2.6.2", + "@abp/ng.setting-management": "~2.6.2", + "@abp/ng.setting-management.config": "~2.6.2", + "@abp/ng.tenant-management": "~2.6.2", + "@abp/ng.tenant-management.config": "~2.6.2", + "@abp/ng.theme.basic": "~2.6.2", + "@abp/ng.theme.shared": "^2.6.2", "@abp/utils": "^2.6.0", "@angular-builders/jest": "^8.2.0", "@angular-devkit/build-angular": "~0.803.21", @@ -76,7 +76,7 @@ "just-compare": "^1.3.0", "lerna": "^3.19.0", "ng-packagr": "^5.7.1", - "ngxs-reset-plugin": "^1.2.0", + "ngxs-reset-plugin": "^1.3.0", "ngxs-schematic": "^1.1.9", "prettier": "^1.18.2", "protractor": "~5.4.0", diff --git a/npm/ng-packs/packages/account-config/src/lib/services/account-config.service.ts b/npm/ng-packs/packages/account-config/src/lib/services/account-config.service.ts index 0add43bc33..d94935c071 100644 --- a/npm/ng-packs/packages/account-config/src/lib/services/account-config.service.ts +++ b/npm/ng-packs/packages/account-config/src/lib/services/account-config.service.ts @@ -1,5 +1,6 @@ import { addAbpRoutes, eLayoutType } from '@abp/ng.core'; import { Injectable } from '@angular/core'; +import { eAccountRouteNames } from '@abp/ng.account'; @Injectable({ providedIn: 'root', @@ -7,14 +8,14 @@ import { Injectable } from '@angular/core'; export class AccountConfigService { constructor() { addAbpRoutes({ - name: 'AbpAccount::Menu:Account', + name: eAccountRouteNames.Account, path: 'account', invisible: true, layout: eLayoutType.application, children: [ - { path: 'login', name: 'AbpAccount::Login', order: 1 }, - { path: 'register', name: 'AbpAccount::Register', order: 2 }, - { path: 'manage-profile', name: 'AbpAccount::ManageYourProfile', order: 3 }, + { path: 'login', name: eAccountRouteNames.Login, order: 1 }, + { path: 'register', name: eAccountRouteNames.Register, order: 2 }, + { path: 'manage-profile', name: eAccountRouteNames.ManageProfile, order: 3 }, ], }); } diff --git a/npm/ng-packs/packages/account/src/lib/enums/index.ts b/npm/ng-packs/packages/account/src/lib/enums/index.ts new file mode 100644 index 0000000000..5ac9b6e8fa --- /dev/null +++ b/npm/ng-packs/packages/account/src/lib/enums/index.ts @@ -0,0 +1,2 @@ +export * from './components'; +export * from './route-names'; diff --git a/npm/ng-packs/packages/account/src/lib/enums/route-names.ts b/npm/ng-packs/packages/account/src/lib/enums/route-names.ts new file mode 100644 index 0000000000..810985e027 --- /dev/null +++ b/npm/ng-packs/packages/account/src/lib/enums/route-names.ts @@ -0,0 +1,6 @@ +export const enum eAccountRouteNames { + Account = 'AbpAccount::Menu:Account', + Login = 'AbpAccount::Login', + Register = 'AbpAccount::Register', + ManageProfile = 'AbpAccount::ManageYourProfile', +} diff --git a/npm/ng-packs/packages/account/src/public-api.ts b/npm/ng-packs/packages/account/src/public-api.ts index 63cb476f9b..8d1ef3c43a 100644 --- a/npm/ng-packs/packages/account/src/public-api.ts +++ b/npm/ng-packs/packages/account/src/public-api.ts @@ -1,6 +1,6 @@ export * from './lib/account.module'; export * from './lib/components'; -export * from './lib/enums/components'; +export * from './lib/enums'; export * from './lib/tokens'; export * from './lib/models'; export * from './lib/services'; diff --git a/npm/ng-packs/packages/core/src/lib/plugins/config.plugin.ts b/npm/ng-packs/packages/core/src/lib/plugins/config.plugin.ts index f9c8f45423..b2afab6072 100644 --- a/npm/ng-packs/packages/core/src/lib/plugins/config.plugin.ts +++ b/npm/ng-packs/packages/core/src/lib/plugins/config.plugin.ts @@ -10,7 +10,7 @@ import { } from '@ngxs/store'; import clone from 'just-clone'; import snq from 'snq'; -import { ABP } from '../models'; +import { ABP } from '../models/common'; import { getAbpRoutes, organizeRoutes } from '../utils/route-utils'; export const NGXS_CONFIG_PLUGIN_OPTIONS = new InjectionToken('NGXS_CONFIG_PLUGIN_OPTIONS'); @@ -30,8 +30,8 @@ export class ConfigPlugin implements NgxsPlugin { if (isInitAction && !this.initialized) { const transformedRoutes = transformRoutes(this.router.config); - let { routes } = transformedRoutes; - const { wrappers } = transformedRoutes; + let { routes, wrappers } = transformedRoutes; + wrappers = reduceWrappers(wrappers); routes = organizeRoutes(routes, wrappers); const flattedRoutes = flatRoutes(clone(routes)); @@ -118,3 +118,14 @@ function flatRoutes(routes: ABP.FullRoute[]): ABP.FullRoute[] { return flat(routes); } + +function reduceWrappers(wrappers: ABP.FullRoute[] = []) { + const existingWrappers = new Set(); + + return wrappers.filter(wrapper => { + if (existingWrappers.has(wrapper.name)) return false; + + existingWrappers.add(wrapper.name); + return true; + }); +} diff --git a/npm/ng-packs/packages/identity-config/src/lib/services/identity-config.service.ts b/npm/ng-packs/packages/identity-config/src/lib/services/identity-config.service.ts index d1b52fed8b..c1fdbb102d 100644 --- a/npm/ng-packs/packages/identity-config/src/lib/services/identity-config.service.ts +++ b/npm/ng-packs/packages/identity-config/src/lib/services/identity-config.service.ts @@ -1,5 +1,6 @@ import { addAbpRoutes, eLayoutType } from '@abp/ng.core'; import { Injectable } from '@angular/core'; +import { eIdentityRouteNames } from '@abp/ng.identity'; @Injectable({ providedIn: 'root', @@ -8,29 +9,29 @@ export class IdentityConfigService { constructor() { addAbpRoutes([ { - name: 'AbpUiNavigation::Menu:Administration', + name: eIdentityRouteNames.Administration, path: '', order: 1, wrapper: true, iconClass: 'fa fa-wrench', }, { - name: 'AbpIdentity::Menu:IdentityManagement', + name: eIdentityRouteNames.IdentityManagement, path: 'identity', order: 1, - parentName: 'AbpUiNavigation::Menu:Administration', + parentName: eIdentityRouteNames.Administration, layout: eLayoutType.application, iconClass: 'fa fa-id-card-o', children: [ { path: 'roles', - name: 'AbpIdentity::Roles', + name: eIdentityRouteNames.Roles, order: 1, requiredPolicy: 'AbpIdentity.Roles', }, { path: 'users', - name: 'AbpIdentity::Users', + name: eIdentityRouteNames.Users, order: 2, requiredPolicy: 'AbpIdentity.Users', }, diff --git a/npm/ng-packs/packages/identity/src/lib/enums/index.ts b/npm/ng-packs/packages/identity/src/lib/enums/index.ts new file mode 100644 index 0000000000..5ac9b6e8fa --- /dev/null +++ b/npm/ng-packs/packages/identity/src/lib/enums/index.ts @@ -0,0 +1,2 @@ +export * from './components'; +export * from './route-names'; diff --git a/npm/ng-packs/packages/identity/src/lib/enums/route-names.ts b/npm/ng-packs/packages/identity/src/lib/enums/route-names.ts new file mode 100644 index 0000000000..15933a5b77 --- /dev/null +++ b/npm/ng-packs/packages/identity/src/lib/enums/route-names.ts @@ -0,0 +1,6 @@ +export const enum eIdentityRouteNames { + Administration = 'AbpUiNavigation::Menu:Administration', + IdentityManagement = 'AbpIdentity::Menu:IdentityManagement', + Roles = 'AbpIdentity::Roles', + Users = 'AbpIdentity::Users', +} diff --git a/npm/ng-packs/packages/identity/src/public-api.ts b/npm/ng-packs/packages/identity/src/public-api.ts index 1a2b217931..fd26c8a649 100644 --- a/npm/ng-packs/packages/identity/src/public-api.ts +++ b/npm/ng-packs/packages/identity/src/public-api.ts @@ -4,7 +4,7 @@ export * from './lib/identity.module'; export * from './lib/actions/identity.actions'; -export * from './lib/enums/components'; +export * from './lib/enums'; export * from './lib/components'; export * from './lib/models/identity'; export * from './lib/services'; diff --git a/npm/ng-packs/packages/setting-management-config/src/lib/services/setting-management-config.service.ts b/npm/ng-packs/packages/setting-management-config/src/lib/services/setting-management-config.service.ts index 721a5a5991..a5b319ccb1 100644 --- a/npm/ng-packs/packages/setting-management-config/src/lib/services/setting-management-config.service.ts +++ b/npm/ng-packs/packages/setting-management-config/src/lib/services/setting-management-config.service.ts @@ -2,6 +2,7 @@ import { Injectable, Injector } from '@angular/core'; import { addAbpRoutes, eLayoutType, PatchRouteByName, ABP } from '@abp/ng.core'; import { getSettingTabs } from '@abp/ng.theme.shared'; import { Store } from '@ngxs/store'; +import { eSettingManagementRouteNames } from '@abp/ng.setting-management'; @Injectable({ providedIn: 'root', @@ -13,7 +14,7 @@ export class SettingManagementConfigService { constructor(private injector: Injector) { const route = { - name: 'AbpSettingManagement::Settings', + name: eSettingManagementRouteNames.Settings, path: 'setting-management', parentName: 'AbpUiNavigation::Menu:Administration', requiredPolicy: 'AbpAccount.SettingManagement', diff --git a/npm/ng-packs/packages/setting-management/src/lib/enums/index.ts b/npm/ng-packs/packages/setting-management/src/lib/enums/index.ts new file mode 100644 index 0000000000..5ac9b6e8fa --- /dev/null +++ b/npm/ng-packs/packages/setting-management/src/lib/enums/index.ts @@ -0,0 +1,2 @@ +export * from './components'; +export * from './route-names'; diff --git a/npm/ng-packs/packages/setting-management/src/lib/enums/route-names.ts b/npm/ng-packs/packages/setting-management/src/lib/enums/route-names.ts new file mode 100644 index 0000000000..86ad145f5f --- /dev/null +++ b/npm/ng-packs/packages/setting-management/src/lib/enums/route-names.ts @@ -0,0 +1,3 @@ +export const enum eSettingManagementRouteNames { + Settings = 'AbpSettingManagement::Settings', +} diff --git a/npm/ng-packs/packages/setting-management/src/public-api.ts b/npm/ng-packs/packages/setting-management/src/public-api.ts index ac030c3ccf..24e62d4da1 100644 --- a/npm/ng-packs/packages/setting-management/src/public-api.ts +++ b/npm/ng-packs/packages/setting-management/src/public-api.ts @@ -1,3 +1,3 @@ export * from './lib/setting-management.module'; export * from './lib/components/setting-management.component'; -export * from './lib/enums/components'; +export * from './lib/enums'; diff --git a/npm/ng-packs/packages/tenant-management-config/src/lib/services/tenant-management-config.service.ts b/npm/ng-packs/packages/tenant-management-config/src/lib/services/tenant-management-config.service.ts index 48a6d31c84..ab5ca4a081 100644 --- a/npm/ng-packs/packages/tenant-management-config/src/lib/services/tenant-management-config.service.ts +++ b/npm/ng-packs/packages/tenant-management-config/src/lib/services/tenant-management-config.service.ts @@ -1,25 +1,34 @@ import { Injectable } from '@angular/core'; import { addAbpRoutes, eLayoutType } from '@abp/ng.core'; - +import { eTenantManagementRouteNames } from '@abp/ng.tenant-management'; @Injectable({ providedIn: 'root', }) export class TenantManagementConfigService { constructor() { - addAbpRoutes({ - name: 'AbpTenantManagement::Menu:TenantManagement', - path: 'tenant-management', - parentName: 'AbpUiNavigation::Menu:Administration', - layout: eLayoutType.application, - iconClass: 'fa fa-users', - children: [ - { - path: 'tenants', - name: 'AbpTenantManagement::Tenants', - order: 1, - requiredPolicy: 'AbpTenantManagement.Tenants', - }, - ], - }); + addAbpRoutes([ + { + name: eTenantManagementRouteNames.Administration, + path: '', + order: 1, + wrapper: true, + iconClass: 'fa fa-wrench', + }, + { + name: eTenantManagementRouteNames.TenantManagement, + path: 'tenant-management', + parentName: eTenantManagementRouteNames.Administration, + layout: eLayoutType.application, + iconClass: 'fa fa-users', + children: [ + { + path: 'tenants', + name: eTenantManagementRouteNames.Tenants, + order: 1, + requiredPolicy: 'AbpTenantManagement.Tenants', + }, + ], + }, + ]); } } diff --git a/npm/ng-packs/packages/tenant-management/src/lib/enums/index.ts b/npm/ng-packs/packages/tenant-management/src/lib/enums/index.ts new file mode 100644 index 0000000000..5ac9b6e8fa --- /dev/null +++ b/npm/ng-packs/packages/tenant-management/src/lib/enums/index.ts @@ -0,0 +1,2 @@ +export * from './components'; +export * from './route-names'; diff --git a/npm/ng-packs/packages/tenant-management/src/lib/enums/route-names.ts b/npm/ng-packs/packages/tenant-management/src/lib/enums/route-names.ts new file mode 100644 index 0000000000..8125e28a4f --- /dev/null +++ b/npm/ng-packs/packages/tenant-management/src/lib/enums/route-names.ts @@ -0,0 +1,5 @@ +export const enum eTenantManagementRouteNames { + Administration = 'AbpUiNavigation::Menu:Administration', + TenantManagement = 'AbpTenantManagement::Menu:TenantManagement', + Tenants = 'AbpTenantManagement::Tenants', +} diff --git a/npm/ng-packs/packages/tenant-management/src/public-api.ts b/npm/ng-packs/packages/tenant-management/src/public-api.ts index 003074b2c4..3fc7cf90d5 100644 --- a/npm/ng-packs/packages/tenant-management/src/public-api.ts +++ b/npm/ng-packs/packages/tenant-management/src/public-api.ts @@ -1,7 +1,7 @@ export * from './lib/tenant-management.module'; export * from './lib/actions'; export * from './lib/components'; -export * from './lib/enums/components'; +export * from './lib/enums'; export * from './lib/models'; export * from './lib/services'; export * from './lib/states'; diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.html b/npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.html index 2687c2cf81..438c315c16 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.html +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/application-layout/application-layout.component.html @@ -5,7 +5,13 @@ >
- +
-

- {{ data.title | abpLocalization: data.options?.titleLocalizationParams }} -

-

- {{ data.message | abpLocalization: data.options?.messageLocalizationParams }} -

+

+

diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts index e43ff885f9..e70b88061f 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts @@ -43,6 +43,25 @@ describe('ConfirmationService', () => { expect(selectConfirmationContent('.message')).toBe('MESSAGE'); })); + test('should display HTML string in title, message, and buttons', fakeAsync(() => { + service.show( + 'MESSAGE', + 'TITLE', + 'neutral', + { + cancelText: 'CANCEL', + yesText: 'YES', + }, + ); + + tick(); + + expect(selectConfirmationContent('.custom-title')).toBe('TITLE'); + expect(selectConfirmationContent('.custom-message')).toBe('MESSAGE'); + expect(selectConfirmationContent('.custom-cancel')).toBe('CANCEL'); + expect(selectConfirmationContent('.custom-yes')).toBe('YES'); + })); + test.each` type | selector | icon ${'info'} | ${'.info'} | ${'.fa-info-circle'} diff --git a/npm/ng-packs/packages/theme-shared/src/public-api.ts b/npm/ng-packs/packages/theme-shared/src/public-api.ts index 436f687044..9df78be2d9 100644 --- a/npm/ng-packs/packages/theme-shared/src/public-api.ts +++ b/npm/ng-packs/packages/theme-shared/src/public-api.ts @@ -6,6 +6,7 @@ export * from './lib/theme-shared.module'; export * from './lib/animations'; export * from './lib/components'; export * from './lib/directives'; +export * from './lib/handlers'; export * from './lib/models'; export * from './lib/services'; export * from './lib/tokens'; diff --git a/npm/ng-packs/scripts/build.ts b/npm/ng-packs/scripts/build.ts index 049b044033..63b2cb593d 100644 --- a/npm/ng-packs/scripts/build.ts +++ b/npm/ng-packs/scripts/build.ts @@ -32,8 +32,9 @@ import program from 'commander'; 'copy', '--angular', '--no-watch', - '--packages', - '@abp/ng.feature-management,@abp/ng.permission-management,@abp/ng.account.config,@abp/ng.identity.config,@abp/ng.setting-management.config,@abp/ng.tenant-management.config', + '--all-packages', + '--excluded-packages', + '@abp/ng.core,@abp/ng.theme.shared,@abp/ng.feature-management,@abp/ng.permission-management,@abp/ng.account.config,@abp/ng.identity.config,@abp/ng.setting-management.config,@abp/ng.tenant-management.config', ], { stdout: 'inherit', cwd: '../' }, ); @@ -45,9 +46,8 @@ import program from 'commander'; 'copy', '--angular', '--no-watch', - '--all-packages', - '--excluded-packages', - '@abp/ng.core,@abp/ng.theme.shared,@abp/ng.feature-management,@abp/ng.permission-management,@abp/ng.account.config,@abp/ng.identity.config,@abp/ng.setting-management.config,@abp/ng.tenant-management.config', + '--packages', + '@abp/ng.feature-management,@abp/ng.permission-management,@abp/ng.account.config,@abp/ng.identity.config,@abp/ng.setting-management.config,@abp/ng.tenant-management.config', ], { stdout: 'inherit', cwd: '../' }, ); diff --git a/npm/ng-packs/scripts/install-new-dependencies.ts b/npm/ng-packs/scripts/install-new-dependencies.ts index d26d72f88b..3528396fb9 100644 --- a/npm/ng-packs/scripts/install-new-dependencies.ts +++ b/npm/ng-packs/scripts/install-new-dependencies.ts @@ -17,7 +17,7 @@ const updateAndInstall = async () => { ...packageJson.devDependencies, ...dependencies, ...peerDependencies, - [name]: `^${version}`, + [name]: `~${version}`, }; packageJson.devDependencies = Object.keys(packageJson.devDependencies) diff --git a/npm/ng-packs/scripts/prod-build.ts b/npm/ng-packs/scripts/prod-build.ts index a6bad4c8c9..03a1432b99 100644 --- a/npm/ng-packs/scripts/prod-build.ts +++ b/npm/ng-packs/scripts/prod-build.ts @@ -2,6 +2,11 @@ import execa from 'execa'; import fse from 'fs-extra'; (async () => { + await execa('yarn', ['ng', 'build', '--prod'], { + stdout: 'inherit', + cwd: '..', + }); + await execa('yarn', ['install', '--ignore-scripts'], { stdout: 'inherit', cwd: '../../../templates/app/angular', diff --git a/npm/ng-packs/yarn.lock b/npm/ng-packs/yarn.lock index d5101870d5..e8539d81be 100644 --- a/npm/ng-packs/yarn.lock +++ b/npm/ng-packs/yarn.lock @@ -2,26 +2,26 @@ # yarn lockfile v1 -"@abp/ng.account.config@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.account.config/-/ng.account.config-2.6.1.tgz#7df105f84d68e3266cec486d3fd15abc8beeaa4f" - integrity sha512-zN4DzB0XujdoqE3h8K1Rgd7TVuM+LlFwXcsb8DCorHiY91c+hKDd97jSaLB5nemFOiaZZgfT0/9FYafbftbpUw== +"@abp/ng.account.config@^2.6.2", "@abp/ng.account.config@~2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.account.config/-/ng.account.config-2.6.2.tgz#1a3e935b7144525c62f5d08adcd06d2ee12f7c9e" + integrity sha512-dC0/H1dkeQqYbu/dNISQA7NLpdOughhZSxT/7zjq6cBP8gYDceW0wYT5Rh0qIAkYWh2jO4BMXIFqzlH31Jg/XA== dependencies: tslib "^1.9.0" -"@abp/ng.account@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.account/-/ng.account-2.6.1.tgz#aba9c461ab178de22a66aa36ec9ba48d6641d5f4" - integrity sha512-GQWGF8Bhcy98rzug10sdyKjmp+UIp8WVqG/urWa8gYukbRtZZIvQVnaJIJXDyE8qHVOU3SBybjasOKIKe3qLsQ== +"@abp/ng.account@~2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.account/-/ng.account-2.6.2.tgz#aac921c49bcd8cdec506bd27d2546193416ea187" + integrity sha512-s3gLSqNdCW6D+DvuML+aGFT8braCfSXUbxvJ1WRCovd2Ec0jNxPBKj84zgYPYEb3smD294DcXxNx36jtJK+1OA== dependencies: - "@abp/ng.account.config" "^2.6.1" - "@abp/ng.theme.shared" "^2.6.1" + "@abp/ng.account.config" "^2.6.2" + "@abp/ng.theme.shared" "^2.6.2" tslib "^1.9.0" -"@abp/ng.core@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.core/-/ng.core-2.6.1.tgz#7a788e414403b3872eda3b90d7ee0f30d4e86f98" - integrity sha512-8CQJotkaqaDtqxDBitH+bX1ANlvv7PkwiBINrN/H4CyxZqx6oVxKhFqJ6nlDKijnogGTYMIhegHPrpVZh4O6pg== +"@abp/ng.core@^2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.core/-/ng.core-2.6.2.tgz#d0e4f6666a9a207fc7425001449bea2cdfe93522" + integrity sha512-e7c82d9VF20d4JFUkyzT5iTqGKnkq5FBHdv6IW6lsVyuz2Zf/p4wNtKATRr2ntM7qNIjIQebtGNFyxxX4nmL+w== dependencies: "@abp/utils" "^2.6.0" "@angular/localize" "~9.1.0" @@ -35,86 +35,86 @@ ts-toolbelt "^6.3.6" tslib "^1.9.0" -"@abp/ng.feature-management@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.feature-management/-/ng.feature-management-2.6.1.tgz#e81c9e55360533f8356b165e8548f3cf7e9ecab0" - integrity sha512-t8ugSLiAt/2fLo4OVnEMbk6jST/RZ+b6KysMnajKcxK/TAQG/Mj3FC9K52FdnjJg9s+MoJowR05YebAHhEnXig== +"@abp/ng.feature-management@^2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.feature-management/-/ng.feature-management-2.6.2.tgz#da09b07ebe81f09ccf338a7c10c384920375567a" + integrity sha512-JdHuAZk9j9AR1iKU02Xoux40ULOccm+68q7/4L2G1cBET6yTAE8Ua9RPPX9HadmCG0cR8awl3d+5JPwyL3OjFA== dependencies: - "@abp/ng.theme.shared" "^2.6.1" + "@abp/ng.theme.shared" "^2.6.2" tslib "^1.9.0" -"@abp/ng.identity.config@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.identity.config/-/ng.identity.config-2.6.1.tgz#cbf56d00a3633268b11fc125fcc711637b86770b" - integrity sha512-7dyEhjutbN2Zw7W1fKcldaZppmrP08kmKQAR6XHJhijcCJPsasVJ3F0babrG8SdRnZqoZVZ/5X21IfDuShc2/g== +"@abp/ng.identity.config@^2.6.2", "@abp/ng.identity.config@~2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.identity.config/-/ng.identity.config-2.6.2.tgz#79384ae4b11e53e8bf9dda25d3400def05c5beb8" + integrity sha512-jCqt2ZWEkRA9foJjHwRJtw5JNy03OVge9dUj0eAJ7Vo13IrKAT1kCNlD7cRM9xJ42Ga2utEqqz9FCJHgR2IxbQ== dependencies: tslib "^1.9.0" -"@abp/ng.identity@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.identity/-/ng.identity-2.6.1.tgz#a67c5c3daae08ba8582494c869d384acbeb086f3" - integrity sha512-PsWjjxd29es8tHA8Zkb54HX9zj91Pv9fkWR5HQsUmv3mcBv0QyGEnCC/g6PJEQXNxpxdgG/aWxwrjDh59d8pAg== +"@abp/ng.identity@~2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.identity/-/ng.identity-2.6.2.tgz#14f83305415d27b67d01405e582978682cfd7f22" + integrity sha512-wzR/QFoSSRhIxfH91QsTSCc0rBsFfsg8aiP5xPheNx3/lZygCnMogH3blRgLiNIoyxuG00MTfp+e9lFIBfDnbQ== dependencies: - "@abp/ng.identity.config" "^2.6.1" - "@abp/ng.permission-management" "^2.6.1" - "@abp/ng.theme.shared" "^2.6.1" + "@abp/ng.identity.config" "^2.6.2" + "@abp/ng.permission-management" "^2.6.2" + "@abp/ng.theme.shared" "^2.6.2" tslib "^1.9.0" -"@abp/ng.permission-management@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.permission-management/-/ng.permission-management-2.6.1.tgz#b2ea11ea2c3c3b9671def5164cf4879ce9adbcdd" - integrity sha512-Rm65jyxZQ/9qhze2mT8RuIfe8VA932ydeqivfykax5QPw7c6edHaRgvgdghufKSt5fS8twB2mNHzD6o3lsEXzA== +"@abp/ng.permission-management@^2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.permission-management/-/ng.permission-management-2.6.2.tgz#ba7902256acf3e1ff49552983e4d292f670f9ad8" + integrity sha512-6NW5BF+5ZFQI8oLEQWCiJS5iF2v3gHLvohrbN84RsPNTc+ZRDte6MbIOGq2CWxM8qXwisLBiR/RWC4ssgfPqEA== dependencies: - "@abp/ng.theme.shared" "^2.6.1" + "@abp/ng.theme.shared" "^2.6.2" tslib "^1.9.0" -"@abp/ng.setting-management.config@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.setting-management.config/-/ng.setting-management.config-2.6.1.tgz#85a015399628f560c6759fb48994fd2b9613e073" - integrity sha512-Jl3uE78G42mJhJ1c94En/dHnR8YOgVu93Z8NwvcW2L58TSmtogcS+7/+pG9fgnqnYovIeoMSYQfSRqYrKtJliw== +"@abp/ng.setting-management.config@^2.6.2", "@abp/ng.setting-management.config@~2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.setting-management.config/-/ng.setting-management.config-2.6.2.tgz#43e9e4399d65efc1aee6e314469c303ce22145e9" + integrity sha512-3F0vq5nvBTat2q7S0iBfdEsyPuZHJuL3+9z4Voq4Q9rA2T2VIAUMp9aBsfI2s2ycwGbsb7kCOWTKo/2by1b4AA== dependencies: tslib "^1.9.0" -"@abp/ng.setting-management@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.setting-management/-/ng.setting-management-2.6.1.tgz#2a266de071a5a9c32230fbf8696057430ba6d2bc" - integrity sha512-K+qg6JlX0FT6LDEAB4QsBrnT/Rsla2K8NFKdT3FkwfJtSbysqehTIUoO6m7hfuQzLOUd3XVDSOpaJ+Tqz7k1Bw== +"@abp/ng.setting-management@~2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.setting-management/-/ng.setting-management-2.6.2.tgz#f49d361ed8412380fdf2173f28b890115748a52b" + integrity sha512-0VdGa+t8lEZRsUdKL1EHoZei+ZCDMTu4n3UvXvD0OzRJyYPUjHRrMSSOYifuN4gPatluIe0E2I+tO3lQ3/2tjw== dependencies: - "@abp/ng.setting-management.config" "^2.6.1" - "@abp/ng.theme.shared" "^2.6.1" + "@abp/ng.setting-management.config" "^2.6.2" + "@abp/ng.theme.shared" "^2.6.2" tslib "^1.9.0" -"@abp/ng.tenant-management.config@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management.config/-/ng.tenant-management.config-2.6.1.tgz#3f484df5d59e210851785ec5394f45d0e7e25d73" - integrity sha512-HG8YrQu8TJU4TxoImCPpFIajquiD7OGICcYTXpWl9K6NdvRyv/GaOZDcE0j6O7zGOpSpCHg4mhSgKEQ+pUSZnw== +"@abp/ng.tenant-management.config@^2.6.2", "@abp/ng.tenant-management.config@~2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management.config/-/ng.tenant-management.config-2.6.2.tgz#9a9365ad59bdd2053d7bd33fb623ce633de40b24" + integrity sha512-iZ49Els+J9lbRKTSeNU/KIJuJS7Le3dAk1pdqLg/g5iw7e6uTEih1qeDvJdcXpSKfAZJrOd/KKDjFHIzVrveuA== dependencies: tslib "^1.9.0" -"@abp/ng.tenant-management@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management/-/ng.tenant-management-2.6.1.tgz#7bb60383c05f3de76b99a4274c0f9019d5cca9f5" - integrity sha512-TM6HUS9drqxG7KaLrM6+9kYNnDmX5f/tlk4ReepvU3OzisCC87PQeVAhmrUn0DKy/RTIKKIl3ORzlxN0yyIgLg== +"@abp/ng.tenant-management@~2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.tenant-management/-/ng.tenant-management-2.6.2.tgz#6be284fe6691946e2e6766e34ae958d7d2ec5e3a" + integrity sha512-BGARok01Eqn1kEltOwjwIpMJ8LD0jtYfbSbnO8V9TRg7f4ZEqFmfXudOIluDIwHjSEkNXk+Cs0/iV53lyi5+lg== dependencies: - "@abp/ng.feature-management" "^2.6.1" - "@abp/ng.tenant-management.config" "^2.6.1" - "@abp/ng.theme.shared" "^2.6.1" + "@abp/ng.feature-management" "^2.6.2" + "@abp/ng.tenant-management.config" "^2.6.2" + "@abp/ng.theme.shared" "^2.6.2" tslib "^1.9.0" -"@abp/ng.theme.basic@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.theme.basic/-/ng.theme.basic-2.6.1.tgz#9156bb47536d2a5191542b2d410d4297eaa3004d" - integrity sha512-LWA4ZmhNvVwFQDmvy8JG1+qOUO/7ECM5Atb04AMzoZ1OOXJjL1D0ut3KG1xp6S8FxZ5CVFSVsMj4/cxL1mZh/A== +"@abp/ng.theme.basic@~2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.theme.basic/-/ng.theme.basic-2.6.2.tgz#4b0414411e164885b8c467b04d635a0a3c81bd01" + integrity sha512-qgA4hh7ipIWY88JOqjChjoJgFB0csqXbwdb/pabrsFoHYuiFVT3cqvHq6jMfKQHEPBJSEnl6/GUIm+26DQ5eDw== dependencies: - "@abp/ng.theme.shared" "^2.6.1" + "@abp/ng.theme.shared" "^2.6.2" tslib "^1.9.0" -"@abp/ng.theme.shared@^2.6.1": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@abp/ng.theme.shared/-/ng.theme.shared-2.6.1.tgz#34b0738623221e26b32b8491ea4dc99bb9f875e8" - integrity sha512-V8xwqIjwhDp3szIeGDUK16gVaV2XPQUkPOFdhEN9fzTl0yuLoBByYjPuTI3Slb3C220pwcZsc5l+8rCsnKMUDA== +"@abp/ng.theme.shared@^2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/ng.theme.shared/-/ng.theme.shared-2.6.2.tgz#9d70b7aab010af4cd5a451c9c2d162d73ec5f9cc" + integrity sha512-e3QVsPpmbPGmTpQY9J1RgmWer7Tx7mrsOtxjFvbJGqKVF1UUtU5Kc3VyGPb3JsjQtT+890/9qmAf/YSj3cNPIA== dependencies: - "@abp/ng.core" "^2.6.1" + "@abp/ng.core" "^2.6.2" "@fortawesome/fontawesome-free" "^5.12.1" "@ng-bootstrap/ng-bootstrap" "^5.3.0" "@ngx-validate/core" "^0.0.7" @@ -123,9 +123,9 @@ tslib "^1.9.0" "@abp/utils@^2.6.0": - version "2.6.0" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-2.6.0.tgz#ca174b19f74594a0ae8d64f56b032f07b9168ed1" - integrity sha512-ABQUgFHbv8ktGR+tKx/1s+5a9JHuBwUVAy/Hn8OjbEDoSK73UHoapfFSShF/x12dWDKLcpvErmeiASiXb+soCw== + version "2.6.2" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-2.6.2.tgz#dba9e7c0aa1531fae6c5ff22fcbdfb4d98c576e3" + integrity sha512-MqExfPA4FRYORsskVG7kbkAwzs9z4XaMaZ75XkoahD+FyZtm1e0miEEoDvwrdye9jgL/FrWeShWNE49GY7PvpA== dependencies: just-compare "^1.3.0" @@ -356,9 +356,9 @@ integrity sha512-7EhN9JJbAJcH2xCa+rIOmekjiEuB0qwPdHuD5qn/wwMfRzMZo+Db4hHbR9KHrLH6H82PTwYKye/LLpDaZqoHOA== "@angular/localize@~9.1.0": - version "9.1.1" - resolved "https://registry.yarnpkg.com/@angular/localize/-/localize-9.1.1.tgz#267d7cecb288b3019482744330da0ef9e049c85d" - integrity sha512-5mYdSL7IwqFXWRgBXj9c5vHT15AQy2kjD40fJJsmUx4WUFr+uf0Ss9ADCfL2FjCHpATrjYKpiyJs1mAF60USPQ== + version "9.1.3" + resolved "https://registry.yarnpkg.com/@angular/localize/-/localize-9.1.3.tgz#d03d9cb1b4fe10dabaf806062d23ee1b7774945d" + integrity sha512-aV7AFgvEWm6aaEjS45EPJ1U4i2DOVR6DswUu9CsYkRfy/gSOxWE95SyrhLdg2hB64Bu4mcXxiV1So2BUI7NzBA== dependencies: "@babel/core" "7.8.3" glob "7.1.2" @@ -2101,9 +2101,9 @@ glob-to-regexp "^0.3.0" "@ng-bootstrap/ng-bootstrap@^5.3.0": - version "5.3.0" - resolved "https://registry.yarnpkg.com/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-5.3.0.tgz#4d624dd70a93f99c38bc35b1d3e00afc490155fc" - integrity sha512-oNg1sgT+XXxwSBX9oF4AeCHrq+Nm1CvukfVT/NPuYiuNAoTeVOdXc5bJHTLd+cCwoZ1OPZN/yZT+1BcW1KCksQ== + version "5.3.1" + resolved "https://registry.yarnpkg.com/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-5.3.1.tgz#4cf2929583e9572475d1d0f3c905b05885a086bb" + integrity sha512-xlhBJb4oNcOQk04h5sQcq9P1E97sGB1HjqBCqgL0+S2w2uvLWME9F9SuH7wU4S1+eYe7WG9SKFpq+R4BjG2kMw== dependencies: tslib "^1.9.0" @@ -2183,12 +2183,12 @@ dependencies: "@octokit/types" "^2.0.0" -"@octokit/endpoint@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.0.tgz#4c7acd79ab72df78732a7d63b09be53ec5a2230b" - integrity sha512-3nx+MEYoZeD0uJ+7F/gvELLvQJzLXhep2Az0bBSXagbApDvDW0LWwpnAIY/hb0Jwe17A0fJdz0O12dPh05cj7A== +"@octokit/endpoint@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.1.tgz#16d5c0e7a83e3a644d1ddbe8cded6c3d038d31d7" + integrity sha512-pOPHaSz57SFT/m3R5P8MUu4wLPszokn5pXcB/pzavLTQf2jbU+6iayTvzaY6/BiotuRS0qyEUkx3QglT4U958A== dependencies: - "@octokit/types" "^2.0.0" + "@octokit/types" "^2.11.1" is-plain-object "^3.0.0" universal-user-agent "^5.0.0" @@ -2236,13 +2236,13 @@ once "^1.4.0" "@octokit/request@^5.2.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.0.tgz#52382c830f0cf3295b5a03e308872ac0f5ccba9b" - integrity sha512-uAJO6GI8z8VHBqtY7VTL9iFy1Y+UTp5ShpI97tY5z0qBfYKE9rZCRsCm23VmF00x+IoNJ7a0nuVITs/+wS9/mg== + version "5.4.2" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.2.tgz#74f8e5bbd39dc738a1b127629791f8ad1b3193ee" + integrity sha512-zKdnGuQ2TQ2vFk9VU8awFT4+EYf92Z/v3OlzRaSh4RIP0H6cvW1BFPXq4XYvNez+TPQjqN+0uSkCYnMFFhcFrw== dependencies: - "@octokit/endpoint" "^6.0.0" + "@octokit/endpoint" "^6.0.1" "@octokit/request-error" "^2.0.0" - "@octokit/types" "^2.8.2" + "@octokit/types" "^2.11.1" deprecation "^2.0.0" is-plain-object "^3.0.0" node-fetch "^2.3.0" @@ -2271,10 +2271,10 @@ once "^1.4.0" universal-user-agent "^4.0.0" -"@octokit/types@^2.0.0", "@octokit/types@^2.0.1", "@octokit/types@^2.8.2": - version "2.9.0" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-2.9.0.tgz#b3b69cddab629dd31a559ffb25c9f471301d7538" - integrity sha512-IzptUpoDsFlXF+AOys+KnfItIVY3EK+eH9Akv+lJYELnMSGkJnIcClt6Cm0QRR4ecsUTsmFJWn10iFgJ9BQqIQ== +"@octokit/types@^2.0.0", "@octokit/types@^2.0.1", "@octokit/types@^2.11.1": + version "2.12.1" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-2.12.1.tgz#4a26b4a85ec121043d3b0745b5798f9d8fd968ca" + integrity sha512-LRLR1tjbcCfAmUElvTmMvLEzstpx6Xt/aQVTg2xvd+kHA2Ekp1eWl5t+gU7bcwjXHYEAzh4hH4WH+kS3vh+wRw== dependencies: "@types/node" ">= 8" @@ -2429,9 +2429,9 @@ integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== "@types/node@*", "@types/node@>= 8": - version "13.11.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.1.tgz#49a2a83df9d26daacead30d0ccc8762b128d53c7" - integrity sha512-eWQGP3qtxwL8FGneRrC5DwrJLGN4/dH1clNTuLfN81HCrxVtxRjygDTUoZJ5ASlDEeo0ppYFQjQIlXhtXpOn6g== + version "13.13.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.4.tgz#1581d6c16e3d4803eb079c87d4ac893ee7501c2c" + integrity sha512-x26ur3dSXgv5AwKS0lNfbjpCakGIduWU1DU91Zz58ONRWrIKGunmZBNv4P7N+e27sJkiGDsw/3fT4AtsqQBrBA== "@types/node@^8.0.31": version "8.10.60" @@ -2789,9 +2789,9 @@ ajv@6.9.1: uri-js "^4.2.2" ajv@^6.1.0, ajv@^6.10.2, ajv@^6.12.0, ajv@^6.5.5: - version "6.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7" - integrity sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw== + version "6.12.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" + integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ== dependencies: fast-deep-equal "^3.1.1" fast-json-stable-stringify "^2.0.0" @@ -3154,9 +3154,9 @@ babel-jest@^24.9.0: slash "^2.0.0" babel-plugin-dynamic-import-node@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f" - integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ== + version "2.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" + integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== dependencies: object.assign "^4.1.0" @@ -3437,12 +3437,12 @@ browserslist@4.10.0: pkg-up "^3.1.0" browserslist@^4.0.0, browserslist@^4.11.1, browserslist@^4.6.3, browserslist@^4.8.5, browserslist@^4.9.1: - version "4.11.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.11.1.tgz#92f855ee88d6e050e7e7311d987992014f1a1f1b" - integrity sha512-DCTr3kDrKEYNw6Jb9HFxVLQNaue8z+0ZfRBRjmCunKDEXEBajKDj2Y+Uelg+Pi29OnvaSGwjOsnRyNEkXzHg5g== + version "4.12.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.0.tgz#06c6d5715a1ede6c51fc39ff67fd647f740b656d" + integrity sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg== dependencies: - caniuse-lite "^1.0.30001038" - electron-to-chromium "^1.3.390" + caniuse-lite "^1.0.30001043" + electron-to-chromium "^1.3.413" node-releases "^1.1.53" pkg-up "^2.0.0" @@ -3677,10 +3677,10 @@ caniuse-lite@1.0.30001035: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001035.tgz#2bb53b8aa4716b2ed08e088d4dc816a5fe089a1e" integrity sha512-C1ZxgkuA4/bUEdMbU5WrGY4+UhMFFiXrgNAfxiMIqWgFTWfv/xsZCS2xEHT2LMq7xAZfuAnu6mcqyDl0ZR6wLQ== -caniuse-lite@^1.0.30000980, caniuse-lite@^1.0.30001035, caniuse-lite@^1.0.30001038, caniuse-lite@^1.0.30001039: - version "1.0.30001042" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001042.tgz#c91ec21ec2d270bd76dbc2ce261260c292b8c93c" - integrity sha512-igMQ4dlqnf4tWv0xjaaE02op9AJ2oQzXKjWf4EuAHFN694Uo9/EfPVIPJcmn2WkU9RqozCxx5e2KPcVClHDbDw== +caniuse-lite@^1.0.30000980, caniuse-lite@^1.0.30001035, caniuse-lite@^1.0.30001039, caniuse-lite@^1.0.30001043: + version "1.0.30001048" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001048.tgz#4bb4f1bc2eb304e5e1154da80b93dee3f1cf447e" + integrity sha512-g1iSHKVxornw0K8LG9LLdf+Fxnv7T1Z+mMsf0/YYLclQX4Cd522Ap0Lrw6NFqHgezit78dtyWxzlV2Xfc7vgRg== canonical-path@1.0.0: version "1.0.0" @@ -3768,9 +3768,9 @@ chokidar@2.0.4: fsevents "^1.2.2" "chokidar@>=2.0.0 <4.0.0", chokidar@^3.0.0, chokidar@^3.0.2: - version "3.3.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450" - integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg== + version "3.4.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.0.tgz#b30611423ce376357c765b9b8f904b9fba3c0be8" + integrity sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ== dependencies: anymatch "~3.1.1" braces "~3.0.2" @@ -3778,7 +3778,7 @@ chokidar@2.0.4: is-binary-path "~2.1.0" is-glob "~4.0.1" normalize-path "~3.0.0" - readdirp "~3.3.0" + readdirp "~3.4.0" optionalDependencies: fsevents "~2.1.2" @@ -5023,10 +5023,10 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-to-chromium@^1.3.378, electron-to-chromium@^1.3.390: - version "1.3.407" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.407.tgz#361dbfc92a3d1b0b097b77221256271967e04c0e" - integrity sha512-ayDQgkwCLHq0AVmsU10t2H/8X9lq3nb1CRV9rWYsc2nTTbFMvh4Aaau48Ao0iW/L+Q8Y9bgbJ5iHyPse7DGMJw== +electron-to-chromium@^1.3.378, electron-to-chromium@^1.3.413: + version "1.3.418" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.418.tgz#840021191f466b803a873e154113620c9f53cec6" + integrity sha512-i2QrQtHes5fK/F9QGG5XacM5WKEuR322fxTYF9e8O9Gu0mc0WmjjwGpV8c7Htso6Zf2Di18lc3SIPxmMeRFBug== elliptic@^6.0.0: version "6.5.2" @@ -5104,9 +5104,9 @@ env-paths@^2.2.0: integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA== envinfo@^7.3.1: - version "7.5.0" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.5.0.tgz#91410bb6db262fb4f1409bd506e9ff57e91023f4" - integrity sha512-jDgnJaF/Btomk+m3PZDTTCb5XIIIX3zYItnCRfF73zVgvinLoRomuhi75Y4su0PtQxWz4v66XnLLckyvyJTOIQ== + version "7.5.1" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.5.1.tgz#93c26897225a00457c75e734d354ea9106a72236" + integrity sha512-hQBkDf2iO4Nv0CNHpCuSBeaSrveU6nThVxFGTrq/eDlV716UQk09zChaJae4mZRsos1x4YLY2TaH3LHUae3ZmQ== err-code@^1.0.0: version "1.1.2" @@ -5503,9 +5503,9 @@ figgy-pudding@^3.4.1, figgy-pudding@^3.5.1: integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== figlet@^1.2.3: - version "1.3.0" - resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.3.0.tgz#c49e3d92907ba13bebadc7124f76ba71f1f32ef0" - integrity sha512-f7A8aOJAfyehLJ7lQ6rEA8WJw7kOk3lfWRi5piSjkzbK5YkI5sqO8eiLHz1ehO+DM0QYB85i8VfA6XIGUbU1dg== + version "1.4.0" + resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.4.0.tgz#21c5878b3752a932ebdb8be400e2d10bbcddfd60" + integrity sha512-CxxIjEKHlqGosgXaIA+sikGDdV6KZOOlzPJnYuPgQlOSHZP5h9WIghYI30fyXnwEVeSH7Hedy72gC6zJrFC+SQ== figures@^2.0.0: version "2.0.0" @@ -5744,9 +5744,9 @@ fsevents@^1.2.2, fsevents@^1.2.7: nan "^2.12.1" fsevents@~2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" - integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== + version "2.1.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" + integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== function-bind@^1.1.1: version "1.1.1" @@ -6861,11 +6861,6 @@ is-plain-object@^3.0.0: dependencies: isobject "^4.0.0" -is-promise@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" - integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= - is-reference@^1.1.2: version "1.1.4" resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.1.4.tgz#3f95849886ddb70256a3e6d062b1a68c13c51427" @@ -7612,9 +7607,9 @@ jsrsasign@^8.0.12: integrity sha512-6UKHqnNs5lYROn03wf1BTw7DQx5tW616DTigjbo0JHV97D3HzIqYmPVCBSNsfEfQOrfpFqmPZJvaC3cMNOT0Yw== jszip@^3.1.3: - version "3.3.0" - resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.3.0.tgz#29d72c21a54990fa885b11fc843db320640d5271" - integrity sha512-EJ9k766htB1ZWnsV5ZMDkKLgA+201r/ouFF8R2OigVjVdcm2rurcBrrdXaeqBJbqnUVMko512PYmlncBKE1Huw== + version "3.4.0" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.4.0.tgz#1a69421fa5f0bb9bc222a46bca88182fba075350" + integrity sha512-gZAOYuPl4EhPTXT0GjhI3o+ZAz3su6EhLrKUoAivcKqyqC7laS5JEv4XWZND9BgcDcF83vI85yGbDmDR6UhrIg== dependencies: lie "~3.3.0" pako "~1.0.2" @@ -8052,9 +8047,9 @@ make-dir@^2.0.0, make-dir@^2.1.0: semver "^5.6.0" make-dir@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.0.2.tgz#04a1acbf22221e1d6ef43559f43e05a90dbb4392" - integrity sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w== + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== dependencies: semver "^6.0.0" @@ -8265,17 +8260,17 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@1.43.0, "mime-db@>= 1.43.0 < 2": - version "1.43.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" - integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== +mime-db@1.44.0, "mime-db@>= 1.43.0 < 2": + version "1.44.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" + integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: - version "2.1.26" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" - integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== + version "2.1.27" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" + integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== dependencies: - mime-db "1.43.0" + mime-db "1.44.0" mime@1.6.0, mime@^1.4.1: version "1.6.0" @@ -8347,11 +8342,6 @@ minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minimist@~0.0.1: - version "0.0.10" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" - integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= - minipass@^2.3.5, minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" @@ -8490,9 +8480,9 @@ mz@^2.5.0: thenify-all "^1.0.0" nan@^2.12.1: - version "2.14.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" - integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== + version "2.14.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" + integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== nanomatch@^1.2.9: version "1.2.13" @@ -8559,7 +8549,7 @@ ng-packagr@^5.7.1: terser "^4.1.2" update-notifier "^3.0.0" -ngxs-reset-plugin@^1.2.0: +ngxs-reset-plugin@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/ngxs-reset-plugin/-/ngxs-reset-plugin-1.3.0.tgz#370b89190e729de94f0116b99ff65389582a7626" integrity sha512-gxAUuka1ewSs04M+cFaUu5NVMSWW9EHw0mZAxXbtagtbPyEwSRjgFvobb4nNyWVVBPsdAuBIfPCfkztZ6Dtj3w== @@ -9006,14 +8996,6 @@ opn@^5.5.0: dependencies: is-wsl "^1.1.0" -optimist@~0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" - integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY= - dependencies: - minimist "~0.0.1" - wordwrap "~0.0.2" - optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -9451,7 +9433,7 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -picomatch@^2.0.4, picomatch@^2.0.7: +picomatch@^2.0.4, picomatch@^2.2.1: version "2.2.2" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== @@ -9709,9 +9691,9 @@ protoduck@^5.0.1: genfun "^5.0.0" protractor@~5.4.0: - version "5.4.3" - resolved "https://registry.yarnpkg.com/protractor/-/protractor-5.4.3.tgz#35f050741e404a45868618ea648745d89af31683" - integrity sha512-7pMAolv8Ah1yJIqaorDTzACtn3gk7BamVKPTeO5lqIGOrfosjPgXFx/z1dqSI+m5EeZc2GMJHPr5DYlodujDNA== + version "5.4.4" + resolved "https://registry.yarnpkg.com/protractor/-/protractor-5.4.4.tgz#b241466aaf83b76bc2c58df67deb9a5cdfc61529" + integrity sha512-BaL4vePgu3Vfa/whvTUAlgaCAId4uNSGxIFSCXMgj7LMYENPWLp85h5RBi9pdpX/bWQ8SF6flP7afmi2TC4eHw== dependencies: "@types/q" "^0.0.32" "@types/selenium-webdriver" "^3.0.0" @@ -9721,13 +9703,13 @@ protractor@~5.4.0: glob "^7.0.3" jasmine "2.8.0" jasminewd2 "^2.1.0" - optimist "~0.6.0" q "1.4.1" saucelabs "^1.5.0" selenium-webdriver "3.6.0" source-map-support "~0.4.0" webdriver-js-extender "2.1.0" webdriver-manager "^12.0.6" + yargs "^12.0.5" proxy-addr@~2.0.5: version "2.0.6" @@ -10048,12 +10030,12 @@ readdirp@^2.0.0, readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" -readdirp@~3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.3.0.tgz#984458d13a1e42e2e9f5841b129e162f369aff17" - integrity sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ== +readdirp@~3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada" + integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ== dependencies: - picomatch "^2.0.7" + picomatch "^2.2.1" realpath-native@^1.1.0: version "1.1.0" @@ -10320,9 +10302,9 @@ resolve@1.1.7, resolve@~1.1.6: integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= resolve@1.x, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.11.0, resolve@^1.11.1, resolve@^1.3.2: - version "1.15.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" - integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== + version "1.17.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== dependencies: path-parse "^1.0.6" @@ -10457,11 +10439,9 @@ rsvp@^4.8.4: integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== run-async@^2.2.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.0.tgz#e59054a5b86876cfae07f431d18cbaddc594f1e8" - integrity sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg== - dependencies: - is-promise "^2.1.0" + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== run-queue@^1.0.0, run-queue@^1.0.3: version "1.0.3" @@ -10547,9 +10527,9 @@ sass@1.22.9: chokidar ">=2.0.0 <4.0.0" sass@^1.17.3: - version "1.26.3" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.26.3.tgz#412df54486143b76b5a65cdf7569e86f44659f46" - integrity sha512-5NMHI1+YFYw4sN3yfKjpLuV9B5l7MqQ6FlkTcC4FT+oHbBRUZoSjHrrt/mE0nFXJyY2kQtU9ou9HxvFVjLFuuw== + version "1.26.5" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.26.5.tgz#2d7aecfbbabfa298567c8f06615b6e24d2d68099" + integrity sha512-FG2swzaZUiX53YzZSjSakzvGtlds0lcbF+URuU9mxOv7WBh7NhXEVDa4kPKN4hN6fC2TkOTOKqiqp6d53N9X5Q== dependencies: chokidar ">=2.0.0 <4.0.0" @@ -10587,9 +10567,9 @@ schema-utils@^1.0.0: ajv-keywords "^3.1.0" schema-utils@^2.0.0, schema-utils@^2.0.1, schema-utils@^2.6.1: - version "2.6.5" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.6.5.tgz#c758f0a7e624263073d396e29cd40aa101152d8a" - integrity sha512-5KXuwKziQrTVHh8j/Uxz+QUbxkaLW9X/86NBlx/gnKgtsZA2GIVMUn17qWhRFwF8jdYb3Dig5hRO/W5mZqy6SQ== + version "2.6.6" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.6.6.tgz#299fe6bd4a3365dc23d99fd446caff8f1d6c330c" + integrity sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA== dependencies: ajv "^6.12.0" ajv-keywords "^3.4.1" @@ -10952,9 +10932,9 @@ source-map-support@0.5.13: source-map "^0.6.0" source-map-support@^0.5.5, source-map-support@^0.5.6, source-map-support@~0.5.12: - version "0.5.16" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" - integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -11014,9 +10994,9 @@ spdx-correct@^3.0.0: spdx-license-ids "^3.0.0" spdx-exceptions@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" - integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== spdx-expression-parse@^3.0.0: version "3.0.0" @@ -11529,9 +11509,9 @@ terser@4.6.3: source-map-support "~0.5.12" terser@^4.1.2: - version "4.6.11" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.11.tgz#12ff99fdd62a26de2a82f508515407eb6ccd8a9f" - integrity sha512-76Ynm7OXUG5xhOpblhytE7X58oeNSmC8xnNhjWVo8CksHit0U0kO4hfNbPrrYwowLWFgM2n9L176VNx2QaHmtA== + version "4.6.12" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.12.tgz#44b98aef8703fdb09a3491bf79b43faffc5b4fee" + integrity sha512-fnIwuaKjFPANG6MAixC/k1TDtnl1YlPLUlLVIxxGZUn1gfUx2+l3/zGNB72wya+lgsb50QBi2tUV75RiODwnww== dependencies: commander "^2.20.0" source-map "~0.6.1" @@ -11740,9 +11720,9 @@ ts-node@~7.0.0: yn "^2.0.0" ts-toolbelt@^6.3.6: - version "6.4.2" - resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-6.4.2.tgz#97df4fc577eccccfbe8929d82ab364d86ea99548" - integrity sha512-B27axztBC+3K4D1cuaRO3ygMcyR54LRpQxlt1zNIS9RnqDwrlDoVlIIazDqWxLuRaFzGWNvDLkjDgs3eeLEVig== + version "6.5.1" + resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-6.5.1.tgz#1316b8c6522797c0bee289ca5deaa159af846231" + integrity sha512-zjnZ/vy1eUA0li3H0JXecl0R3jiP42snpLimsrppt9V3LLbM4NM4jMgjXQ4S67hvehq+r9CxpX4Wj6RnFRzReA== tsickle@^0.37.0: version "0.37.1" @@ -11848,9 +11828,9 @@ typescript@^3.3.3333, typescript@^3.5.2: integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== uglify-js@^3.1.4: - version "3.9.0" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.9.0.tgz#037163a936992050ed5d14f5d5c4014126019661" - integrity sha512-j5wNQBWaql8gr06dOUrfaohHlscboQZ9B8sNsoK5o4sBjm7Ht9dxSbrMXyktQpA16Acaij8AcoozteaPYZON0g== + version "3.9.1" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.9.1.tgz#a56a71c8caa2d36b5556cc1fd57df01ae3491539" + integrity sha512-JUPoL1jHsc9fOjVFHdQIhqEEJsQvfKDjlubcCilu8U26uZ73qOg8VsN8O1jbuei44ZPlwL7kmbAdM4tzaUvqnA== dependencies: commander "~2.20.3" @@ -12399,11 +12379,6 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= -wordwrap@~0.0.2: - version "0.0.3" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" - integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= - worker-farm@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" @@ -12600,14 +12575,14 @@ yargs-parser@^15.0.1: decamelize "^1.2.0" yargs-parser@^18.1.0, yargs-parser@^18.1.1: - version "18.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.2.tgz#2f482bea2136dbde0861683abea7756d30b504f1" - integrity sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ== + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== dependencies: camelcase "^5.0.0" decamelize "^1.2.0" -yargs@12.0.5: +yargs@12.0.5, yargs@^12.0.5: version "12.0.5" resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13" integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw== diff --git a/nupkg/common.ps1 b/nupkg/common.ps1 index f9e1263908..9c77b05eef 100644 --- a/nupkg/common.ps1 +++ b/nupkg/common.ps1 @@ -101,6 +101,7 @@ $projects = ( "framework/src/Volo.Abp.Sms", "framework/src/Volo.Abp.Specifications", "framework/src/Volo.Abp.TestBase", + "framework/src/Volo.Abp.TextTemplating", "framework/src/Volo.Abp.Threading", "framework/src/Volo.Abp.Timing", "framework/src/Volo.Abp.UI", diff --git a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/pl.json b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/pl-PL.json similarity index 90% rename from samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/pl.json rename to samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/pl-PL.json index 9181e35186..e8629599a2 100644 --- a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/pl.json +++ b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Permission:ProductManagement": "Zarządzanie produktami", "Permission:Products": "Produkty", diff --git a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/pl-PL.json b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/pl-PL.json new file mode 100644 index 0000000000..3ea7b190ee --- /dev/null +++ b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/pl-PL.json @@ -0,0 +1,6 @@ +{ + "culture": "pl-PL", + "texts": { + + } +} \ No newline at end of file diff --git a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/pl.json b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/pl.json deleted file mode 100644 index 2ea227cbf3..0000000000 --- a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/pl.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "culture": "pl", - "texts": { - - } -} \ No newline at end of file diff --git a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/pl.json b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/pl-PL.json similarity index 95% rename from samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/pl.json rename to samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/pl-PL.json index 5828dd8942..929b543df8 100644 --- a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/pl.json +++ b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Menu:ProductManagement": "Zarządzanie produktami", "Menu:Products": "Produkty", diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json similarity index 90% rename from templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl.json rename to templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json index 815bbcc83f..33412f307c 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json @@ -1,5 +1,5 @@ { - "culture": "pl", + "culture": "pl-PL", "texts": { "Menu:Home": "Home", "Welcome": "Witaj", diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json new file mode 100644 index 0000000000..3ea7b190ee --- /dev/null +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl-PL.json @@ -0,0 +1,6 @@ +{ + "culture": "pl-PL", + "texts": { + + } +} \ No newline at end of file diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl.json b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl.json deleted file mode 100644 index 2ea227cbf3..0000000000 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/pl.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "culture": "pl", - "texts": { - - } -} \ No newline at end of file