diff --git a/.github/lock.yml b/.github/lock.yml new file mode 100644 index 0000000000..2e36a791b6 --- /dev/null +++ b/.github/lock.yml @@ -0,0 +1,38 @@ +# Configuration for Lock Threads - https://github.com/dessant/lock-threads-app + +# Number of days of inactivity before a closed issue or pull request is locked +daysUntilLock: 30 + +# Skip issues and pull requests created before a given timestamp. Timestamp must +# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable +skipCreatedBefore: false + +# Issues and pull requests with these labels will be ignored. Set to `[]` to disable +exemptLabels: [] + +# Label to add before locking, such as `outdated`. Set to `false` to disable +lockLabel: false + +# Comment to post before locking. Set to `false` to disable +lockComment: > + This thread has been automatically locked since there has not been + any recent activity after it was closed. Please open a new issue for + related bugs. + +# Assign `resolved` as the reason for locking. Set to `false` to disable +setLockReason: true + +# Limit to only `issues` or `pulls` +# only: issues + +# Optionally, specify configuration settings just for `issues` or `pulls` +# issues: +# exemptLabels: +# - help-wanted +# lockLabel: outdated + +# pulls: +# daysUntilLock: 30 + +# Repository to extend settings from +# _extends: repo \ No newline at end of file diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000000..c1133728da --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,15 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 60 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Set to true to ignore issues in a milestone (defaults to false) +exemptMilestones: true +# Label to use when marking an issue as stale +staleLabel: inactive +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/AbpIoLocalizationModule.cs b/abp_io/AbpIoLocalization/AbpIoLocalization/AbpIoLocalizationModule.cs index 37541b4d4c..b3ebccd72f 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/AbpIoLocalizationModule.cs +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/AbpIoLocalizationModule.cs @@ -3,6 +3,7 @@ using AbpIoLocalization.Admin.Localization; using AbpIoLocalization.Base.Localization; using AbpIoLocalization.Blog.Localization; using AbpIoLocalization.Commercial.Localization; +using AbpIoLocalization.Community.Localization; using AbpIoLocalization.Docs.Localization; using AbpIoLocalization.Support.Localization; using AbpIoLocalization.Www; @@ -29,6 +30,7 @@ namespace AbpIoLocalization { options.MapCodeNamespace("Volo.AbpIo.Commercial", typeof(AbpIoCommercialResource)); options.MapCodeNamespace("Volo.AbpIo.Domain", typeof(AbpIoBaseResource)); + options.MapCodeNamespace("Volo.AbpIo.Community", typeof(AbpIoCommunityResource)); }); Configure(options => @@ -74,6 +76,11 @@ namespace AbpIoLocalization .Add("en") .AddVirtualJson("/Www/Localization/Resources") .AddBaseTypes(typeof(AbpIoBaseResource)); + + options.Resources + .Add("en") + .AddVirtualJson("/Community/Localization/Resources") + .AddBaseTypes(typeof(AbpIoBaseResource)); }); } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/ar.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/ar.json new file mode 100644 index 0000000000..2f7a9b9142 --- /dev/null +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/ar.json @@ -0,0 +1,14 @@ +{ + "culture": "ar", + "texts": { + "Account": "حساب ABP - تسجيل الدخول وإنشاء حساب | ABP.IO", + "Welcome": "أهلا و سهلا", + "UseOneOfTheFollowingLinksToContinue": "استخدم أحد الروابط التالية للمتابعة", + "FrameworkHomePage": "الصفحة الرئيسية للنظام", + "FrameworkDocumentation": "وثائق النظام", + "OfficialBlog": "المدونة الرسمية", + "CommercialHomePage": "الصفحة الرئيسية للنظام التجاري", + "CommercialSupportWebSite": "موقع الدعم الفني للنظام التجاري", + "CommunityWebSite": "موقع مجتمع ABP" + } +} \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/ar.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/ar.json new file mode 100644 index 0000000000..96661aebeb --- /dev/null +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/ar.json @@ -0,0 +1,161 @@ +{ + "culture": "ar", + "texts": { + "Permission:Organizations": "المنظمات", + "Permission:Manage": "إدارة المنظمات", + "Permission:DiscountRequests": "طلبات الخصم", + "Permission:DiscountManage": "إدارة طلبات الخصم", + "Permission:Disable": "تعطيل", + "Permission:Enable": "تفعيل", + "Permission:EnableSendEmail": "تمكين إرسال البريد الإلكتروني", + "Permission:SendEmail": "ارسل بريد الكتروني", + "Permission:NpmPackages": "حزم NPM", + "Permission:NugetPackages": "حزم Nuget", + "Permission:Maintenance": "صيانة", + "Permission:Maintain": "المحافظة", + "Permission:ClearCaches": "مسح ذاكرة التخزين المؤقت", + "Permission:Modules": "الوحدات", + "Permission:Packages": "حزم", + "Permission:Edit": "تعديل", + "Permission:Delete": "حذف", + "Permission:Create": "إنشاء", + "Permission:Accounting": "محاسبة", + "Permission:Accounting:Quotation": "عرض سعر", + "Permission:Accounting:Invoice": "فاتورة", + "Menu:Organizations": "المنظمات", + "Menu:Accounting": "المحاسبة", + "Menu:Packages": "الحزم", + "Menu:DiscountRequests": "طلبات الخصم", + "NpmPackageDeletionWarningMessage": "سيتم حذف حزمة NPM هذه. هل تؤكد ذلك؟", + "NugetPackageDeletionWarningMessage": "سيتم حذف حزمة Nuget هذه. هل تؤكد ذلك؟", + "ModuleDeletionWarningMessage": "سيتم حذف هذه الوحدة. هل تؤكد ذلك؟", + "Name": "اسم", + "DisplayName": "اسم العرض", + "ShortDescription": "وصف قصير", + "NameFilter": "الاسم", + "CreationTime": "وقت الإنشاء", + "IsPro": "هو pro", + "ShowOnModuleList": "إظهار في قائمة الوحدات", + "EfCoreConfigureMethodName": "تكوين اسم method", + "IsProFilter": "هو pro", + "ApplicationType": "نوع التطبيق", + "Target": "استهداف", + "TargetFilter": "استهداف", + "Edit": "تعديل", + "Delete": "حذف", + "Refresh": "تحديث", + "NpmPackages": "حزم NPM", + "NugetPackages": "حزم Nuget", + "NpmPackageCount": "عدد حزم NPM", + "NugetPackageCount": "عدد حزم Nuget", + "Module": "الوحدات", + "ModuleInfo": "معلومات الوحدة", + "CreateANpmPackage": "إنشاء حزمة NPM", + "CreateAModule": "إنشاء وحدة", + "CreateANugetPackage": "أنشاء حزمة Nuget", + "AddNew": "اضف جديد", + "PackageAlreadyExist{0}": "", + "ModuleAlreadyExist{0}": "", + "ClearCache": "مسح ذاكرة التخزين المؤقت", + "SuccessfullyCleared": "تم محوه بنجاح", + "Menu:NpmPackages": "حزم NPM", + "Menu:Modules": "الوحدات", + "Menu:Maintenance": "الصيانة", + "Menu:NugetPackages": "حزم Nuget", + "CreateAnOrganization": "إنشاء منظمة", + "Organizations": "المنظمات", + "LongName": "اسم طويل", + "LicenseType": "نوع الرخصة", + "MissingLicenseTypeField": "حقل نوع الرخصة مطلوب!", + "LicenseStartTime": "وقت بدء الرخصة", + "LicenseEndTime": "وقت إنتهاء الرخصة", + "AllowedDeveloperCount": "عدد مطوري البرامج المسموح به", + "UserNameOrEmailAddress": "اسم المستخدم أو البريد الالكتروني", + "AddOwner": "أضف مالك", + "UserName": "اسم المستخدم", + "Email": "البريد الإلكتروني", + "Developers": "المطورين", + "AddDeveloper": "أضف مطور", + "Create": "إنشاء", + "UserNotFound": "لم نعثر على المستخدم", + "{0}WillBeRemovedFromDevelopers": "{0} ستتم إزالته من المطورين ، هل تؤكد ذلك؟", + "{0}WillBeRemovedFromOwners": "{0} ستتم إزالته من المالكين ، هل تؤكد ذلك؟", + "Computers": "أجهزة الكمبيوتر", + "UniqueComputerId": "معرف الكمبيوتر", + "LastSeenDate": "تاريخ آخر ظهور", + "{0}Computer{1}WillBeRemovedFromRecords": "ستتم إزالة كمبيوتر {0} ({1}) من السجلات", + "OrganizationDeletionWarningMessage": "سيتم حذف المنظمة", + "DeletingLastOwnerWarningMessage": "يجب أن يكون للمنظمة مالك واحد على الأقل! لذلك لا يمكنك إزالة هذا المالك", + "This{0}AlreadyExistInThisOrganization": "هذا {0} موجود بالفعل في هذه المنظمة", + "AreYouSureYouWantToDeleteAllComputers": "هل أنت متأكد أنك تريد حذف جميع أجهزة الكمبيوتر؟", + "DeleteAll": "حذف الكل", + "DoYouWantToCreateNewUser": "هل تريد إنشاء مستخدم جديد؟", + "MasterModules": "الوحدات الرئيسية", + "OrganizationName": "اسم المنظمة", + "CreationDate": "تاريخ الإنشاء", + "LicenseStart": "تاريخ بدء الرخصة", + "LicenseEndDate": "تاريخ إنتهاء الرخصة", + "OrganizationNamePlaceholder": "اسم المنظمة ...", + "TotalQuestionCountPlaceholder": "إجمالي عدد الأسئلة ...", + "RemainingQuestionCountPlaceholder": "عدد الأسئلة المتبقية ...", + "LicenseTypePlaceholder": "نوع الرخصة...", + "CreationDatePlaceholder": "تاريخ الإنشاء...", + "LicenseStartDatePlaceholder": "تاريخ بدء الرخصة ...", + "LicenseEndDatePlaceholder": "تاريخ إنتهاء الرخصة ...", + "UsernameOrEmail": "اسم المستخدم أو البريد الالكتروني", + "UsernameOrEmailPlaceholder": "اسم المستخدم أو البريد الالكتروني...", + "Member": "عضو", + "PurchaseOrderNo": "طلب الشراء رقم", + "QuotationDate": "تاريخ عرض السعر", + "CompanyName": "اسم الشركة", + "CompanyAddress": "عنوان الشركة", + "Price": "السعر", + "DiscountText": "نص الخصم", + "DiscountQuantity": "كمية الخصم", + "DiscountPrice": "سعر الخصم", + "Quotation": "عرض السعر", + "ExtraText": "نص إضافي", + "ExtraAmount": "مبلغ إضافي", + "DownloadQuotation": "تنزيل عرض الأسعار", + "Invoice": "فاتورة", + "TaxNumber": "الرقم الضريبي", + "InvoiceNumber": "رقم الفاتورة", + "InvoiceDate": "تاريخ الفاتورة", + "InvoiceNote": "ملاحظة الفاتورة", + "Quantity": "كمية", + "AddProduct": "أضف منتج", + "AddProductWarning": "تحتاج إلى إضافة منتج!", + "TotalPrice": "السعر الكلي", + "Generate": "انشاء", + "MissingQuantityField": "حقل الكمية مطلوب!", + "MissingPriceField": "حقل السعر مطلوب!", + "CodeUsageStatus": "الحالة", + "Country": "الدولة", + "DeveloperCount": "عدد المطورين", + "RequestCode": "كود الطلب", + "WebSite": "موقع الكتروني", + "GithubUsername": "اسم مستخدم Github", + "PhoneNumber": "رقم الهاتف", + "ProjectDescription": "وصف المشروع", + "Referrer": "المُحيل", + "DiscountRequests": "طلب الخصم", + "Copylink": "انسخ الرابط", + "Disable": "تعطيل", + "Enable": "تفعيل", + "EnableSendEmail": "تمكين إرسال البريد الإلكتروني", + "SendEmail": "ارسل بريد الكتروني", + "SuccessfullyDisabled": "تم التعطيل بنجاح", + "SuccessfullyEnabled": "تم التفعيل بنجاح", + "EmailSent": "تم ارسال البريد الالكتروني", + "SuccessfullySent": "أرسلت بنجاح", + "SuccessfullyDeleted": "تم الحذف بنجاح", + "DiscountRequestDeletionWarningMessage": "سيتم حذف طلب الخصم", + "BusinessType": "نوع العمل", + "TotalQuestionCount": "إجمالي عدد الأسئلة", + "RemainingQuestionCount": "عدد الأسئلة المتبقية", + "TotalQuestionMustBeGreaterWarningMessage": "يجب أن يكون TotalQuestionCount أكبر من RemainingQuestionCount!", + "QuestionCountsMustBeGreaterThanZero": "يجب أن يكون TotalQuestionCount و RemainingQuestionCount صفرًا أو أكبر من الصفر!", + "UnlimitedQuestionCount": "عدد أسئلة غير محدود", + "Notes": "ملاحظات" + } +} \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json index a07b35c66b..e5cfbcae4e 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json @@ -103,7 +103,7 @@ "MasterModules": "Master Modules", "OrganizationName": "Organization name", "CreationDate": "Creation date", - "LicenseStart": "License start date", + "LicenseStartDate": "License start date", "LicenseEndDate": "License end date", "OrganizationNamePlaceholder": "Organization name...", "TotalQuestionCountPlaceholder": "Total question count...", @@ -166,6 +166,30 @@ "TotalQuestionMustBeGreaterWarningMessage": "TotalQuestionCount must be greater than RemainingQuestionCount !", "QuestionCountsMustBeGreaterThanZero": "TotalQuestionCount and RemainingQuestionCount must be zero or greater than zero !", "UnlimitedQuestionCount": "Unlimited question count", - "Notes": "Notes" + "Notes": "Notes", + "Menu:Community": "Community", + "Menu:Articles": "Articles", + "Wait": "Wait", + "Approve": "Approve", + "Reject": "Reject", + "Details": "Details", + "Url": "Url", + "Title": "Title", + "ContentSource": "Content source", + "Status": "Status", + "ReadArticle": "Read article", + "ArticleHasBeenWaiting": "Article has been waiting", + "ArticleHasBeenApproved": "Article has been approved", + "ArticleHasBeenRejected": "Article has been rejected", + "Permission:Community": "Community", + "Permission:CommunityArticle": "Article", + "Link": "Link", + "Enum:ContentSource:0": "Github", + "Enum:ContentSource:1": "External", + "Enum:Status:0": "Waiting", + "Enum:Status:1": "Rejected", + "Enum:Status:2": "Approved", + "Summary": "Summary", + "AuthorName": "Author name" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/AbpIoCommunityResource.cs b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/AbpIoCommunityResource.cs new file mode 100644 index 0000000000..2d7f93aeae --- /dev/null +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/AbpIoCommunityResource.cs @@ -0,0 +1,10 @@ +using Volo.Abp.Localization; + +namespace AbpIoLocalization.Community.Localization +{ + [LocalizationResourceName("AbpIoCommunity")] + public class AbpIoCommunityResource + { + + } +} \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json new file mode 100644 index 0000000000..dbe786fb97 --- /dev/null +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json @@ -0,0 +1,41 @@ +{ + "culture": "en", + "texts": { + "Permission:CommunityArticle": "Community Article", + "Permission:Edit": "Edit", + "Waiting": "Waiting", + "Approved": "Approved", + "Rejected": "Rejected", + "Wait": "Wait", + "Approve": "Approve", + "Reject": "Reject", + "ReadArticle": "Read Article", + "Status": "Status", + "ContentSource": "Content Source", + "Details": "Details", + "Url": "Url", + "Title": "Title", + "CreationTime": "Creation time", + "Save": "Save", + "SameUrlAlreadyExist": "Same url already exist if you want to add this article, you should change the url!", + "UrlIsNotValid": "Url is not valid.", + "UrlNotFound" : "Url not found.", + "UrlContentNotFound": "Url content not found.", + "Summary": "Summary", + "MostRead": "Most Read", + "LatestArticles": "Latest Articles", + "ContributeAbpCommunity": "Contribute to the ABP Community", + "SubmitYourArticle": "Submit Your Article", + "ContributionGuide": "Contribution Guide", + "BugReport": "Bug Report", + "SeeAllArticles": "See All Articles", + "WelcomeToABPCommunity!": "Welcome to the ABP Community!", + "MyProfile": "My profile", + "MyOrganizations": "My organizations", + "EmailNotValid": "Please enter a valid email address.", + "FeatureRequest": "Feature Request", + "CreateArticleTitleInfo": "Title of the article to be shown on the article list.", + "CreateArticleUrlInfo": "Original GitHub/External URL of the article.", + "CreateArticleSummaryInfo": "A short summary of the article to be shown on the article list." + } +} diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json index 6a1d10ee51..fa7b216bd0 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json @@ -15,7 +15,7 @@ "Tutorial": "Tutorial", "UsingCLI": "Using CLI", "SeeDetails": "See Details", - "AbpShortDescription": "ABP is a complete architecture and strong infrastructure to create modern web applications! Follows best practices and conventions to provide you a SOLID development experience.", + "AbpShortDescription": "ABP Framework is a complete infrastructure to create modern web applications by following the software development best practices and conventions.", "SourceCodeUpper": "SOURCE CODE", "LatestReleaseLogs": "Latest release logs", "Infrastructure": "Infrastructure", @@ -31,31 +31,31 @@ "MultiTenancy": "Multi-Tenancy", "MultiTenancyExplanationShort": "SaaS applications made easy! Integrated multi-tenancy from database to UI.", "CrossCuttingConcerns": "Cross Cutting Concerns", - "CrossCuttingConcernsExplanationShort": "Complete infrastructure for authorization, validation, exception handling, caching, audit logging, transaction management and so on.", + "CrossCuttingConcernsExplanationShort": "Complete infrastructure for authorization, validation, exception handling, caching, audit logging, transaction management and more.", "BuiltInBundlingMinification": "Built-In Bundling & Minification", - "BuiltInBundlingMinificationExplanation": "Stop to use external tools for bundling & minification. ABP offers a simpler, dynamic, powerful, modular and built-in way!", + "BuiltInBundlingMinificationExplanation": "No need to use external tools for bundling & minification. ABP offers a simpler, dynamic, powerful, modular and built-in way!", "VirtualFileSystem": "Virtual File System", - "VirtualFileSystemExplanation": "Embed views, scripts, styles, images... into packages/libraries and reuse in different applications.", + "VirtualFileSystemExplanation": "Embed views, scripts, styles, images... into packages/libraries and reuse them in different applications.", "Theming": "Theming", - "ThemingExplanationShort": "Use and customize the bootstrap-based standard UI theme or create your own one.", + "ThemingExplanationShort": "Use and customize the bootstrap-based standard UI theme or create your own.", "BootstrapTagHelpersDynamicForms": "Bootstrap Tag Helpers & Dynamic Forms", - "BootstrapTagHelpersDynamicFormsExplanation": "Instead of manually writing the repeating details of bootstrap components, Use ABP's tag helpers to simplify it and take advantage of intellisense. Dynamic form can create the complete form from a C# class as the model.", + "BootstrapTagHelpersDynamicFormsExplanation": "Instead of manually writing the repeating details of bootstrap components, Use ABP's tag helpers to simplify it and take advantage of intellisense. Quickly build UI forms based on a C# model using the dynamic form tag helper.", "HTTPAPIsDynamicProxies": "HTTP APIs & Dynamic Proxies", - "HTTPAPIsDynamicProxiesExplanation": "Automatically expose application services as REST style HTTP APIs and consume with dynamic JavaScript & C# proxies.", + "HTTPAPIsDynamicProxiesExplanation": "Automatically expose application services as REST style HTTP APIs, and consume them with dynamic JavaScript and C# proxies.", "CompleteArchitectureInfo": "Modern architecture to create maintainable software solutions.", "DomainDrivenDesignBasedLayeringModelExplanation": "Helps you to implement a DDD based layered architecture and build a maintainable code base.", "DomainDrivenDesignBasedLayeringModelExplanationCont": "Provides startup templates, abstractions, base classes, services, documentation and guides to help you to develop your application based on DDD patterns & principles.", "MicroserviceCompatibleModelExplanation": "The core framework & pre-build modules are designed the microservice architecture in mind.", "MicroserviceCompatibleModelExplanationCont": "Provides infrastructure, integrations, samples and documentation to implement microservice solutions easier, while it doesn\u2019t bring additional complexity if you want a monolithic application.", - "ModularInfo": "ABP provides complete modularity system to allow you to develop reusable application modules.", + "ModularInfo": "ABP provides a module system that allows you to develop reusable application modules, tie into application lifecycle events, and express dependencies between core parts of your system.", "PreBuiltModulesThemes": "Pre-Built Modules & Themes", "PreBuiltModulesThemesExplanation": "Open source and commercial modules & themes are ready to use in your business application.", "NuGetNPMPackages": "NuGet & NPM Packages", "NuGetNPMPackagesExplanation": "Distributed as NuGet & NPM packages. Easy to install and upgrade.", "ExtensibleReplaceable": "Extensible/Replaceable", - "ExtensibleReplaceableExplanation": "All services & modules are designed extensibility in mind. You can replace services, pages, styles, components...", - "CrossCuttingConcernsExplanation2": "Keep your code cleaner and focus on your own business code.", - "CrossCuttingConcernsExplanation3": "Don\u2019t send time to implement common application requirements again and again.", + "ExtensibleReplaceableExplanation": "All services & modules are designed extensibility in mind. You can replace services, pages, styles and components.", + "CrossCuttingConcernsExplanation2": "Keep your codebase smaller so you can maintain focus on the code that’s specific to your business.", + "CrossCuttingConcernsExplanation3": "Don\u2019t send time implementing common application requirements on multiple projects.", "AuthenticationAuthorization": "Authentication & Authorization", "ExceptionHandling": "Exception Handling", "Validation": "Validation", @@ -76,14 +76,14 @@ "BaseClasses": "Base Classes", "BaseClassesExplanation": "Pre-built base classes for common application patterns.", "DeveloperFocusedExplanation": "ABP is for developers.", - "DeveloperFocusedExplanationCont": "It aims to simplify your daily software development while not restricting you to work low level when you need it.", + "DeveloperFocusedExplanationCont": "It aims to simplify your daily software development while not restricting you from writing low level code.", "SeeAllFeatures": "See All Features", "CLI_CommandLineInterface": "CLI (Command Line Interface)", - "CLI_CommandLineInterfaceExplanation": "CLI automates to create new projects and add modules to your application.", + "CLI_CommandLineInterfaceExplanation": "Includes a CLI to help you automate the creation of new projects and the addition of new modules.", "StartupTemplates": "Startup Templates", - "StartupTemplatesExplanation": "Various startup templates provide you fully configured solution to jump start your development.", + "StartupTemplatesExplanation": "Various startup templates provide a fully configured solution to jump start your development.", "BasedOnFamiliarTools": "Based on Familiar Tools", - "BasedOnFamiliarToolsExplanation": "Built on and integrated to popular tools you already know. Low learning curve, easy adaptation, comfortable development.", + "BasedOnFamiliarToolsExplanation": "Built on and integrated with popular tools you already know. Low learning curve, easy adaptation, comfortable development.", "ORMIndependent": "ORM Independent", "ORMIndependentExplanation": "The core framework is ORM/database independent and can work with any data source. Entity Framework Core and MongoDB providers are already available.", "Features": "Explore the ABP Framework Features", @@ -158,4 +158,4 @@ "Mobile": "Mobile", "ReactNative": "React Native" } -} \ No newline at end of file +} diff --git a/build/build-all-release.ps1 b/build/build-all-release.ps1 index 1c86351379..f54912d4f5 100644 --- a/build/build-all-release.ps1 +++ b/build/build-all-release.ps1 @@ -1,4 +1,4 @@ -. ".\common.ps1" +. ".\common.ps1" -f # Build all solutions diff --git a/build/build-all.ps1 b/build/build-all.ps1 index ffaffa67a3..16f8bf037c 100644 --- a/build/build-all.ps1 +++ b/build/build-all.ps1 @@ -1,6 +1,10 @@ -. ".\common.ps1" +$full = $args[0] -# Build all solutions +. ".\common.ps1" $full + +# Build all solutions + +Write-Host $solutionPaths foreach ($solutionPath in $solutionPaths) { $solutionAbsPath = (Join-Path $rootFolder $solutionPath) diff --git a/build/common.ps1 b/build/common.ps1 index 70a432818b..18019a154e 100644 --- a/build/common.ps1 +++ b/build/common.ps1 @@ -1,27 +1,38 @@ -# COMMON PATHS +$full = $args[0] + +# COMMON PATHS $rootFolder = (Get-Item -Path "./" -Verbose).FullName -# List of solutions +# List of solutions used only in development mode +$solutionPaths = @( + "../framework", + "../modules/users", + "../modules/permission-management", + "../modules/setting-management", + "../modules/feature-management", + "../modules/identity", + "../modules/identityserver", + "../modules/tenant-management", + "../modules/audit-logging", + "../modules/background-jobs", + "../modules/account" + ) -$solutionPaths = ( - "../framework", - "../modules/users", - "../modules/permission-management", - "../modules/setting-management", - "../modules/feature-management", - "../modules/identity", - "../modules/identityserver", - "../modules/tenant-management", - "../modules/account", - "../modules/docs", - "../modules/blogging", - "../modules/audit-logging", - "../modules/background-jobs", - "../modules/client-simulation", - "../modules/virtual-file-explorer", - "../templates/module/aspnet-core", - "../templates/app/aspnet-core", - "../samples/MicroserviceDemo", - "../abp_io/AbpIoLocalization" -) \ No newline at end of file +if ($full -eq "-f") +{ + # List of additional solutions required for full build + $solutionPaths += ( + "../modules/client-simulation", + "../modules/virtual-file-explorer", + "../modules/docs", + "../modules/blogging", + "../templates/module/aspnet-core", + "../templates/app/aspnet-core", + "../abp_io/AbpIoLocalization" + ) +}else{ + Write-host "" + Write-host ":::::::::::::: !!! You are in development mode !!! ::::::::::::::" -ForegroundColor red -BackgroundColor yellow + Write-host "" +} diff --git a/build/test-all.ps1 b/build/test-all.ps1 index 1465bff6c3..9a94ec3760 100644 --- a/build/test-all.ps1 +++ b/build/test-all.ps1 @@ -1,4 +1,6 @@ -. ".\common.ps1" +$full = $args[0] + +. ".\common.ps1" $full # Test all solutions diff --git a/common.props b/common.props index 8e124f3e4c..eea8755d42 100644 --- a/common.props +++ b/common.props @@ -1,7 +1,7 @@ - - latest - 3.0.5 + + latest + 3.1.0 $(NoWarn);CS1591 https://abp.io/assets/abp_nupkg.png https://abp.io diff --git a/docs/docs-langs.json b/docs/docs-langs.json index a73882bd51..3986dc8618 100644 --- a/docs/docs-langs.json +++ b/docs/docs-langs.json @@ -14,11 +14,6 @@ "DisplayName" : "简体中文", "Code" : "zh-Hans", "IsDefault": false - }, - { - "DisplayName" : "Čeština", - "Code" : "cs", - "IsDefault": false - } + } ] -} \ No newline at end of file +} diff --git a/docs/en/API/Application-Configuration.md b/docs/en/API/Application-Configuration.md index 1465f0fd24..3aea468f8c 100644 --- a/docs/en/API/Application-Configuration.md +++ b/docs/en/API/Application-Configuration.md @@ -7,7 +7,7 @@ ABP Framework provides a pre-built and standard endpoint that contains some usef * [Setting](../Settings.md) values for the current user. * Info about the [current user](../CurrentUser.md) (like id and user name). * Info about the current [tenant](../Multi-Tenancy.md) (like id and name). -* [Time zone](../Timing.md) information for the current user and the [clock](Timing.md) type of the application. +* [Time zone](../Timing.md) information for the current user and the [clock](../Timing.md) type of the application. ## HTTP API diff --git a/docs/en/Authentication/Social-External-Logins.md b/docs/en/Authentication/Social-External-Logins.md new file mode 100644 index 0000000000..c48763a92a --- /dev/null +++ b/docs/en/Authentication/Social-External-Logins.md @@ -0,0 +1,30 @@ +# Social/External Logins + +## ASP.NET Core MVC / Razor Pages UI + +The [Account Module](../Modules/Account.md) has already configured to handle social or external logins out of the box. You can follow the ASP.NET Core documentation to add a social/external login provider to your application. + +### Example: Facebook Authentication + +Follow the [ASP.NET Core Facebook integration document](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/facebook-logins) to support the Facebook login for your application. + +#### Add the NuGet Package + +Add the [Microsoft.AspNetCore.Authentication.Facebook](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.Facebook) package to your project. Based on your architecture, this can be `.Web`, `.IdentityServer` (for tiered setup) or `.Host` project. + +#### Configure the Provider + +Use the `.AddFacebook(...)` extension method in the `ConfigureServices` method of your [module](../Module-Development-Basics.md), to configure the client: + +````csharp +context.Services.AddAuthentication() + .AddFacebook(facebook => + { + facebook.AppId = "..."; + facebook.AppSecret = "..."; + facebook.Scope.Add("email"); + facebook.Scope.Add("public_profile"); + }); +```` + +> It would be a better practice to use the `appsettings.json` or the ASP.NET Core User Secrets system to store your credentials, instead of a hard-coded value like that. Follow the [Microsoft's document](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/facebook-logins) to learn the user secrets usage. \ No newline at end of file diff --git a/docs/en/Background-Jobs-Hangfire.md b/docs/en/Background-Jobs-Hangfire.md index 3a98fed66e..cfa2e12a6b 100644 --- a/docs/en/Background-Jobs-Hangfire.md +++ b/docs/en/Background-Jobs-Hangfire.md @@ -66,9 +66,7 @@ After you have installed these NuGet packages, you need to configure your projec } ```` -2. We need to add `UseHangfireServer` call in the `OnApplicationInitialization` method in `Module` class - -If you want to use hangfire's dashboard, you can add it, too: by `UseHangfireDashboard` +2. If you want to use hangfire's dashboard, you can add `UseHangfireDashboard` call in the `OnApplicationInitialization` method in `Module` class ````csharp public override void OnApplicationInitialization(ApplicationInitializationContext context) @@ -77,7 +75,6 @@ If you want to use hangfire's dashboard, you can add it, too: by `UseHangfireDas // ... others - app.UseHangfireServer(); app.UseHangfireDashboard(); } diff --git a/docs/en/Background-Workers-Quartz.md b/docs/en/Background-Workers-Quartz.md index 9b419d6c27..e1d8287019 100644 --- a/docs/en/Background-Workers-Quartz.md +++ b/docs/en/Background-Workers-Quartz.md @@ -36,6 +36,9 @@ public class YourModule : AbpModule } ```` +> Quartz background worker integration provided `QuartzPeriodicBackgroundWorkerAdapter` to adapt `PeriodicBackgroundWorkerBase` and `AsyncPeriodicBackgroundWorkerBase` derived class. So, you can still fllow the [background workers document](Background-Workers.md) to define the background worker. +> `BackgroundJobWorker` checks todo jobs every 5 seconds, but quartz will not block when long time jobs are executing. So,after Added Quartz background worker integration, you also need to add [Quartz Background Jobs](Background-Jobs-Quartz.md) or [Hangfire Background Jobs](Background-Jobs-Hangfire.md) to avoid duplicate execution jobs. + ## Configuration See [Configuration](Background-Jobs-Quartz#Configuration). diff --git a/docs/en/Blob-Storing-Aliyun.md b/docs/en/Blob-Storing-Aliyun.md new file mode 100644 index 0000000000..62e57e58c7 --- /dev/null +++ b/docs/en/Blob-Storing-Aliyun.md @@ -0,0 +1,78 @@ +# BLOB Storing Aliyun Provider + +BLOB Storing Aliyun Provider can store BLOBs in [Aliyun Blob storage](https://help.aliyun.com/product/31815.html). + +> Read the [BLOB Storing document](Blob-Storing.md) to understand how to use the BLOB storing system. This document only covers how to configure containers to use a Aliyun BLOB as the storage provider. + +## Installation + +Use the ABP CLI to add [Volo.Abp.BlobStoring.Aliyun](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Aliyun) NuGet package to your project: + +* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before. +* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.BlobStoring.Aliyun` package. +* Run `abp add-package Volo.Abp.BlobStoring.Aliyun` command. + +If you want to do it manually, install the [Volo.Abp.BlobStoring.Aliyun](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Aliyun) NuGet package to your project and add `[DependsOn(typeof(AbpBlobStoringAliyunModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project. + +## Configuration + +Configuration is done in the `ConfigureServices` method of your [module](Module-Development-Basics.md) class, as explained in the [BLOB Storing document](Blob-Storing.md). + +**Example: Configure to use the Aliyun storage provider by default** + +````csharp +Configure(options => +{ + options.Containers.ConfigureDefault(container => + { + container.UseAliyun(aliyun => + { + aliyun.AccessKeyId = "your aliyun access key id"; + aliyun.AccessKeySecret = "your aliyun access key secret"; + aliyun.Endpoint = "your oss endpoint"; + aliyun.RegionId = "your sts region id"; + aliyun.RoleArn = "the arn of ram role"; + aliyun.RoleSessionName = "the name of the certificate"; + aliyun.Policy = "policy"; + aliyun.DurationSeconds = "expiration date"; + aliyun.ContainerName = "your aliyun container name"; + aliyun.CreateContainerIfNotExists = false; + }); + }); +}); +```` + +> See the [BLOB Storing document](Blob-Storing.md) to learn how to configure this provider for a specific container. + +### Options + +* **AccessKeyId** ([NotNull]string): AccessKey is the key to access the Alibaba Cloud API. It has full permissions for the account. Please keep it safe! Recommend to follow [Alibaba Cloud security best practicess](https://help.aliyun.com/document_detail/102600.html),Use RAM sub-user AccessKey to call API. +* **AccessKeySecret** ([NotNull]string): Same as above. +* **Endpoint** ([NotNull]string): Endpoint is the external domain name of OSS. See the [document](https://help.aliyun.com/document_detail/31837.html) for details. +* **UseSecurityTokenService** (bool): Use [STS temporary credentials](https://help.aliyun.com/document_detail/100624.html) to access OSS services,default: `false`. +* **RegionId** (string): Access address of STS service. See the [document](https://help.aliyun.com/document_detail/66053.html) for details. +* **RoleArn** ([NotNull]string): STS required role ARN. See the [document](https://help.aliyun.com/document_detail/100624.html) for details. +* **RoleSessionName** ([NotNull]string): Used to identify the temporary access credentials, it is recommended to use different application users to distinguish. +* **Policy** (string): Additional permission restrictions. See the [document](https://help.aliyun.com/document_detail/100680.html) for details. +* **DurationSeconds** (int): Validity period(s) of a temporary access certificate,minimum is 900 and the maximum is 3600. +* **ContainerName** (string): You can specify the container name in Aliyun. If this is not specified, it uses the name of the BLOB container defined with the `BlogContainerName` attribute (see the [BLOB storing document](Blob-Storing.md)). Please note that Aliyun has some **rules for naming containers**. A container name must be a valid DNS name, conforming to the [following naming rules](https://help.aliyun.com/knowledge_detail/39668.html): + * Container names must start or end with a letter or number, and can contain only letters, numbers, and the dash (-) character. + * Container names Must start and end with lowercase letters and numbers. + * Container names must be from **3** through **63** characters long. +* **CreateContainerIfNotExists** (bool): Default value is `false`, If a container does not exist in Aliyun, `AliyunBlobProvider` will try to create it. +* **TemporaryCredentialsCacheKey** (bool): The cache key of STS credentials. + + +## Aliyun Blob Name Calculator + +Aliyun Blob Provider organizes BLOB name and implements some conventions. The full name of a BLOB is determined by the following rules by default: + +* Appends `host` string if [current tenant](Multi-Tenancy.md) is `null` (or multi-tenancy is disabled for the container - see the [BLOB Storing document](Blob-Storing.md) to learn how to disable multi-tenancy for a container). +* Appends `tenants/` string if current tenant is not `null`. +* Appends the BLOB name. + +## Other Services + +* `AliyunBlobProvider` is the main service that implements the Aliyun BLOB storage provider, if you want to override/replace it via [dependency injection](Dependency-Injection.md) (don't replace `IBlobProvider` interface, but replace `AliyunBlobProvider` class). +* `IAliyunBlobNameCalculator` is used to calculate the full BLOB name (that is explained above). It is implemented by the `DefaultAliyunBlobNameCalculator` by default. +* `IOssClientFactory` is used create OSS client. It is implemented by the `DefaultOssClientFactory` by default. You can override/replace it,if you want customize. diff --git a/docs/en/Blob-Storing-Aws.md b/docs/en/Blob-Storing-Aws.md new file mode 100644 index 0000000000..cfee5c3f57 --- /dev/null +++ b/docs/en/Blob-Storing-Aws.md @@ -0,0 +1,85 @@ +# BLOB Storing Aws Provider + +BLOB Storing Aws Provider can store BLOBs in [Amazon Simple Storage Service](https://aws.amazon.com/s3/). + +> Read the [BLOB Storing document](Blob-Storing.md) to understand how to use the BLOB storing system. This document only covers how to configure containers to use a Aws BLOB as the storage provider. + +## Installation + +Use the ABP CLI to add [Volo.Abp.BlobStoring.Aws](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Aws) NuGet package to your project: + +* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before. +* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.BlobStoring.Aws` package. +* Run `abp add-package Volo.Abp.BlobStoring.Aws` command. + +If you want to do it manually, install the [Volo.Abp.BlobStoring.Aws](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Aws) NuGet package to your project and add `[DependsOn(typeof(AbpBlobStoringAwsModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project. + +## Configuration + +Configuration is done in the `ConfigureServices` method of your [module](Module-Development-Basics.md) class, as explained in the [BLOB Storing document](Blob-Storing.md). + +**Example: Configure to use the Aws storage provider by default** + +````csharp +Configure(options => +{ + options.Containers.ConfigureDefault(container => + { + container.UseAws(Aws => + { + Aws.AccessKeyId = "your Aws access key id"; + Aws.SecretAccessKey = "your Aws access key secret"; + Aws.UseCredentials = "set true to use credentials"; + Aws.UseTemporaryCredentials = "set true to use temporary credentials"; + Aws.UseTemporaryFederatedCredentials = "set true to use temporary federated credentials"; + Aws.ProfileName = "the name of the profile to get credentials from"; + Aws.ProfilesLocation = "the path to the aws credentials file to look at"; + Aws.Region = "the system name of the service"; + Aws.Name = "the name of the federated user"; + Aws.Policy = "policy"; + Aws.DurationSeconds = "expiration date"; + Aws.ContainerName = "your Aws container name"; + Aws.CreateContainerIfNotExists = false; + }); + }); +}); + +```` + +> See the [BLOB Storing document](Blob-Storing.md) to learn how to configure this provider for a specific container. + +### Options + +* **AccessKeyId** (string): AWS Access Key ID. +* **SecretAccessKey** (string): AWS Secret Access Key. +* **UseCredentials** (bool): Use [credentials](https://docs.aws.amazon.com/AmazonS3/latest/dev/AuthUsingAcctOrUserCredentials.html) to access AWS services,default : `false`. +* **UseTemporaryCredentials** (bool): Use [temporary credentials](https://docs.aws.amazon.com/AmazonS3/latest/dev/AuthUsingTempSessionToken.html) to access AWS services,default : `false`. +* **UseTemporaryFederatedCredentials** (bool): Use [federated user temporary credentials](https://docs.aws.amazon.com/AmazonS3/latest/dev/AuthUsingTempFederationToken.html) to access AWS services, default : `false`. +* **ProfileName** (string): The [name of the profile]((https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/net-dg-config-creds.html)) to get credentials from. +* **ProfilesLocation** (string): The path to the aws credentials file to look at. +* **Region** (string): The system name of the service. +* **Policy** (string): An IAM policy in JSON format that you want to use as an inline session policy. +* **DurationSeconds** (int): Validity period(s) of a temporary access certificate,minimum is 900 and the maximum is 3600. **note**: Using subaccounts operated OSS,if the value is 0. +* **ContainerName** (string): You can specify the container name in Aws. If this is not specified, it uses the name of the BLOB container defined with the `BlogContainerName` attribute (see the [BLOB storing document](Blob-Storing.md)). Please note that Aws has some **rules for naming containers**. A container name must be a valid DNS name, conforming to the [following naming rules](https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html): + * Bucket names must be between **3** and **63** characters long. + * Bucket names can consist only of **lowercase** letters, numbers, dots (.), and hyphens (-). + * Bucket names must begin and end with a letter or number. + * Bucket names must not be formatted as an IP address (for example, 192.168.5.4). + * Bucket names can't begin with **xn--** (for buckets created after February 2020). + * Bucket names must be unique within a partition. + * Buckets used with Amazon S3 Transfer Acceleration can't have dots (.) in their names. For more information about transfer acceleration, see Amazon S3 Transfer Acceleration. +* **CreateContainerIfNotExists** (bool): Default value is `false`, If a container does not exist in Aws, `AwsBlobProvider` will try to create it. + +## Aws Blob Name Calculator + +Aws Blob Provider organizes BLOB name and implements some conventions. The full name of a BLOB is determined by the following rules by default: + +* Appends `host` string if [current tenant](Multi-Tenancy.md) is `null` (or multi-tenancy is disabled for the container - see the [BLOB Storing document](Blob-Storing.md) to learn how to disable multi-tenancy for a container). +* Appends `tenants/` string if current tenant is not `null`. +* Appends the BLOB name. + +## Other Services + +* `AwsBlobProvider` is the main service that implements the Aws BLOB storage provider, if you want to override/replace it via [dependency injection](Dependency-Injection.md) (don't replace `IBlobProvider` interface, but replace `AwsBlobProvider` class). +* `IAwsBlobNameCalculator` is used to calculate the full BLOB name (that is explained above). It is implemented by the `DefaultAwsBlobNameCalculator` by default. +* `IAmazonS3ClientFactory` is used create OSS client. It is implemented by the `DefaultAmazonS3ClientFactory` by default. You can override/replace it,if you want customize. \ No newline at end of file diff --git a/docs/en/Blob-Storing-Azure.md b/docs/en/Blob-Storing-Azure.md index d43f8b3a43..9d033013da 100644 --- a/docs/en/Blob-Storing-Azure.md +++ b/docs/en/Blob-Storing-Azure.md @@ -23,11 +23,14 @@ Configuration is done in the `ConfigureServices` method of your [module](Module- ````csharp Configure(options => { - options.Containerscontainer.UseAzure(azure => + options.Containers.ConfigureDefault(container => { - azure.ConnectionString = "your azure connection string"; - azure.ContainerName = "your azure container name"; - azure.CreateContainerIfNotExists = false; + container.UseAzure(azure => + { + azure.ConnectionString = "your azure connection string"; + azure.ContainerName = "your azure container name"; + azure.CreateContainerIfNotExists = false; + }); }); }); ```` diff --git a/docs/en/Blob-Storing-Minio.md b/docs/en/Blob-Storing-Minio.md new file mode 100644 index 0000000000..505c6721b0 --- /dev/null +++ b/docs/en/Blob-Storing-Minio.md @@ -0,0 +1,69 @@ +# BLOB Storing Minio Provider + +BLOB Storing Minio Provider can store BLOBs in [MinIO Object storage](https://min.io/). + +> Read the [BLOB Storing document](Blob-Storing.md) to understand how to use the BLOB storing system. This document only covers how to configure containers to use a Minio BLOB as the storage provider. + +## Installation + +Use the ABP CLI to add [Volo.Abp.BlobStoring.Minio](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Minio) NuGet package to your project: + +* Install the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) if you haven't installed before. +* Open a command line (terminal) in the directory of the `.csproj` file you want to add the `Volo.Abp.BlobStoring.Minio` package. +* Run `abp add-package Volo.Abp.BlobStoring.Minio` command. + +If you want to do it manually, install the [Volo.Abp.BlobStoring.Minio](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Minio) NuGet package to your project and add `[DependsOn(typeof(AbpBlobStoringMinioModule))]` to the [ABP module](Module-Development-Basics.md) class inside your project. + +## Configuration + +Configuration is done in the `ConfigureServices` method of your [module](Module-Development-Basics.md) class, as explained in the [BLOB Storing document](Blob-Storing.md). + +**Example: Configure to use the minio storage provider by default** + +````csharp +Configure(options => +{ + options.Containers.ConfigureDefault(container => + { + container.UseMinio(minio => + { + minio.EndPoint = "your minio endPoint"; + minio.AccessKey = "your minio accessKey"; + minio.SecretKey = "your minio secretKey"; + minio.BucketName = "your minio bucketName"; + }); + }); +}); +```` + +> See the [BLOB Storing document](Blob-Storing.md) to learn how to configure this provider for a specific container. + +### Options + +* **EndPoint** (string): URL to object storage service. Please refer to MinIO Client SDK for .NET: https://docs.min.io/docs/dotnet-client-quickstart-guide.html +* **AccessKey** (string): Access key is the user ID that uniquely identifies your account. +* **SecretKey** (string): Secret key is the password to your account. +* **BucketName** (string): You can specify the bucket name in MinIO. If this is not specified, it uses the name of the BLOB container defined with the `BlogContainerName` attribute (see the [BLOB storing document](Blob-Storing.md)).MinIO is the defacto standard for S3 compatibility, So MinIO has some **rules for naming bucket**. The [following rules](https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html) apply for naming MinIO buckets: + * Bucket names must be between **3** and **63** characters long. + * Bucket names can consist only of **lowercase** letters, numbers, dots (.), and hyphens (-). + * Bucket names must begin and end with a letter or number. + * Bucket names must not be formatted as an IP address (for example, 192.168.5.4). + * Bucket names can't begin with **xn--** (for buckets created after February 2020). + * Bucket names must be unique within a partition. + * Buckets used with Amazon S3 Transfer Acceleration can't have dots (.) in their names. For more information about transfer acceleration, see Amazon S3 Transfer Acceleration. +* **WithSSL** (bool): Default value is `false`,Chain to MinIO Client object to use https instead of http. +* **CreateContainerIfNotExists** (bool): Default value is `false`, If a bucket does not exist in minio, `MinioBlobProvider` will try to create it. + + +## Minio Blob Name Calculator + +Minio Blob Provider organizes BLOB name and implements some conventions. The full name of a BLOB is determined by the following rules by default: + +* Appends `host` string if [current tenant](Multi-Tenancy.md) is `null` (or multi-tenancy is disabled for the container - see the [BLOB Storing document](Blob-Storing.md) to learn how to disable multi-tenancy for a container). +* Appends `tenants/` string if current tenant is not `null`. +* Appends the BLOB name. + +## Other Services + +* `MinioBlobProvider` is the main service that implements the Minio BLOB storage provider, if you want to override/replace it via [dependency injection](Dependency-Injection.md) (don't replace `IBlobProvider` interface, but replace `MinioBlobProvider` class). +* `IMinioBlobNameCalculator` is used to calculate the full BLOB name (that is explained above). It is implemented by the `DefaultMinioBlobNameCalculator` by default. diff --git a/docs/en/Blob-Storing.md b/docs/en/Blob-Storing.md index 2b49a896cd..23b4f80560 100644 --- a/docs/en/Blob-Storing.md +++ b/docs/en/Blob-Storing.md @@ -19,6 +19,9 @@ The ABP Framework has already the following storage provider implementations; * [File System](Blob-Storing-File-System.md): Stores BLOBs in a folder of the local file system, as standard files. * [Database](Blob-Storing-Database.md): Stores BLOBs in a database. * [Azure](Blob-Storing-Azure.md): Stores BLOBs on the [Azure BLOB storage](https://azure.microsoft.com/en-us/services/storage/blobs/). +* [Aliyun](Blob-Storing-Aliyun.md): Stores BLOBs on the [Aliyun Storage Service](https://help.aliyun.com/product/31815.html). +* [Ninio](Blob-Storing-Minio.md): Stores BLOBs on the [MinIO Object storage](https://min.io/). +* [Aws](Blob-Storing-Aws.md): Stores BLOBs on the[Amazon Simple Storage Service](https://min.io/). More providers will be implemented by the time. You can [request](https://github.com/abpframework/abp/issues/new) it for your favorite provider or [create it yourself](Blob-Storing-Custom-Provider.md) and [contribute](Contribution/Index.md) to the ABP Framework. diff --git a/docs/en/Blog-Posts/2020-07-01 v3_0_Release/Post.md b/docs/en/Blog-Posts/2020-07-01 v3_0_Release/Post.md new file mode 100644 index 0000000000..92ec1561d3 --- /dev/null +++ b/docs/en/Blog-Posts/2020-07-01 v3_0_Release/Post.md @@ -0,0 +1,177 @@ +# ABP Framework v3.0 Has Been Released + +We are excited to announce that the **ABP Framework** & and the **ABP Commercial** version 3.0 have been released. As different than the regular release lifecycle, which is 2-weeks, this version has taken 4-weeks with **119 [issues](https://github.com/abpframework/abp/issues?q=is%3Aopen+is%3Aissue+milestone%3A3.0)** closed, **89 [pull requests](https://github.com/abpframework/abp/pulls?q=is%3Aopen+is%3Apr+milestone%3A3.0)** merged and **798 commits** done in the main framework [repository](https://github.com/abpframework/abp). + +Since this is a **major version**, it also includes some **breaking changes**. Don't panic, the changes are easy to adapt and will be explained below. + +> See the [GitHub release notes](https://github.com/abpframework/abp/releases/tag/3.0.0) for a detailed change log. + +## What's New with the ABP Framework 3.0? + +This post will only cover the important features/changes. You can see all the changes on the [GitHub release notes](https://github.com/abpframework/abp/releases/tag/2.9.0). + +### Angular 10! + +Angular version 10 has just been [released](https://blog.angular.io/version-10-of-angular-now-available-78960babd41) and we've immediately migrated the [startup templates](https://docs.abp.io/en/abp/latest/Startup-Templates/Application) to Angular 10! So, when you [create a new solution](https://abp.io/get-started) with the Angular UI, you will take the advantage of the new Angular. + +We've prepared a [migration guide](https://github.com/abpframework/abp/blob/dev/docs/en/UI/Angular/Migration-Guide-v3.md) for the projects created an older version and want to migrate to Angular 10. + +### The Oracle Integration Package + +We had created [an integration package](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle.Devart) for the Oracle for EF Core based applications using the Devart's library since the official Oracle EF Core package was not supporting the EF Core 3.1. It now supports as a [beta release](https://www.nuget.org/packages/Oracle.EntityFrameworkCore/3.19.0-beta1). While it is in beta, we've created [the integration package](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle), so you can use it in your application. + +See [the documentation](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Oracle) for details. + +### Azure BLOB Storage Provider + +We had created a [BLOB storing system](https://docs.abp.io/en/abp/latest/Blob-Storing) in the previous version with a file system and database storage provider. This release introduces the Azure BLOB Storage provider. See [the documentation](https://docs.abp.io/en/abp/latest/Blob-Storing-Azure). + +### Distributed Cache Bulk Operations & the New Redis Cache Package + +The [standard IDistributeCache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) interface of the ASP.NET Core doesn't contain **bulk operations**, like setting multiple items with a single method/server call. ABP Framework introduces new methods those can be used for bulk operations on the ABP's `IDistributedCache` interface: + +* GetManyAsync / GetMany +* SetManyAsync / SetMany + +Then we needed to implement these new methods for Redis cache and [had to create](https://github.com/abpframework/abp/issues/4483) a Redis integration package which extends the Microsoft's implementation. + +These methods are also used by the ABP Framework to cache settings, features and permissions for a user/role/tenant and brings a **significant performance improvement**. + +See the [caching document](https://docs.abp.io/en/abp/latest/Caching) for details. + +### Embedded Files Manifest Support for the Virtual File System + +Virtual File System now supports to use `GenerateEmbeddedFilesManifest` in your projects to add the **real file/directory structure** of your embedded resources in the compiled assembly. So, you can now access to the files without any file name restriction (previously, some special chars like `.` in the directory names was a problem in some cases) + +See [the documentation](https://docs.abp.io/en/abp/latest/Virtual-File-System) to learn how to take the advantage of new system. + +### New Samples + +Based on the requests from the community, we've prepared two new sample applications: + +* [StoredProcedureDemo](https://github.com/abpframework/abp-samples/tree/master/StoredProcedureDemo) demonstrates how to call stored procedures, views and functions inside a custom repository. +* [OrganizationUnitSample](https://github.com/abpframework/abp-samples/tree/master/OrganizationUnitSample) shows how to use the organization unit system of the [Identity module](https://docs.abp.io/en/abp/latest/Modules/Identity) for your entities. + +### DynamicStringLength & DynamicMaxLength Attributes + +The standard `StringLength` and `MaxLength` data annotation attributes is useful to validate properties of a class when the class is used as a Model or [DTO](https://docs.abp.io/en/abp/latest/Data-Transfer-Objects). However, just like any other attribute, the length values should be literal (constant) values known at **compile time**. + +**Example: Using the `StringLength`** + +````csharp +public class CreateBookDto +{ + public const int MaxNameLength = 128; //CONSTANT! + + [StringLength(MaxNameLength)] + public string Name { get; set; } +} +```` + +ABP Framework now has the `DynamicStringLength` & `DynamicMaxLength` properties to allow to determine the lengths at **runtime**. + +**Example: Using the `DynamicStringLength`** + +````csharp +public class CreateBookDto +{ + public static int MaxNameLength { get; set; } = 128; + + [DynamicStringLength(typeof(CreateBookDto), nameof(MaxNameLength))] + public string Name { get; set; } +} +```` + +`DynamicStringLength` gets a class **type** and the **name** of a static property on this class to read the max length (there is also a minimum length option just like the `StringLength`). + +This allows you to get the max value from a configuration and set on the application startup (generally, in the `PreConfigureServices` method of your [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics)): + +````csharp +CreateBookDto.MaxNameLength = 200; +```` + +This feature is used by the [pre-built application modules](https://docs.abp.io/en/abp/latest/Modules/Index), so you can now override the max lengths of the properties defined in these modules. + +### Auto Distributed Events + +ABP can **automatically publish distributed events** for all entities on their create, update and delete events. That's pretty useful since you commonly interest in these basic events in a distributed system. + +This feature is **mature and [documented](https://docs.abp.io/en/abp/latest/Distributed-Event-Bus#pre-defined-events)** with the v3.0. You can easily configure some or all the entities to be published. + +### IAsyncQueryableExecuter + +When you work with LINQ extension methods, you need to call `ToListAsync()`, `FirstOrDefaultAsync()`... methods on your queries. Unfortunately, these methods are **not standard** LINQ extension methods. They are defined in the [Microsoft.EntityFrameworkCore](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore) package (or in the [MongoDB.Driver](https://www.nuget.org/packages/MongoDB.Driver/) package if you are using the MongoDB). + +So, you need to depend on this package if you want to use the async extension methods. That breaks the layering and makes your application or domain layer depends on the EF Core / MongoDB package. + +`IAsyncQueryableExecuter` is a service defined by the ABP Framework to **execute queries asynchronously without depending the specific provider** (EF Core / MongoDB) package. + +See [the documentation](https://docs.abp.io/en/abp/latest/Repositories#iqueryable-async-operations) to read the details and learn our recommendations. + +### API documentation + +We are now publishing [API documents](https://docs.abp.io/api-docs/abp/2.9/api/index.html) for the ABP Framework and modules in every release. So, you can explore the ABP Framework classes much more easier than before. Click the the **API Documentation** link on the navigation menu of the [documentation](https://docs.abp.io/en/abp/latest/). + +### Package List + +We have [created a page](http://abp.io/packages) to list all the ABP-related official NuGet and NPM packages. + +### Others + +* Implemented front-channel server-side clients [sign out](https://identityserver4.readthedocs.io/en/latest/topics/signout.html) for the identity server. +* `abp.currentUser` (`CurrentUser` service in the Angular UI) now has a `roles` array that contains role names of the current user. +* Upgraded all the NuGet and NPM package dependencies. +* Introduced `ReadOnlyAppService` base class (which has only the get operations) in addition to the `CrudAppService` base class (which has get, create, update and delete operations). + +See the [GitHub release notes](https://github.com/abpframework/abp/releases/tag/3.0.0) for others updates. + +## What's New with the ABP Commercial 3.0? + +In addition to all the features coming with the ABP Framework, the ABP Commercial has additional features with this release, as always. This section covers the [ABP Commercial](https://commercial.abp.io/) highlights in the version 2.9. + +### New File Management Module + +We've created a new module that is used to store and manage files in your application. This new module is based on the [BLOB Storing system](https://docs.abp.io/en/abp/latest/Blob-Storing), so it can use different storage providers to store the file contents. + +**Example screenshot** + +![file-management-ui](file-management-ui.png) + +You can upload, download and organize files in a hierarchical folder structure. It is also compatible to multi-tenancy and you can determine total size limit for your tenants. In the next versions, we will be working on a "share" system to share files between users in a more controlled way or share your files with your customers with a public link. + +> File Management module is currently available only for the MVC / Razor Pages UI. We are working on the Angular UI and it will be released in the next versions. + +## Breaking Changes + +Since this is a major version, we've redesigned some APIs and introduced a few "easy to fix" breaking changes. + +### ABP Framework + +* Changed some **consts** in the pre-built application modules to static properties that is possible to change by your code. If you've used these consts on an attribute, then use the `DynamicStringLength` as explained above. +* Changed `ConcurrencyStamp` max length to 40. You need to **add a database migration** and update your database after upgrading the ABP Framework. +* Using `~` instead of `^` for NPM package dependencies anymore, to be more stable. + +### ABP Commercial + +* Changed file names for the application logos. Previously, it was using separate logo files for each theme, like `theme1.png`, `theme1-reverse.png`, `theme2.png`, `theme2-reverse.png` (... `6`). Now, we have only two logo files: `logo-light.png` and `logo-dark.png`. So, rename your logo in the `wwwroot/images/logo/` folder for the MVC UI and `/src/assets/images/logo/` folder for the Angular UI. +* We've added the [API documentation](https://docs.abp.io/api-docs/commercial/2.9/api/index.html) for the ABP Commercial too. + +> **Also, see the [migration guide](https://github.com/abpframework/abp/blob/dev/docs/en/UI/Angular/Migration-Guide-v3.md) for Angular UI**. + +## Known Issues + +* 3.0.0 version has a problem with tiered architecture. See [this issue](https://github.com/abpframework/abp/pull/4564) to fix it for your application until we release the v3.0.1. + +## About the Next Versions + +We will continue to release a new minor/feature version in every two weeks. So, the next expected release date is **2020-07-16** for the version **3.1**. + +In the next few versions, we will be focused on the **Blazor UI**, as promised on [the road map](https://docs.abp.io/en/abp/latest/Road-Map). We will continue to improve the documentation, create samples, add other new features and enhancements. Follow the [ABP Framework Twitter account](https://twitter.com/abpframework) for the latest news... + +## Bonus: Articles! + +Beside developing our products, our team are constantly writing articles/tutorials on various topics. You may want to check the latest articles: + +* [What is New in Angular 10?](https://volosoft.com/blog/what-is-new-in-angular-10) +* [Real-Time Messaging In A Distributed Architecture Using ABP, SignalR & RabbitMQ](https://volosoft.com/blog/RealTime-Messaging-Distributed-Architecture-Abp-SingalR-RabbitMQ) +* [How to Use Attribute Directives to Avoid Repetition in Angular Templates](https://volosoft.com/blog/attribute-directives-to-avoid-repetition-in-angular-templates) diff --git a/docs/en/Blog-Posts/2020-07-01 v3_0_Release/file-management-ui.png b/docs/en/Blog-Posts/2020-07-01 v3_0_Release/file-management-ui.png new file mode 100644 index 0000000000..b5d5ea54e4 Binary files /dev/null and b/docs/en/Blog-Posts/2020-07-01 v3_0_Release/file-management-ui.png differ diff --git a/docs/en/CLI.md b/docs/en/CLI.md index 8551d64505..b8ab602c3b 100644 --- a/docs/en/CLI.md +++ b/docs/en/CLI.md @@ -90,6 +90,7 @@ abp new Acme.BookStore * **`console`**: [Console template](Startup-Templates/Console.md). * `--output-folder` or `-o`: Specifies the output folder. Default value is the current directory. * `--version` or `-v`: Specifies the ABP & template version. It can be a [release tag](https://github.com/abpframework/abp/releases) or a [branch name](https://github.com/abpframework/abp/branches). Uses the latest release if not specified. Most of the times, you will want to use the latest version. +* `--preview`: Use latest pre-release version (Only if `--version ` is not specified and there is at least one pre-release after latest stable version). * `--template-source` or `-ts`: Specifies a custom template source to use to build the project. Local and network sources can be used(Like `D:\local-template` or `https://.../my-template-file.zip`). * `--create-solution-folder` or `-csf`: Specifies if the project will be in a new folder in the output folder or directly the output folder. * `--connection-string` or `-cs`: Overwrites the default connection strings in all `appsettings.json` files. The default connection string is `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true` for EF Core and it is configured to use the SQL Server. If you want to use the EF Core, but need to change the DBMS, you can change it as [described here](Entity-Framework-Core-Other-DBMS.md) (after creating the solution). @@ -196,12 +197,12 @@ abp generate-proxy --apiUrl https://localhost:44305 --ui angular --module all ### switch-to-preview -You can use this command to switch your project to latest preview version of the ABP framework packages. +You can use this command to switch your project to latest **nightly** preview version of the ABP framework packages. Usage: ````bash -abp switch-to-preview [options] +abp switch-to-nightly [options] ```` #### Options @@ -210,7 +211,7 @@ abp switch-to-preview [options] ### switch-to-stable -If you're using the ABP Framework preview packages, you can switch back to stable version using this command. +If you're using the ABP Framework preview packages, you can switch back to latest stable version using this command. Usage: @@ -273,11 +274,13 @@ Then review changes on your source control system to be sure that it has changed Some features of the CLI requires to be logged in to abp.io platform. To login with your username write: ```bash -abp login # Asks password separately -abp login -p # Specify the password as a parameter +abp login # Allows you to enter your password hidden +abp login -p # Specify the password as a parameter (password is visible) +abp login --organization # If you have multiple organizations, you need set your active organization +abp login -p -o # You can enter both your password and organization in the same command ``` -> Using `-p` parameter might not be safe if someone is watching your screen :) It can be useful for automation purposes. +> When using the -p parameter, be careful as your password will be visible. It's useful for CI/CD automation pipelines. A new login with an already active session overwrites the previous session. diff --git a/docs/en/Community-Articles/2020-04-19-Customize-the-SignIn-Manager/POST.md b/docs/en/Community-Articles/2020-04-19-Customize-the-SignIn-Manager/POST.md new file mode 100644 index 0000000000..d46049526d --- /dev/null +++ b/docs/en/Community-Articles/2020-04-19-Customize-the-SignIn-Manager/POST.md @@ -0,0 +1,96 @@ +# How to Customize the SignIn Manager for ABP Applications + +After creating a new application using the [application startup template](https://docs.abp.io/en/abp/latest/Startup-Templates/Application), you may want extend or change the default behavior of the SignIn Manager for your authentication and registration flow needs. ABP [Account Module](https://docs.abp.io/en/abp/latest/Modules/Account) uses the [Identity Management Module](https://docs.abp.io/en/abp/latest/Modules/Identity) for SignIn Manager and the [Identity Management Module](https://docs.abp.io/en/abp/latest/Modules/Identity) uses default [Microsoft Identity SignIn Manager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) ([see here](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs#L17)). + +To write your Custom SignIn Manager, you need to extend [Microsoft Identity SignIn Manager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) class and register it to the DI container. + +This document explains how to customize the SignIn Manager for your own application. + +## Create a CustomSignInManager + +Create a new class inheriting the [SignInMager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) of Microsoft Identity package. + +````csharp +public class CustomSignInManager : Microsoft.AspNetCore.Identity.SignInManager +{ + public CustomSignInManager( + Microsoft.AspNetCore.Identity.UserManager userManager, + Microsoft.AspNetCore.Http.IHttpContextAccessor contextAccessor, + Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory claimsFactory, + Microsoft.Extensions.Options.IOptions optionsAccessor, + Microsoft.Extensions.Logging.ILogger> logger, + Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemes, + Microsoft.AspNetCore.Identity.IUserConfirmation confirmation) + : base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation) + { + } +} +```` + +> It is important to use **Volo.Abp.Identity.IdentityUser** type for SignInManager to inherit, not the AppUser of your application. + +Afterwards you can override any of the SignIn Manager methods you need and add new methods and properties needed for your authentication or registration flow. + +## Overriding the GetExternalLoginInfoAsync Method + +In this case we'll be overriding the `GetExternalLoginInfoAsync` method which is invoked when a third party authentication is implemented. + +A good way to override a method is copying its [source code](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Identity/Core/src/SignInManager.cs#L638-L674). In this case, we will be using a minorly modified version of the source code which explicitly shows the namespaces of the methods and properties to help better understanding of the concept. + +````csharp +public override async Task GetExternalLoginInfoAsync(string expectedXsrf = null) +{ + var auth = await Context.AuthenticateAsync(Microsoft.AspNetCore.Identity.IdentityConstants.ExternalScheme); + var items = auth?.Properties?.Items; + if (auth?.Principal == null || items == null || !items.ContainsKey(LoginProviderKey)) + { + return null; + } + + if (expectedXsrf != null) + { + if (!items.ContainsKey(XsrfKey)) + { + return null; + } + var userId = items[XsrfKey] as string; + if (userId != expectedXsrf) + { + return null; + } + } + + var providerKey = auth.Principal.FindFirstValue(ClaimTypes.NameIdentifier); + var provider = items[LoginProviderKey] as string; + if (providerKey == null || provider == null) + { + return null; + } + + var providerDisplayName = (await GetExternalAuthenticationSchemesAsync()).FirstOrDefault(p => p.Name == provider)?.DisplayName + ?? provider; + return new Microsoft.AspNetCore.Identity.ExternalLoginInfo(auth.Principal, provider, providerKey, providerDisplayName) + { + AuthenticationTokens = auth.Properties.GetTokens() + }; +} +```` + +To get your overridden method invoked and your customized SignIn Manager class to work, you need to register your class to the [Dependency Injection System](https://docs.abp.io/en/abp/latest/Dependency-Injection). + +## Register to Dependency Injection + +Registering `CustomSignInManager` should be done with adding **AddSignInManager** extension method of the [IdentityBuilderExtensions](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/IdentityBuilderExtensions.cs) of the [IdentityBuilder](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Extensions.Core/src/IdentityBuilder.cs). + +Inside your `.Web` project, locate the `YourProjectNameWebModule` and add the following code under the `PreConfigureServices` method to replace the old `SignInManager` with your customized one: + +````csharp +PreConfigure(identityBuilder => +{ + identityBuilder.AddSignInManager(); +}); +```` + +## The Source Code + +You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization). \ No newline at end of file diff --git a/docs/en/Community-Articles/2020-04-27-Use-Azure-Active-Directory-Authentication-for-MVC-Razor-Page-Applications/POST.md b/docs/en/Community-Articles/2020-04-27-Use-Azure-Active-Directory-Authentication-for-MVC-Razor-Page-Applications/POST.md new file mode 100644 index 0000000000..c7b0258d55 --- /dev/null +++ b/docs/en/Community-Articles/2020-04-27-Use-Azure-Active-Directory-Authentication-for-MVC-Razor-Page-Applications/POST.md @@ -0,0 +1,207 @@ +# How to Use the Azure Active Directory Authentication for MVC / Razor Page Applications + +This guide demonstrates how to integrate AzureAD to an ABP application that enables users to sign in using OAuth 2.0 with credentials from **Azure Active Directory**. + +Adding Azure Active Directory is pretty straightforward in ABP framework. Couple of configurations needs to be done correctly. + +Two different **alternative approaches** for AzureAD integration will be demonstrated for better coverage. + +1. **AddAzureAD**: This approach uses Microsoft [AzureAD UI nuget package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/) which is very popular when users search the web about how to integrate AzureAD to their web application. + +2. **AddOpenIdConnect**: This approach uses default [OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/) which can be used for not only AzureAD but for all OpenId connections. + +> There is **no difference** in functionality between these approaches. AddAzureAD is an abstracted way of OpenIdConnection ([source](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADAuthenticationBuilderExtensions.cs#L122)) with predefined cookie settings. +> +> However there are key differences in integration to ABP applications because of default configurated signin schemes which will be explained below. + +## 1. AddAzureAD + +This approach uses the most common way to integrate AzureAD by using the [Microsoft AzureAD UI nuget package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/). + +If you choose this approach, you will need to install `Microsoft.AspNetCore.Authentication.AzureAD.UI` package to your **.Web** project. Also, since AddAzureAD extension uses [configuration binding](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1#default-configuration), you need to update your appsettings.json file located in your **.Web** project. + +#### **Updating `appsettings.json`** + +You need to add a new section to your `appsettings.json` which will be binded to configuration when configuring the `OpenIdConnectOptions`: + +````json + "AzureAd": { + "Instance": "https://login.microsoftonline.com/", + "TenantId": "", + "ClientId": "", + "Domain": "domain.onmicrosoft.com", + "CallbackPath": "/signin-azuread-oidc" + } +```` + +> Important configuration here is the CallbackPath. This value must be the same with one of your Azure AD-> app registrations-> Authentication -> RedirectUri. + +Then, you need to configure the `OpenIdConnectOptions` to complete the integration. + +#### Configuring OpenIdConnectOptions + +In your **.Web** project, locate your **ApplicationWebModule** and modify `ConfigureAuthentication` method with the following: + +````csharp +private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) + { + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier); + context.Services.AddAuthentication() + .AddIdentityServerAuthentication(options => + { + options.Authority = configuration["AuthServer:Authority"]; + options.RequireHttpsMetadata = false; + options.ApiName = "Acme.BookStore"; + }) + .AddAzureAD(options => configuration.Bind("AzureAd", options)); + + context.Services.Configure(AzureADDefaults.OpenIdScheme, options => + { + options.Authority = options.Authority + "/v2.0/"; + options.ClientId = configuration["AzureAd:ClientId"]; + options.CallbackPath = configuration["AzureAd:CallbackPath"]; + options.ResponseType = OpenIdConnectResponseType.CodeIdToken; + options.RequireHttpsMetadata = false; + + options.TokenValidationParameters.ValidateIssuer = false; + options.GetClaimsFromUserInfoEndpoint = true; + options.SaveTokens = true; + options.SignInScheme = IdentityConstants.ExternalScheme; + + options.Scope.Add("email"); + }); + } +```` + +> **Don't forget to:** +> +> * Add `.AddAzureAD(options => configuration.Bind("AzureAd", options))` after `.AddAuthentication()`. This binds your AzureAD appsettings and easy to miss out. +> * Add `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear()`. This will disable the default Microsoft claim type mapping. +> * Add `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier)`. Mapping this to [ClaimTypes.NameIdentifier](https://github.com/dotnet/runtime/blob/6d395de48ac718a913e567ae80961050f2a9a4fa/src/libraries/System.Security.Claims/src/System/Security/Claims/ClaimTypes.cs#L59) is important since default SignIn Manager behavior uses this claim type for external login information. +> * Add `options.SignInScheme = IdentityConstants.ExternalScheme` since [default signin scheme is `AzureADOpenID`](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADOpenIdConnectOptionsConfiguration.cs#L35). +> * Add `options.Scope.Add("email")` if you are using **v2.0** endpoint of AzureAD since v2.0 endpoint doesn't return the `email` claim as default. The [Account Module](https://docs.abp.io/en/abp/latest/Modules/Account) uses `email` claim to [register external users](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L215). + +You are done and integration is completed. + +## 2. Alternative Approach: AddOpenIdConnect + +If you don't want to use an extra nuget package in your application, you can use the straight default [OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/) which can be used for all OpenId connections including AzureAD external authentication. + +You don't have to use `appsettings.json` configuration but it is a good practice to set AzureAD information in the `appsettings.json`. + +To get the AzureAD information from `appsettings.json`, which will be used in `OpenIdConnectOptions` configuration, simply add a new section to `appsettings.json` located in your **.Web** project: + +````json + "AzureAd": { + "Instance": "https://login.microsoftonline.com/", + "TenantId": "", + "ClientId": "", + "Domain": "domain.onmicrosoft.com", + "CallbackPath": "/signin-azuread-oidc" + } +```` + +Then, In your **.Web** project; you can modify the `ConfigureAuthentication` method located in your **ApplicationWebModule** with the following: + +````csharp +private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) + { + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier); + + context.Services.AddAuthentication() + .AddIdentityServerAuthentication(options => + { + options.Authority = configuration["AuthServer:Authority"]; + options.RequireHttpsMetadata = false; + options.ApiName = "BookStore"; + }) + .AddOpenIdConnect("AzureOpenId", "Azure Active Directory OpenId", options => + { + options.Authority = "https://login.microsoftonline.com/" + configuration["AzureAd:TenantId"] + "/v2.0/"; + options.ClientId = configuration["AzureAd:ClientId"]; + options.ResponseType = OpenIdConnectResponseType.CodeIdToken; + options.CallbackPath = configuration["AzureAd:CallbackPath"]; + options.RequireHttpsMetadata = false; + options.SaveTokens = true; + options.GetClaimsFromUserInfoEndpoint = true; + + options.Scope.Add("email"); + }); + } +```` + +And that's it, integration is completed. Keep on mind that you can connect any other external authentication providers. + +## The Source Code + +You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization). + +# FAQ + +* Help! `GetExternalLoginInfoAsync` returns `null`! + + * There can be 2 reasons for this; + + 1. You are trying to authenticate against wrong scheme. Check if you set **SignInScheme** to `IdentityConstants.ExternalScheme`: + + ````csharp + options.SignInScheme = IdentityConstants.ExternalScheme; + ```` + + 2. Your `ClaimTypes.NameIdentifier` is `null`. Check if you added claim mapping: + + ````csharp + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier); + ```` + + +* Help! I am getting ***System.ArgumentNullException: Value cannot be null. (Parameter 'userName')*** error! + + + * This occurs when you use Azure Authority **v2.0 endpoint** without requesting `email` scope. [Abp checks unique email to create user](https://github.com/abpframework/abp/blob/037ef9abe024c03c1f89ab6c933710bcfe3f5c93/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L208). Simply add + + ````csharp + options.Scope.Add("email"); + ```` + + to your openid configuration. + +* Help! I keep getting ***AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application*** error! + + * If you set your **CallbackPath** in appsettings as: + + ````csharp + "AzureAd": { + ... + "CallbackPath": "/signin-azuread-oidc" + } + ```` + + your **Redirect URI** of your application in azure portal must be with domain like `https://localhost:44320/signin-azuread-oidc`, not only `/signin-azuread-oidc`. + +* Help! I keep getting ***AADSTS700051: The response_type 'token' is not enabled for the application.*** error! + + * This error occurs when you request **token** (access token) along with **id_token** without enabling Access tokens on Azure portal app registrations. Simply tick **Access tokens** checkbox located on top of ID tokens to be able to request token aswell. + +* Help! I keep getting ***AADSTS7000218: The request body must contain the following parameter: 'client_assertion' or 'client_secret*** error! + + * This error occurs when you request **code** along with **id_token**. You need to add **client secret** on azure portal app registrations, under **Certificates & secrets** menu. Afterwards, you need to add openid configuration option like: + + ````csharp + options.ClientSecret = "Value of your secret on azure portal"; + ```` + +* How can I **debug/watch** which claims I get before they get mapped? + + * You can add a simple event under openid configuration to debug before mapping like: + + ````csharp + options.Events.OnTokenValidated = (async context => + { + var claimsFromOidcProvider = context.Principal.Claims.ToList(); + await Task.CompletedTask; + }); + ```` diff --git a/docs/en/Community-Articles/2020-05-09-Customize-the-Login-Page-for-MVC-Razor-Page-Applications/POST.md b/docs/en/Community-Articles/2020-05-09-Customize-the-Login-Page-for-MVC-Razor-Page-Applications/POST.md new file mode 100644 index 0000000000..562d545ac6 --- /dev/null +++ b/docs/en/Community-Articles/2020-05-09-Customize-the-Login-Page-for-MVC-Razor-Page-Applications/POST.md @@ -0,0 +1,108 @@ +# How to Customize the Login Page for MVC / Razor Page Applications + +When you create a new application using the [application startup template](https://docs.abp.io/en/abp/latest/Startup-Templates/Application), source code of the login page will not be inside your solution, so you can not directly change it. The login page comes from the [Account Module](https://docs.abp.io/en/abp/latest/Modules/Account) that is used a [NuGet package](https://www.nuget.org/packages/Volo.Abp.Account.Web) reference. + +This document explains how to customize the login page for your own application. + +## Create a Login PageModel + +Create a new class inheriting from the [LoginModel](https://github.com/abpframework/abp/blob/037ef9abe024c03c1f89ab6c933710bcfe3f5c93/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs) of the Account module. + +````csharp +public class CustomLoginModel : LoginModel +{ + public CustomLoginModel( + Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemeProvider, + Microsoft.Extensions.Options.IOptions accountOptions) + : base(schemeProvider, accountOptions) + { + } +} +```` + +> Naming convention is important here. If your class name doesn't end with `LoginModel`, you need to manually replace the `LoginModel` using the [dependency injection](https://docs.abp.io/en/abp/latest/Dependency-Injection) system. + +Then you can override any method you need and add new methods and properties needed by the UI. + +## Overriding the Login Page UI + +Create folder named **Account** under **Pages** directory and create a **Login.cshtml** under this folder. It will automatically override the `Login.cshtml` file defined in the Account Module. + +A good way to customize a page is to copy its source code. [Click here](https://github.com/abpframework/abp/blob/dev/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml) for the source code of the login page. At the time this document has been written, the source code was like below: + +````xml +@page +@using Volo.Abp.Account.Settings +@using Volo.Abp.Settings +@model Acme.BookStore.Web.Pages.Account.CustomLoginModel +@inject Volo.Abp.Settings.ISettingProvider SettingProvider +@if (Model.EnableLocalLogin) +{ +
+
+

@L["Login"]

+ @if (await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled)) + { + + @L["AreYouANewUser"] + @L["Register"] + + } +
+ + +
+ + + +
+
+ + + +
+
+ +
+ @L["Login"] +
+
+ + +
+} + +@if (Model.VisibleExternalProviders.Any()) +{ +
+

@L["UseAnotherServiceToLogIn"]

+
+ + + @foreach (var provider in Model.VisibleExternalProviders) + { + + } +
+
+} + +@if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any()) +{ +
+ @L["InvalidLoginRequest"] + @L["ThereAreNoLoginSchemesConfiguredForThisClient"] +
+} +```` + +Just changed the `@model` to `Acme.BookStore.Web.Pages.Account.CustomLoginModel` to use the customized `PageModel` class. You can change it however your application needs. + +## The Source Code + +You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization). \ No newline at end of file diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/POST.md b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/POST.md new file mode 100644 index 0000000000..915d4ef5c1 --- /dev/null +++ b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/POST.md @@ -0,0 +1,462 @@ +# Real Time Messaging In A Distributed Architecture Using Abp Framework, SingalR & RabbitMQ + +In this article, we will build a basic real time messaging application in a distributed architecture. We will use [Abp Framework](https://abp.io) for infrastructure and tiered startup template, [SignalR](https://dotnet.microsoft.com/apps/aspnet/signalr) for real time server-client communication and [RabbitMQ](https://www.rabbitmq.com/) as the distributed event bus. + +When Web & API tiers are separated, it is impossible to directly send a server-to-client message from the HTTP API. This is also true for a microservice architected application. We suggest to use the distributed event bus to deliver the message from API application to the web application, then to the client. + + +![data flow](dataflow-diagram.png) + +Above, you can see the data-flow that we will implement in this article. This diagram represents how data will flow in our application when **Client 1** sends a message to **Client 2**. It is explained in 5 steps: + +1. **Client 1** sends a message data to **Web Application** via REST call. +2. **Web Application** redirects the message data to **Http Api**. +3. The message data is processed in **Http Api** and **Http Api** publishes an event that holds the data that will be sent to **Client 2**. +4. **Web application**, that is subscribed to that event, receives it. +5. **Web Application** sends the message to **Client 2**. + +For this example flow, we could send message from **Client 1** to **Client 2** directly on the **SignalR Hub**. However, what we are trying here to demonstrate is sending a real-time message from the **Http Api** to a specific user who is connected to the web application. + +## Implementation + +### Startup template and initial run + +[Abp Framework](https://www.abp.io) offers startup templates to get into the business faster. We can download a new tiered startup template using [Abp CLI](https://docs.abp.io/en/abp/latest/CLI): + +`abp new SignalRTieredDemo --tiered` + +After download is finished, we run ***.DbMigrator** project to create the database and seed initial data (admin user, role etc). Then we run ***.IdentityServer**, ***.HttpApi.Host** and ***.Web** to see our application working. + +### Creating Application Layer + +We create an [application service](https://docs.abp.io/en/abp/latest/Application-Services) that publishes the message as event. + +In ***.Application.Contracts** project: + +````csharp +using System.Threading.Tasks; +using Volo.Abp.Application.Services; + +namespace SignalRTieredDemo +{ + public interface IChatAppService : IApplicationService + { + Task SendMessageAsync(SendMessageInput input); + } +} +```` +Input DTO for SendMessageAsync method: + +````csharp +namespace SignalRTieredDemo +{ + public class SendMessageInput + { + public string TargetUserName { get; set; } + + public string Message { get; set; } + } +} +```` +Event transfer object (ETO) for communication on event bus: + +````csharp +using System; + +namespace SignalRTieredDemo +{ + public class ReceivedMessageEto + { + public string ReceivedText { get; set; } + + public Guid TargetUserId { get; set; } + + public string SenderUserName { get; set; } + + public ReceivedMessageEto( + Guid targetUserId, string senderUserName, string receivedText) + { + ReceivedText = receivedText; + TargetUserId = targetUserId; + SenderUserName = senderUserName; + } + } +} + +```` + +In ***.Application** project: + +````csharp +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Identity; + +namespace SignalRTieredDemo +{ + public class ChatAppService: SignalRTieredDemoAppService, IChatAppService + { + private readonly IIdentityUserRepository _identityUserRepository; + private readonly ILookupNormalizer _lookupNormalizer; + private readonly IDistributedEventBus _distributedEventBus; + + public ChatAppService(IIdentityUserRepository identityUserRepository, ILookupNormalizer lookupNormalizer, IDistributedEventBus distributedEventBus) + { + _identityUserRepository = identityUserRepository; + _lookupNormalizer = lookupNormalizer; + _distributedEventBus = distributedEventBus; + } + + public async Task SendMessageAsync(SendMessageInput input) + { + var targetId = (await _identityUserRepository.FindByNormalizedUserNameAsync(_lookupNormalizer.NormalizeName(input.TargetUserName))).Id; + + await _distributedEventBus.PublishAsync(new ReceivedMessageEto(targetId, CurrentUser.UserName, input.Message)); + } + } +} +```` + +### Creating API Layer + +We create an endpoint for sending message that redirects the process to application layer: + +In **controllers** folder of ***.HttpApi** project: + +````csharp +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc; + +namespace SignalRTieredDemo.Controllers +{ + [Route("api/app/chat")] + public class ChatController : AbpController, IChatAppService + { + private readonly IChatAppService _chatAppService; + + public ChatController(IChatAppService chatAppService) + { + _chatAppService = chatAppService; + } + + [HttpPost] + [Route("send-message")] + public async Task SendMessageAsync(SendMessageInput input) + { + await _chatAppService.SendMessageAsync(input); + } + } +} +```` +### Adding SignalR + +To add SignalR to our solution, we add `Volo.Abp.AspNetCore.SignalR` nuget package to ***.Web** project. + +And then add `AbpAspNetCoreSignalRModule` dependency: + +````csharp + +namespace SignalRTieredDemo.Web +{ + [DependsOn( + ... + typeof(AbpAspNetCoreSignalRModule) // <--- + )] + public class SignalRTieredDemoWebModule : AbpModule + { +```` + +Also, we need to add [@abp/signalr](https://www.npmjs.com/package/@abp/signalr) npm package to package.json in ***.Web** project, then run **yarn** and **gulp** commands. + +`````json +{ + . + . + "dependencies": { + . + . + "@abp/signalr": "^2.9.0" + } +} +````` + +*Remember to add the latest package version.* + +You can find more information for Abp SignalR Integration on [the related document](https://docs.abp.io/en/abp/latest/SignalR-Integration). + +### Creating A Hub + +We need a hub for SignalR connection. We can inherit it from `AbpHup` base class. + +In ***.Web** project: + +````csharp +using Microsoft.AspNetCore.Authorization; +using Volo.Abp.AspNetCore.SignalR; + +namespace SignalRTieredDemo.Web +{ + [Authorize] + public class ChatHub : AbpHub + { + } +} + +```` + +While you could inherit from the standard `Hub` class, `AbpHub` has some common services pre-injected as base properties, which is useful on your development. + +### Adding & Configuring RabbitMQ + +To add RabbitMQ to our solution, we add `Volo.Abp.EventBus.RabbitMQ` nuget package to ***.HttpApi.Host** and ***.Web** projects. + +Launch a **command line**, navigate to directory where ***.HttpApi.Host.csproj** file exist, and run the command below using [Abp CLI](https://docs.abp.io/en/abp/latest/CLI): + +````bash + abp add-package Volo.Abp.EventBus.RabbitMQ +```` + +Then do the same for ***.Web** project. + +After we add the package, we configure RabbitMQ by adding configuration in **appsettings.json** files of those projects. + +For ***.HttpApi.Host** project: + +````json +{ + ... + "RabbitMQ": { + "Connections": { + "Default": { + "HostName": "localhost" + } + }, + "EventBus": { + "ClientName": "SignalRTieredDemo_HttpApi", + "ExchangeName": "SignalRTieredDemoTest" + } + }, + ... +} +```` +For ***.Web** project: +````json +{ + ... + "RabbitMQ": { + "Connections": { + "Default": { + "HostName": "localhost" + } + }, + "EventBus": { + "ClientName": "SignalRTieredDemo_Web", + "ExchangeName": "SignalRTieredDemoTest" + } + }, + ... +} + +```` + +### Handling New Message Event + +Once we publish a new message event from `Http Api`, we must to handle it in `Web Application`. Therefore we need an event handler in ***.Web** Project: + +````csharp +using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus.Distributed; + +namespace SignalRTieredDemo.Web +{ + public class ReceivedMessageEventHandler : + IDistributedEventHandler, + ITransientDependency + { + private readonly IHubContext _hubContext; + + public ReceivedMessageEventHandler(IHubContext hubContext) + { + _hubContext = hubContext; + } + + public async Task HandleEventAsync(ReceivedMessageEto eto) + { + var message = $"{eto.SenderUserName}: {eto.ReceivedText}"; + + await _hubContext.Clients + .User(eto.TargetUserId.ToString()) + .SendAsync("ReceiveMessage", message); + } + } +} +```` + +### Creating Chat Page + +We create the files below in **Pages** folder of ***.Web** Project. + + **Chat.cshtml**: + +````html +@page +@using Volo.Abp.AspNetCore.Mvc.UI.Packages.SignalR +@model SignalRTieredDemo.Web.Pages.ChatModel +@section styles { + +} +@section scripts { + + +} +

Chat

+ +
+ + +
All Messages:
+
    +
+
+ +
+ + + + + + + + + + + + + + + + + +
+
+
+
+```` + + **Chat.cshtml.cs**: + +````csharp +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace SignalRTieredDemo.Web.Pages +{ + public class ChatModel : PageModel + { + public void OnGet() + { + } + } +} +```` + +**Chat.css**: + +````css +#MessageList { + border: 1px solid gray; + height: 400px; + overflow: auto; + list-style: none; + padding-left: 0; + padding: 10px; +} + +#TargetUser { + width: 100%; +} + +#Message { + width: 100%; +} +```` + +**Chat.js**: + +````javascript +$(function () { + var connection = new signalR.HubConnectionBuilder().withUrl("/signalr-hubs/chat").build(); + + connection.on("ReceiveMessage", function (message) { + console.log(message); + $('#MessageList').append('
  • ' + message + '
  • '); + }); + + connection.start().then(function () { + + }).catch(function (err) { + return console.error(err.toString()); + }); + + $('#SendMessageButton').click(function (e) { + e.preventDefault(); + + var targetUserName = $('#TargetUser').val(); + var message = $('#Message').val(); + $('#Message').val(''); + + + signalRTieredDemo.controllers.chat.sendMessage({ + targetUserName: targetUserName, + message: message + }).then(function() { + $('#MessageList') + .append('
  • ' + abp.currentUser.userName + ': ' + message + '
  • '); + }); + + }); +}); +```` + +Then we can add this new page to menu on ***MenuContributor.cs** in **Menus** folder: + +````csharp + ... + public class SignalRTieredDemoMenuContributor : IMenuContributor + { + ... + private Task ConfigureMainMenuAsync(MenuConfigurationContext context) + { + ... + context.Menu.Items.Add(new ApplicationMenuItem("SignalRDemo.Chat", "Chat", "/Chat")); // <-- We add this line + + return Task.CompletedTask; + } + ... + } +```` + +## Running & Testing + +We run ***.IdentityServer**, ***.HttpApi.Host** and ***.Web** in order. After ***.Web** project is ran, firstly login with `admin` username and `1q2w3E*` password. + +![click on login](login1.png) + +![login with `admin` username and `1q2w3E*` password.](login2.png) + +After we login, go to `/Identity/Users` page and create a new user. So that we can chat with them. + +![create a new user](new-user.png) + +Then we open the application in another browser and login with the user we created above. Now we can go to chat page and start messaging: + +![messaging](chat.png) + +We can test with more user. All sent and incoming messages are displayed in the left box. + +### Source code + +Source code of the final application can be found on the [GitHub repository](https://github.com/abpframework/abp-samples/tree/master/SignalRTieredDemo). diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/chat.png b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/chat.png new file mode 100644 index 0000000000..b29daa9492 Binary files /dev/null and b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/chat.png differ diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/dataflow-diagram.png b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/dataflow-diagram.png new file mode 100644 index 0000000000..bb81ddf37a Binary files /dev/null and b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/dataflow-diagram.png differ diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/header.png b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/header.png new file mode 100644 index 0000000000..6e5a46b780 Binary files /dev/null and b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/header.png differ diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login1.png b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login1.png new file mode 100644 index 0000000000..4e371e2ee9 Binary files /dev/null and b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login1.png differ diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login2.png b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login2.png new file mode 100644 index 0000000000..68c5a3259d Binary files /dev/null and b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/login2.png differ diff --git a/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/new-user.png b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/new-user.png new file mode 100644 index 0000000000..2ffdf99d45 Binary files /dev/null and b/docs/en/Community-Articles/2020-05-29-Real-Time-Messaging-In-A-Distributed-Architecture-Using-Abp-Framework-SingalR-RabbitMQ/new-user.png differ diff --git a/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/POST.md b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/POST.md new file mode 100644 index 0000000000..560e15e288 --- /dev/null +++ b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/POST.md @@ -0,0 +1,396 @@ +# File Upload/Download with BLOB Storage System in ASP.NET Core & ABP Framework + +## Introduction + +This step-by-step article describes how to upload a file to a Web server and also download by client with using ASP.NET Core & ABP Framework. By following this article, you will create a web project and its related code to upload and download files. + +Before the creating application, we need to know some fundamentals. + +## BLOB Storing + +It is typical to **store file contents** in an application and read these file contents on need. Not only files, but you may also need to save various types of **large binary objects**, a.k.a. [BLOB](https://en.wikipedia.org/wiki/Binary_large_object)s, into a **storage**. For example, you may want to save user profile pictures. + +A BLOB is a typically **byte array**. There are various places to store a BLOB item; storing in the local file system, in a shared database or on the [Azure BLOB storage](https://azure.microsoft.com/en-us/services/storage/blobs/) can be options. + +The ABP Framework provides an abstraction to work with BLOBs and provides some pre-built storage providers that you can easily integrate to. Having such an abstraction has some benefits; + +- You can **easily integrate** to your favorite BLOB storage provides with a few lines of configuration. +- You can then **easily change** your BLOB storage without changing your application code. +- If you want to create **reusable application modules**, you don't need to make assumption about how the BLOBs are stored. + +ABP BLOB Storage system is also compatible to other ABP Framework features like [multi-tenancy](https://docs.abp.io/en/abp/latest/Multi-Tenancy). + +To get more information about ABP BLOB Storing system, please check this [documentation](https://docs.abp.io/en/abp/latest/Blob-Storing). + +## Preparing the Project + +### Startup template and the initial run + +Abp Framework offers startup templates to get into the business faster. We can download a new startup template using Abp CLI: + +`abp new FileActionsDemo -m none` + +After the download is finished, we run `FileActionsDemo.DbMigrator` project to create the database and seed initial data (admin user, role, etc). Then we run `FileActionsDemo.Web` to see our application working. + +> _Default admin username is **admin** and password is **1q2w3E\***_ + +![initial-project](initial-project.png) + +### Adding Blob Storing Module + +For this article, we use [Blob Storing Database Provider](https://docs.abp.io/en/abp/latest/Blob-Storing-Database). + +You can use [Azure](https://docs.abp.io/en/abp/latest/Blob-Storing-Azure) or [File System](https://docs.abp.io/en/abp/latest/Blob-Storing-File-System) providers also. + +Open a command prompt (terminal) in the folder containing your solution (.sln) file and run the following command: + +`abp add-module Volo.Abp.BlobStoring.Database` + +This action will add the module depencies and also module migration. After this action, run `FileActionsDemo.DbMigrator` to update the database. + +### Setting up Blob Storaging + +BLOB Strorage system works with `Containers`. Before the using blob storage, we need to create our blob container. + +Create a class that name `MyFileContainer` at the `FileActionsDemo.Domain` project. + +```csharp +using Volo.Abp.BlobStoring; + +namespace FileActionsDemo +{ + [BlobContainerName("my-file-container")] + public class MyFileContainer + { + + } +} +``` + +That's all, we can start to use BLOB storing in our application. + +## Creating Application Layer + +Before the creating Application Service, we need to create some [DTO](https://docs.abp.io/en/abp/latest/Data-Transfer-Objects)s that used by Application Service. + +Create following DTOs in `FileActionsDemo.Application.Contracts` project. + +- `BlobDto.cs` + + ```csharp + namespace FileActionsDemo + { + public class BlobDto + { + public byte[] Content { get; set; } + + public string Name { get; set; } + } + } + ``` + +- `GetBlobRequestDto.cs` + + ```csharp + using System.ComponentModel.DataAnnotations; + + namespace FileActionsDemo + { + public class GetBlobRequestDto + { + [Required] + public string Name { get; set; } + } + } + ``` + +- `SaveBlobInputDto.cs` + + ```csharp + using System.ComponentModel.DataAnnotations; + + namespace FileActionsDemo + { + public class SaveBlobInputDto + { + public byte[] Content { get; set; } + + [Required] + public string Name { get; set; } + } + } + ``` + +Create `IFileAppService.cs` interface at the same place with DTOs. + +- `IFileAppService` + + ```csharp + using System.Threading.Tasks; + using Volo.Abp.Application.Services; + + namespace FileActionsDemo + { + public interface IFileAppService : IApplicationService + { + Task SaveBlobAsync(SaveBlobInputDto input); + + Task GetBlobAsync(GetBlobRequestDto input); + } + } + ``` + +After creating DTOs and interface, `FileActionsDemo.Application.Contracts` project should be like as following image. + +![application-contracts-project](application-contracts-project.png) + +Then we can create our Application Service. + +Create `FileAppService.cs` in `FileActionsDemo.Application` project. + +```csharp +using System.Threading.Tasks; +using Volo.Abp.Application.Services; +using Volo.Abp.BlobStoring; + +namespace FileActionsDemo +{ + public class FileAppService : ApplicationService, IFileAppService + { + private readonly IBlobContainer _fileContainer; + + public FileAppService(IBlobContainer fileContainer) + { + _fileContainer = fileContainer; + } + + public async Task SaveBlobAsync(SaveBlobInputDto input) + { + await _fileContainer.SaveAsync(input.Name, input.Content, true); + } + + public async Task GetBlobAsync(GetBlobRequestDto input) + { + var blob = await _fileContainer.GetAllBytesAsync(input.Name); + + return new BlobDto + { + Name = input.Name, + Content = blob + }; + } + } +} +``` + +As you see in previous code block, we inject `IBlobContainer` to our app service. It will handle all blob actions for us. + +- `SaveBlobAsync` method uses `SaveAsync` of `IBlobContainer` to save the given blob to storage, this is a simple example so we don't check is there any file exist with same name. We sent blob name, blob content and `true` for `overrideExisting` parameter. + +- `GetBlobAsync` method is uses `GetAllBytesAsync` of `IBlobContainer` to get blob content by name. + +We finished the application layer for this project. After that we will create a `Controller` for API and `Razor Page` for UI. + +## Creating Controller + +Create `FileController.cs` in your `FileActionsDemo.HttpApi` project. + +```csharp +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc; + +namespace FileActionsDemo +{ + public class FileController : AbpController + { + private readonly IFileAppService _fileAppService; + + public FileController(IFileAppService fileAppService) + { + _fileAppService = fileAppService; + } + + [HttpGet] + [Route("download/{fileName}")] + public async Task DownloadAsync(string fileName) + { + var fileDto = await _fileAppService.GetBlobAsync(new GetBlobRequestDto{ Name = fileName }); + + return File(fileDto.Content, "application/octet-stream", fileDto.Name); + } + } +} +``` + +As you see, `FileController` injects `IFileAppService` that we defined before. This controller has only one endpoint. + +`DownloadAsync` is using to send file from server to client. + +This endpoint is requires only a `string` parameter, then we use that parameter to get stored blob. If blob is exist, we return a `File` result so download process can start. + +## Creating User Interface + +We will create only one page to prove download and upload actions are working. + +Create folder that name `Files` in your `Pages` folder at `FileActionsDemo.Web` project. + +Create a Razor page that name `Index` with its model. + +- `Index.cshtml.cs` + +```csharp +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace FileActionsDemo.Web.Pages.Files +{ + public class Index : AbpPageModel + { + [BindProperty] + public UploadFileDto UploadFileDto { get; set; } + + private readonly IFileAppService _fileAppService; + + public bool Uploaded { get; set; } = false; + + public Index(IFileAppService fileAppService) + { + _fileAppService = fileAppService; + } + + public void OnGet() + { + + } + + public async Task OnPostAsync() + { + using (var memoryStream = new MemoryStream()) + { + await UploadFileDto.File.CopyToAsync(memoryStream); + + await _fileAppService.SaveBlobAsync( + new SaveBlobInputDto + { + Name = UploadFileDto.Name, + Content = memoryStream.ToArray() + } + ); + } + + return Page(); + } + } + + public class UploadFileDto + { + [Required] + [Display(Name = "File")] + public IFormFile File { get; set; } + + [Required] + [Display(Name = "Filename")] + public string Name { get; set; } + } +} +``` + +As you see, we use `UploadFileDto` as a `BindProperty` and we inject `IFileAppService` to upload files. + +The `UploadFileDto` is requires a `string` parameter for using as a blob name and a `IFormFile` that sent by user. + +At the post action (`OnPostAsync`), if everything is well, we use `MemoryStream` to get all bytes from file content. + +Then we save file with `SaveBlobAsync` method of `IFileAppService`. + +- `Index.cshtml` + +```csharp +@page +@model FileActionsDemo.Web.Pages.Files.Index + +@section scripts{ + +} + + + +

    File Upload and Download

    +
    + + + +

    Upload File

    +
    +
    + + + + + +
    +
    + + +

    Download File

    +
    +
    +
    + * + +
    + + +
    +
    +
    +
    +
    +``` + +We divided the page vertically, left side will be using for upload and right side will be using for download. We use [ABP Tag Helpers](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Tag-Helpers/Index) to create page. + +- `index.js` + +```javascript +$(function () { + var DOWNLOAD_ENDPOINT = "/download"; + + var downloadForm = $("form#DownloadFile"); + + downloadForm.submit(function (event) { + event.preventDefault(); + + var fileName = $("#fileName").val().trim(); + + var downloadWindow = window.open( + DOWNLOAD_ENDPOINT + "/" + fileName, + "_blank" + ); + downloadWindow.focus(); + }); + + $("#UploadFileDto_File").change(function () { + var fileName = $(this)[0].files[0].name; + + $("#UploadFileDto_Name").val(fileName); + }); +}); +``` + +This jQuery codes are using for download. Also we wrote a simple code to autofill `Filename` input when user selects a file. + +After creating razor page and js file, `FileActionsDemo.Web` project should be like as following image. + +![web-project](web-project.png) + +## Result + +After completing code tutorial, run `FileActionsDemo.Web` project and go `/Files`. You can upload any file with any name and also download those uploaded files. + +![file-up](file-upload-result.gif) diff --git a/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/application-contracts-project.png b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/application-contracts-project.png new file mode 100644 index 0000000000..924adab6f6 Binary files /dev/null and b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/application-contracts-project.png differ diff --git a/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/file-upload-result.gif b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/file-upload-result.gif new file mode 100644 index 0000000000..e59ee890c6 Binary files /dev/null and b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/file-upload-result.gif differ diff --git a/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/initial-project.png b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/initial-project.png new file mode 100644 index 0000000000..0e611deebe Binary files /dev/null and b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/initial-project.png differ diff --git a/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/web-project.png b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/web-project.png new file mode 100644 index 0000000000..f22d2dbdcb Binary files /dev/null and b/docs/en/Community-Articles/2020-07-21-File-Upload-Download-With-BLOB-Storage-System-in-ASPNET-Core-ABP-Framework/web-project.png differ diff --git a/docs/en/Community-Articles/2020-08-07-Passwordless-Authentication/POST.md b/docs/en/Community-Articles/2020-08-07-Passwordless-Authentication/POST.md new file mode 100644 index 0000000000..1b7cef0867 --- /dev/null +++ b/docs/en/Community-Articles/2020-08-07-Passwordless-Authentication/POST.md @@ -0,0 +1,276 @@ +# Implementing Passwordless Authentication in ASP.NET Core Identity + +## Introduction + +To allow a user login with a magic URL, you need to implement a custom token provider. In this tutorial, we will show you how to add a custom token provider to authenticate a user with a link, instead of entering a password. + +### Source Code + +The completed sample is available on [the GitHub repository](https://github.com/abpframework/abp-samples/tree/master/PasswordlessAuthentication). + +## Creating the Solution + +Before starting to the development, create a new solution named `PasswordlessAuthentication` and run it by following the [getting started tutorial](https://docs.abp.io/en/abp/latest/Getting-Started?UI=MVC&DB=EF&Tiered=No). + +## Step-1 + +Create a class named **PasswordlessLoginProvider** in your ***.Web** project: + +```csharp +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; + +namespace PasswordlessAuthentication.Web +{ + public class PasswordlessLoginProvider : TotpSecurityStampBasedTokenProvider + where TUser : class + { + public override Task CanGenerateTwoFactorTokenAsync(UserManager manager, TUser user) + { + return Task.FromResult(false); + } + + //We need to override this method as well. + public override async Task GetUserModifierAsync(string purpose, UserManager manager, TUser user) + { + var userId = await manager.GetUserIdAsync(user); + + return "PasswordlessLogin:" + purpose + ":" + userId; + } + } +} +``` +## Step-2 + +Create **IdentityBuilderExtensions.cs** in your ***.Web** project. We will use this extension method in the `ConfigureServices`. + +```csharp +using Microsoft.AspNetCore.Identity; + +namespace PasswordlessAuthentication.Web +{ + public static class IdentityBuilderExtensions + { + public static IdentityBuilder AddPasswordlessLoginProvider(this IdentityBuilder builder) + { + var userType = builder.UserType; + var totpProvider = typeof(PasswordlessLoginProvider<>).MakeGenericType(userType); + return builder.AddTokenProvider("PasswordlessLoginProvider", totpProvider); + } + } +} +``` +## Step-3 + +Add the token provider to the `Identity` middleware. To do this, find the module class (eg: `PasswordlessAuthenticationWebModule.cs` in here) in your ***.Web** project and add the below into the `ConfigureServices()` method. + +```csharp +public override void ConfigureServices(ServiceConfigurationContext context) +{ + //... + context.Services + .GetObject() + .AddDefaultTokenProviders() + .AddPasswordlessLoginProvider(); +} +``` + +## Step-4 + +We need to create a user interface to be able to generate the magic login link. To do this quickly, open your existing **Index.cshtml.cs** in your ***.Web** project. It's under `Pages` folder. And copy-paste the below content. + +**Index.cshtml.cs** + +```csharp +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Identity; + +namespace PasswordlessAuthentication.Web.Pages +{ + public class IndexModel : PasswordlessAuthenticationPageModel + { + protected IdentityUserManager UserManager { get; } + + private readonly IIdentityUserRepository _userRepository; + + public string PasswordlessLoginUrl { get; set; } + + public string Email { get; set; } + + public IndexModel(IdentityUserManager userManager, IIdentityUserRepository userRepository) + { + UserManager = userManager; + _userRepository = userRepository; + } + + public ActionResult OnGet() + { + if (!CurrentUser.IsAuthenticated) + { + return Redirect("/Account/Login"); + } + + return Page(); + } + + //added for passwordless authentication + public async Task OnPostGeneratePasswordlessTokenAsync() + { + var adminUser = await _userRepository.FindByNormalizedUserNameAsync("admin"); + + var token = await UserManager.GenerateUserTokenAsync(adminUser, "PasswordlessLoginProvider", + "passwordless-auth"); + + PasswordlessLoginUrl = Url.Action("Login", "Passwordless", + new {token = token, userId = adminUser.Id.ToString()}, Request.Scheme); + + return Page(); + } + } +} +``` + +We added `OnPostGeneratePasswordlessTokenAsync()` action to generate the link. We will generate a link for the **admin** user. Therefore, we injected `IIdentityUserRepository` to get admin user Id. Using the `UserManager.GenerateUserTokenAsync()` method, we generated a token. After that, we created the URL with the admin user Id and the token. Now we will show the `PasswordlessLoginUrl` on the page. + +## Step-5 + +Create a class named **PasswordlessAuthenticationMenus** under `Menus` folder in your ***.Web** project. And set the content as below. + +```csharp +namespace PasswordlessAuthentication.Web.Menus +{ + public class PasswordlessAuthenticationMenus + { + public const string GroupName = "PasswordlessAuthentication"; + + public const string Home = GroupName + ".Home"; + } +} +``` + +## Step-6 + +Open your **Index.cshtml** and set the content as below. We added a form that posts to `GeneratePasswordlessToken` action in the razor page. And it will set the `PasswordlessLoginUrl` field. + +```html +@page +@inject IHtmlLocalizer L +@using Microsoft.AspNetCore.Mvc.Localization +@using PasswordlessAuthentication.Localization +@using PasswordlessAuthentication.Web.Menus +@using Volo.Abp.AspNetCore.Mvc.UI.Layout +@model PasswordlessAuthentication.Web.Pages.IndexModel + +@{ + ViewBag.PageTitle = "Home"; +} +@inject IPageLayout PageLayout; +@{ + PageLayout.Content.Title = L["Home"].Value; + PageLayout.Content.BreadCrumb.Add(L["Menu:Home"].Value); + PageLayout.Content.MenuItemName = PasswordlessAuthenticationMenus.Home; +} + + + +
    + + Generate passwordless token link + + @if (Model.PasswordlessLoginUrl != null) + { + + [@Model.PasswordlessLoginUrl](/en/commercial/latest/how-to/@Model.PasswordlessLoginUrl) + + } + +
    +
    +
    +``` +## Step-7 + +We implemented token generation infrastructure, now it's time validate the token and let the user in. To do this create a folder named `Controllers` in your ***.Web** project and create a controller, named **PasswordlessController** inside it: + +```csharp +using System; +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.Identity; +using Volo.Abp.Security.Claims; +using Volo.Abp.Users; + +namespace PasswordlessAuthentication.Web.Controllers +{ + public class PasswordlessController : AbpController + { + protected IdentityUserManager UserManager { get; } + + public PasswordlessController(IdentityUserManager userManager) + { + UserManager = userManager; + } + + public virtual async Task Login(string token, string userId) + { + var user = await UserManager.FindByIdAsync(userId); + + var isValid = await UserManager.VerifyUserTokenAsync(user, "PasswordlessLoginProvider", "passwordless-auth", token); + if (!isValid) + { + throw new UnauthorizedAccessException("The token " + token + " is not valid for the user " + userId); + } + + await UserManager.UpdateSecurityStampAsync(user); + + var roles = await UserManager.GetRolesAsync(user); + + var principal = new ClaimsPrincipal( + new ClaimsIdentity(CreateClaims(user, roles), IdentityConstants.ApplicationScheme) + ); + + await HttpContext.SignInAsync(IdentityConstants.ApplicationScheme, principal); + + return Redirect("/"); + } + + private static IEnumerable CreateClaims(IUser user, IEnumerable roles) + { + var claims = new List + { + new Claim("sub", user.Id.ToString()), + new Claim(AbpClaimTypes.UserId, user.Id.ToString()), + new Claim(AbpClaimTypes.Email, user.Email), + new Claim(AbpClaimTypes.UserName, user.UserName), + new Claim(AbpClaimTypes.EmailVerified, user.EmailConfirmed.ToString().ToLower()), + }; + + if (!string.IsNullOrWhiteSpace(user.PhoneNumber)) + { + claims.Add(new Claim(AbpClaimTypes.PhoneNumber, user.PhoneNumber)); + } + + foreach (var role in roles) + { + claims.Add(new Claim(AbpClaimTypes.Role, role)); + } + + return claims; + } + } +} +``` + +We created an endpoint for `/Passwordless/Login` that gets the token and the user Id. In this action, we find the user via repository and validate the token via `UserManager.VerifyUserTokenAsync()` method. If it's valid, we create claims of the user then call `HttpContext.SignInAsync` to be able to create an encrypted cookie and add it to the current response. Finally we redirect the page to the root URL. + +That's all! We created a passwordless login with 7 steps. + +## Source Code + +The completed sample is available on [the GitHub repository](https://github.com/abpframework/abp-samples/tree/master/PasswordlessAuthentication). \ No newline at end of file diff --git a/docs/en/Community-Articles/2020-08-12-Patch-Chrome-Login-Issue-For-IdentityServer4/POST.md b/docs/en/Community-Articles/2020-08-12-Patch-Chrome-Login-Issue-For-IdentityServer4/POST.md new file mode 100644 index 0000000000..718a949f3a --- /dev/null +++ b/docs/en/Community-Articles/2020-08-12-Patch-Chrome-Login-Issue-For-IdentityServer4/POST.md @@ -0,0 +1,212 @@ +# How to fix the Chrome login issue for the IdentityServer4 + +## Introduction + +When you use HTTP on your Identity Server 4 enabled website, users may not login because of the changes made by Chrome in the version 8x. This occurs when you use HTTP schema in your website. The issue is explained here https://docs.microsoft.com/en-gb/dotnet/core/compatibility/3.0-3.1#http-browser-samesite-changes-impact-authentication + +## How to solve it? + +### Step-1 + +Create the below extension in your ***.Web** project. + +```csharp +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class SameSiteCookiesServiceCollectionExtensions + { + /// + /// -1 defines the unspecified value, which tells ASPNET Core to NOT + /// send the SameSite attribute. With ASPNET Core 3.1 the + /// enum will have a definition for + /// Unspecified. + /// + private const SameSiteMode Unspecified = (SameSiteMode)(-1); + + /// + /// Configures a cookie policy to properly set the SameSite attribute + /// for Browsers that handle unknown values as Strict. Ensure that you + /// add the + /// into the pipeline before sending any cookies! + /// + /// + /// Minimum ASPNET Core Version required for this code: + /// - 2.1.14 + /// - 2.2.8 + /// - 3.0.1 + /// - 3.1.0-preview1 + /// Starting with version 80 of Chrome (to be released in February 2020) + /// cookies with NO SameSite attribute are treated as SameSite=Lax. + /// In order to always get the cookies send they need to be set to + /// SameSite=None. But since the current standard only defines Lax and + /// Strict as valid values there are some browsers that treat invalid + /// values as SameSite=Strict. We therefore need to check the browser + /// and either send SameSite=None or prevent the sending of SameSite=None. + /// Relevant links: + /// - https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-4.1 + /// - https://tools.ietf.org/html/draft-west-cookie-incrementalism-00 + /// - https://www.chromium.org/updates/same-site + /// - https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/ + /// - https://bugs.webkit.org/show_bug.cgi?id=198181 + /// + /// The service collection to register into. + /// The modified . + public static IServiceCollection ConfigureNonBreakingSameSiteCookies(this IServiceCollection services) + { + services.Configure(options => + { + options.MinimumSameSitePolicy = Unspecified; + options.OnAppendCookie = cookieContext => + CheckSameSite(cookieContext.Context, cookieContext.CookieOptions); + options.OnDeleteCookie = cookieContext => + CheckSameSite(cookieContext.Context, cookieContext.CookieOptions); + }); + + return services; + } + + private static void CheckSameSite(HttpContext httpContext, CookieOptions options) + { + if (options.SameSite == SameSiteMode.None) + { + var userAgent = httpContext.Request.Headers["User-Agent"].ToString(); + + if (DisallowsSameSiteNone(userAgent)) + { + options.SameSite = Unspecified; + } + } + } + + /// + /// Checks if the UserAgent is known to interpret an unknown value as Strict. + /// For those the property should be + /// set to . + /// + /// + /// This code is taken from Microsoft: + /// https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/ + /// + /// The user agent string to check. + /// Whether the specified user agent (browser) accepts SameSite=None or not. + private static bool DisallowsSameSiteNone(string userAgent) + { + // Cover all iOS based browsers here. This includes: + // - Safari on iOS 12 for iPhone, iPod Touch, iPad + // - WkWebview on iOS 12 for iPhone, iPod Touch, iPad + // - Chrome on iOS 12 for iPhone, iPod Touch, iPad + // All of which are broken by SameSite=None, because they use the + // iOS networking stack. + // Notes from Thinktecture: + // Regarding https://caniuse.com/#search=samesite iOS versions lower + // than 12 are not supporting SameSite at all. Starting with version 13 + // unknown values are NOT treated as strict anymore. Therefore we only + // need to check version 12. + if (userAgent.Contains("CPU iPhone OS 12") + || userAgent.Contains("iPad; CPU OS 12")) + { + return true; + } + + // Cover Mac OS X based browsers that use the Mac OS networking stack. + // This includes: + // - Safari on Mac OS X. + // This does not include: + // - Chrome on Mac OS X + // because they do not use the Mac OS networking stack. + // Notes from Thinktecture: + // Regarding https://caniuse.com/#search=samesite MacOS X versions lower + // than 10.14 are not supporting SameSite at all. Starting with version + // 10.15 unknown values are NOT treated as strict anymore. Therefore we + // only need to check version 10.14. + if (userAgent.Contains("Safari") + && userAgent.Contains("Macintosh; Intel Mac OS X 10_14") + && userAgent.Contains("Version/")) + { + return true; + } + + // Cover Chrome 50-69, because some versions are broken by SameSite=None + // and none in this range require it. + // Note: this covers some pre-Chromium Edge versions, + // but pre-Chromium Edge does not require SameSite=None. + // Notes from Thinktecture: + // We can not validate this assumption, but we trust Microsofts + // evaluation. And overall not sending a SameSite value equals to the same + // behavior as SameSite=None for these old versions anyways. + if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6")) + { + return true; + } + + if (GetChromeVersion(userAgent) >= 80) + { + return true; + } + + return false; + } + + private static int GetChromeVersion(string userAgent) + { + try + { + return Convert.ToInt32(userAgent.Split("Chrome/")[1].Split('.')[0]); + } + catch (Exception) + { + return 0; + } + } + } +} +``` + +### Step-2 + +Assume that your project name is *Acme.BookStore*. Then open `AcmeBookStoreWebModule.cs` class. + +Add the following line to `ConfigureServices()` method. + +```csharp + context.Services.ConfigureNonBreakingSameSiteCookies(); +``` +### Step-3 + +Go to`OnApplicationInitialization()` method in `AcmeBookStoreWebModule.cs` add `app.UseCookiePolicy();` + +```csharp +public override void OnApplicationInitialization(ApplicationInitializationContext context) +{ + var app = context.GetApplicationBuilder(); + var env = context.GetEnvironment(); + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseErrorPage(); + app.UseHsts(); + } + + app.UseCookiePolicy(); //<--- added this ---> + + //.... +} +``` + + + +It's all! You are ready to go! + + + +--- + +Referenced from https://www.thinktecture.com/en/identity/samesite/prepare-your-identityserver/ diff --git a/docs/en/Contribution/Index.md b/docs/en/Contribution/Index.md index 871ec0b985..83ec0bf05d 100644 --- a/docs/en/Contribution/Index.md +++ b/docs/en/Contribution/Index.md @@ -1,8 +1,12 @@ -## Contribution Guide +# Contribution Guide ABP is an [open source](https://github.com/abpframework) and community driven project. This guide is aims to help anyone wants to contribute to the project. -### Code Contribution +## community.abp.io + +If you want to write articles or "how to" guides related to the ABP Framework and ASP.NET Core, please submit your article to the [community.abp.io](https://community.abp.io/) web site. + +## Code Contribution You can always send pull requests to the Github repository. @@ -12,15 +16,15 @@ You can always send pull requests to the Github repository. Before making any change, please discuss it on the [Github issues](https://github.com/abpframework/abp/issues). In this way, no other developer will work on the same issue and your PR will have a better chance to be accepted. -#### Bug Fixes & Enhancements +### Bug Fixes & Enhancements You may want to fix a known bug or work on a planned enhancement. See [the issue list](https://github.com/abpframework/abp/issues) on Github. -#### Feature Requests +### Feature Requests If you have a feature idea for the framework or modules, [create an issue](https://github.com/abpframework/abp/issues/new) on Github or attend to an existing discussion. Then you can implement it if it's embraced by the community. -### Document Translation +## Document Translation You may want to translate the complete [documentation](https://abp.io/documents/) (including this one) to your mother language. If so, follow these steps: @@ -37,13 +41,13 @@ There are some fundamental documents need to be translated before publishing a l A new language is published after these minimum translations have been completed. -### Resource Localization +## Resource Localization ABP framework has a flexible [localization system](../Localization.md). You can create localized user interfaces for your own application. In addition to that, the framework and the [pre-build modules](https://docs.abp.io/en/abp/latest/Modules/Index) have localized texts. As an example, see [the localization texts for the Volo.Abp.UI package](https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json). -#### Using the "abp translate" command +### Using the "abp translate" command This is the recommended approach, since it automatically finds all missing texts for a specific culture and lets you to translate in one place. @@ -54,14 +58,10 @@ This is the recommended approach, since it automatically finds all missing texts * Once you done the translation, use `abp translate -a` command to apply changes to the related files. * Send a pull request on GitHub. -#### Manual Translation +### Manual Translation If you want to make a change on a specific resource file, you can find the file yourself, make the necessary change (or create a new file for your language) and send a pull request on GitHub. -### Blog Posts & Tutorials - -If you decide to create some tutorials or blog posts on ABP, please inform us (by creating a [Github issue](https://github.com/abpframework/abp/issues)), so we may add a link to your tutorial/post in the official documentation and we can announce it on our [Twitter account](https://twitter.com/abpframework). - -### Bug Report +## Bug Report If you find any bug, please [create an issue on the Github repository](https://github.com/abpframework/abp/issues/new). diff --git a/docs/en/CurrentUser.md b/docs/en/CurrentUser.md index e4aca40811..5a400dc098 100644 --- a/docs/en/CurrentUser.md +++ b/docs/en/CurrentUser.md @@ -1,4 +1,4 @@ -# Current User +# Current User It is very common to retrieve the information about the logged in user in a web application. The current user is the active user related to the current request in a web application. @@ -86,7 +86,7 @@ Beside these standard methods, there are some extension methods: ## ICurrentPrincipalAccessor -`ICurrentPrincipalAccessor` is the service that should be used (by the ABP Framework and your application code) whenever the current principle of the current user is needed. +`ICurrentPrincipalAccessor` is the service that should be used (by the ABP Framework and your application code) whenever the current principal of the current user is needed. For a web application, it gets the `User` property of the current `HttpContext`. For a non-web application, it returns the `Thread.CurrentPrincipal`. @@ -114,9 +114,9 @@ public class MyService : ITransientDependency } ```` -### Changing the Current Principle +### Changing the Current Principal -Current principle is not something you want to set or change, except at some advanced scenarios. If you need it, use the `Change` method of the `ICurrentPrincipalAccessor`. It takes a `ClaimsPrinciple` object and makes it "current" for a scope. +Current principal is not something you want to set or change, except at some advanced scenarios. If you need it, use the `Change` method of the `ICurrentPrincipalAccessor`. It takes a `ClaimsPrincipal` object and makes it "current" for a scope. Example: @@ -132,7 +132,7 @@ public class MyAppService : ApplicationService public void Foo() { - var newPrinciple = new ClaimsPrincipal( + var newPrincipal = new ClaimsPrincipal( new ClaimsIdentity( new Claim[] { @@ -143,7 +143,7 @@ public class MyAppService : ApplicationService ) ); - using (_currentPrincipalAccessor.Change(newPrinciple)) + using (_currentPrincipalAccessor.Change(newPrincipal)) { var userName = CurrentUser.UserName; //returns "john" //... diff --git a/docs/en/Distributed-Event-Bus.md b/docs/en/Distributed-Event-Bus.md index 947b0f3142..bd85613011 100644 --- a/docs/en/Distributed-Event-Bus.md +++ b/docs/en/Distributed-Event-Bus.md @@ -178,8 +178,11 @@ That's all. You can inject any service and perform any required logic here. A single event handler class can **subscribe to multiple events** but implementing the `IDistributedEventHandler` interface for each event type. +If you perform **database operations** and use the [repositories](Repositories.md) inside the event handler, you may need to create a [unit of work](Unit-Of-Work.md), because some repository methods need to work inside an **active unit of work**. Make the handle method `virtual` and add a `[UnitOfWork]` attribute for the method, or manually use the `IUnitOfWorkManager` to create a unit of work scope. + > The handler class must be registered to the dependency injection (DI). The sample above uses the `ITransientDependency` to accomplish it. See the [DI document](Dependency-Injection.md) for more options. + ## Pre-Defined Events ABP Framework **automatically publishes** distributed events for **create, update and delete** operations for an [entity](Entities.md) once you configure it. diff --git a/docs/en/Entities.md b/docs/en/Entities.md index 538d298461..a5acba0546 100644 --- a/docs/en/Entities.md +++ b/docs/en/Entities.md @@ -234,6 +234,12 @@ ABP Framework does not force you to apply any DDD rule or patterns. However, it While it's not common (and not suggested) for aggregate roots, it is in fact possible to define composite keys in the same way as defined for the mentioned entities above. Use non-generic `AggregateRoot` base class in that case. +### BasicAggregateRoot Class + +`AggregateRoot` class implements the `IHasExtraProperties` and `IHasConcurrencyStamp` interfaces which brings two properties to the derived class. `IHasExtraProperties` makes the entity extensible (see the *Extra Properties* section below) and `IHasConcurrencyStamp` adds a `ConcurrencyStamp` property that is managed by the ABP Framework to implement the [optimistic concurrency](https://docs.microsoft.com/en-us/ef/core/saving/concurrency). In most cases, these are wanted features for aggregate roots. + +However, if you don't need these features, you can inherit from the `BasicAggregateRoot` (or `BasicAggregateRoot`) for your aggregate root. + ## Base Classes & Interfaces for Audit Properties There are some properties like `CreationTime`, `CreatorId`, `LastModificationTime`... which are very common in all applications. ABP Framework provides some interfaces and base classes to **standardize** these properties and also **sets their values automatically**. @@ -293,7 +299,7 @@ While you can manually implement any of the interfaces defined above, it is sugg All these base classes also have non-generic versions to take `AuditedEntity` and `FullAuditedAggregateRoot` to support the composite primary keys. -All these base classes also have `...WithUser` pairs, like `FullAuditedAggregateRootWithUser` and`FullAuditedAggregateRootWithUser`. This makes possible to add a navigation property to your user entity. However, it is not a good practice to add navigation properties between aggregate roots, so this usage is not suggested (unless you are using an ORM, like EF Core, that well supports this scenario and you really need it - otherwise remember that this approach doesn't work for NoSQL databases like MongoDB where you must truly implement the aggregate pattern). +All these base classes also have `...WithUser` pairs, like `FullAuditedAggregateRootWithUser` and `FullAuditedAggregateRootWithUser`. This makes possible to add a navigation property to your user entity. However, it is not a good practice to add navigation properties between aggregate roots, so this usage is not suggested (unless you are using an ORM, like EF Core, that well supports this scenario and you really need it - otherwise remember that this approach doesn't work for NoSQL databases like MongoDB where you must truly implement the aggregate pattern). Also, if you add navigation properties to the AppUser class that comes with the startup template, consider to handle (ignore/map) it on the migration dbcontext (see [the EF Core migration document](Entity-Framework-Core-Migrations.md)). ## Extra Properties diff --git a/docs/en/Getting-Started-Console-Application.md b/docs/en/Getting-Started-Console-Application.md deleted file mode 100644 index d5f3c8519b..0000000000 --- a/docs/en/Getting-Started-Console-Application.md +++ /dev/null @@ -1,181 +0,0 @@ -# Getting Started ABP With Console Application - -This tutorial explains how to start ABP from scratch with minimal dependencies. You generally want to start with a **[startup template](https://abp.io/Templates)**. - -## Create A New Project - -Create a new Regular .Net Core Console Application from Visual Studio: - -![](images/create-new-net-core-console-application.png) - -## Install Volo.Abp Package - -Volo.Abp.Core is the core nuget package to create ABP based applications. So, install it to your project: - -```` -Install-Package Volo.Abp.Core -```` - -## Create First ABP Module - -ABP is a modular framework and it requires a **startup (root) module** class derived from ``AbpModule``: - -````C# -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Modularity; - -namespace AbpConsoleDemo -{ - public class AppModule : AbpModule - { - - } -} -```` - -``AppModule`` is a good name for the startup module for an application. - -## Initialize The Application - -The next step is to bootstrap the application using the startup module created above: - -````C# -using System; -using Volo.Abp; - -namespace AbpConsoleDemo -{ - class Program - { - static void Main(string[] args) - { - using (var application = AbpApplicationFactory.Create()) - { - application.Initialize(); - - Console.WriteLine("Press ENTER to stop application..."); - Console.ReadLine(); - } - } - } -} - -```` - -``AbpApplicationFactory`` is used to create the application and load all modules taking ``AppModule`` as the startup module. ``Initialize()`` method starts the application. - -## Hello World! - -The application above does nothing. Let's create a service that does something: - -````C# -using System; -using Volo.Abp.DependencyInjection; - -namespace AbpConsoleDemo -{ - public class HelloWorldService : ITransientDependency - { - public void SayHello() - { - Console.WriteLine("Hello World!"); - } - } -} - -```` - -``ITransientDependency`` is a special interface of ABP that automatically registers the service as transient (see [dependency injection document](Dependency-Injection.md)). - -Now, we can resolve the ``HelloWorldService`` and say hello. Change the Program.cs as shown below: - -````C# -using System; -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp; - -namespace AbpConsoleDemo -{ - class Program - { - static void Main(string[] args) - { - using (var application = AbpApplicationFactory.Create()) - { - application.Initialize(); - - //Resolve a service and use it - var helloWorldService = - application.ServiceProvider.GetService(); - helloWorldService.SayHello(); - - Console.WriteLine("Press ENTER to stop application..."); - Console.ReadLine(); - } - } - } -} -```` - -While it's enough for this simple code example, it's always suggested to create scopes in case of directly resolving dependencies from ``IServiceProvider`` (see the [Dependency Injection documentation](Dependency-Injection.md)). - -## Using Autofac as the Dependency Injection Framework - -While AspNet Core's Dependency Injection (DI) system is fine for basic requirements, Autofac provides advanced features like Property Injection and Method Interception which are required by ABP to perform advanced application framework features. - -Replacing AspNet Core's DI system by Autofac and integrating to ABP is pretty easy. - -1. Install [Volo.Abp.Autofac](https://www.nuget.org/packages/Volo.Abp.Autofac) package - -``` -Install-Package Volo.Abp.Autofac -``` - -1. Add ``AbpAutofacModule`` Dependency - -```c# -[DependsOn(typeof(AbpAutofacModule))] //Add dependency to the AbpAutofacModule -public class AppModule : AbpModule -{ - -} -``` - -1. Change ``Program.cs`` file as shown below: - -```c# -using System; -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp; - -namespace AbpConsoleDemo -{ - class Program - { - static void Main(string[] args) - { - using (var application = AbpApplicationFactory.Create(options => - { - options.UseAutofac(); //Autofac integration - })) - { - application.Initialize(); - - //Resolve a service and use it - var helloWorldService = - application.ServiceProvider.GetService(); - helloWorldService.SayHello(); - - Console.WriteLine("Press ENTER to stop application..."); - Console.ReadLine(); - } - } - } -} -``` - -Just called `options.UseAutofac()` method in the `AbpApplicationFactory.Create` options. - -## Source Code - -Get source code of the sample project created in this tutorial from [here](https://github.com/abpframework/abp-samples/tree/master/BasicConsoleApplication). diff --git a/docs/en/How-To/Azure-Active-Directory-Authentication-MVC.md b/docs/en/How-To/Azure-Active-Directory-Authentication-MVC.md deleted file mode 100644 index 434d5f8bac..0000000000 --- a/docs/en/How-To/Azure-Active-Directory-Authentication-MVC.md +++ /dev/null @@ -1,212 +0,0 @@ -# How to Use the Azure Active Directory Authentication for MVC / Razor Page Applications - -This guide demonstrates how to integrate AzureAD to an ABP application that enables users to sign in using OAuth 2.0 with credentials from **Azure Active Directory**. - -Adding Azure Active Directory is pretty straightforward in ABP framework. Couple of configurations needs to be done correctly. - -Two different **alternative approaches** for AzureAD integration will be demonstrated for better coverage. - -1. **AddAzureAD**: This approach uses Microsoft [AzureAD UI nuget package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/) which is very popular when users search the web about how to integrate AzureAD to their web application. - -2. **AddOpenIdConnect**: This approach uses default [OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/) which can be used for not only AzureAD but for all OpenId connections. - -> There is **no difference** in functionality between these approaches. AddAzureAD is an abstracted way of OpenIdConnection ([source](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADAuthenticationBuilderExtensions.cs#L122)) with predefined cookie settings. -> -> However there are key differences in integration to ABP applications because of default configurated signin schemes which will be explained below. - -## 1. AddAzureAD - -This approach uses the most common way to integrate AzureAD by using the [Microsoft AzureAD UI nuget package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/). - -If you choose this approach, you will need to install `Microsoft.AspNetCore.Authentication.AzureAD.UI` package to your **.Web** project. Also, since AddAzureAD extension uses [configuration binding](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1#default-configuration), you need to update your appsettings.json file located in your **.Web** project. - -#### **Updating `appsettings.json`** - -You need to add a new section to your `appsettings.json` which will be binded to configuration when configuring the `OpenIdConnectOptions`: - -````json - "AzureAd": { - "Instance": "https://login.microsoftonline.com/", - "TenantId": "", - "ClientId": "", - "Domain": "domain.onmicrosoft.com", - "CallbackPath": "/signin-azuread-oidc" - } -```` - -> Important configuration here is the CallbackPath. This value must be the same with one of your Azure AD-> app registrations-> Authentication -> RedirectUri. - -Then, you need to configure the `OpenIdConnectOptions` to complete the integration. - -#### Configuring OpenIdConnectOptions - -In your **.Web** project, locate your **ApplicationWebModule** and modify `ConfigureAuthentication` method with the following: - -````csharp -private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) - { - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier); - context.Services.AddAuthentication() - .AddIdentityServerAuthentication(options => - { - options.Authority = configuration["AuthServer:Authority"]; - options.RequireHttpsMetadata = false; - options.ApiName = "Acme.BookStore"; - }) - .AddAzureAD(options => configuration.Bind("AzureAd", options)); - - context.Services.Configure(AzureADDefaults.OpenIdScheme, options => - { - options.Authority = options.Authority + "/v2.0/"; - options.ClientId = configuration["AzureAd:ClientId"]; - options.CallbackPath = configuration["AzureAd:CallbackPath"]; - options.ResponseType = OpenIdConnectResponseType.CodeIdToken; - options.RequireHttpsMetadata = false; - - options.TokenValidationParameters.ValidateIssuer = false; - options.GetClaimsFromUserInfoEndpoint = true; - options.SaveTokens = true; - options.SignInScheme = IdentityConstants.ExternalScheme; - - options.Scope.Add("email"); - }); - } -```` - -> **Don't forget to:** -> -> * Add `.AddAzureAD(options => configuration.Bind("AzureAd", options))` after `.AddAuthentication()`. This binds your AzureAD appsettings and easy to miss out. -> * Add `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear()`. This will disable the default Microsoft claim type mapping. -> * Add `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier)`. Mapping this to [ClaimTypes.NameIdentifier](https://github.com/dotnet/runtime/blob/6d395de48ac718a913e567ae80961050f2a9a4fa/src/libraries/System.Security.Claims/src/System/Security/Claims/ClaimTypes.cs#L59) is important since default SignIn Manager behavior uses this claim type for external login information. -> * Add `options.SignInScheme = IdentityConstants.ExternalScheme` since [default signin scheme is `AzureADOpenID`](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADOpenIdConnectOptionsConfiguration.cs#L35). -> * Add `options.Scope.Add("email")` if you are using **v2.0** endpoint of AzureAD since v2.0 endpoint doesn't return the `email` claim as default. The [Account Module](../Modules/Account.md) uses `email` claim to [register external users](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L215). - -You are done and integration is completed. - -## 2. Alternative Approach: AddOpenIdConnect - -If you don't want to use an extra nuget package in your application, you can use the straight default [OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/) which can be used for all OpenId connections including AzureAD external authentication. - -You don't have to use `appsettings.json` configuration but it is a good practice to set AzureAD information in the `appsettings.json`. - -To get the AzureAD information from `appsettings.json`, which will be used in `OpenIdConnectOptions` configuration, simply add a new section to `appsettings.json` located in your **.Web** project: - -````json - "AzureAd": { - "Instance": "https://login.microsoftonline.com/", - "TenantId": "", - "ClientId": "", - "Domain": "domain.onmicrosoft.com", - "CallbackPath": "/signin-azuread-oidc" - } -```` - -Then, In your **.Web** project; you can modify the `ConfigureAuthentication` method located in your **ApplicationWebModule** with the following: - -````csharp -private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) - { - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier); - - context.Services.AddAuthentication() - .AddIdentityServerAuthentication(options => - { - options.Authority = configuration["AuthServer:Authority"]; - options.RequireHttpsMetadata = false; - options.ApiName = "BookStore"; - }) - .AddOpenIdConnect("AzureOpenId", "Azure Active Directory OpenId", options => - { - options.Authority = "https://login.microsoftonline.com/" + configuration["AzureAd:TenantId"] + "/v2.0/"; - options.ClientId = configuration["AzureAd:ClientId"]; - options.ResponseType = OpenIdConnectResponseType.CodeIdToken; - options.CallbackPath = configuration["AzureAd:CallbackPath"]; - options.RequireHttpsMetadata = false; - options.SaveTokens = true; - options.GetClaimsFromUserInfoEndpoint = true; - - options.Scope.Add("email"); - }); - } -```` - -And that's it, integration is completed. Keep on mind that you can connect any other external authentication providers. - -## The Source Code - -You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization). - -# FAQ - -* Help! `GetExternalLoginInfoAsync` returns `null`! - - * There can be 2 reasons for this; - - 1. You are trying to authenticate against wrong scheme. Check if you set **SignInScheme** to `IdentityConstants.ExternalScheme`: - - ````csharp - options.SignInScheme = IdentityConstants.ExternalScheme; - ```` - - 2. Your `ClaimTypes.NameIdentifier` is `null`. Check if you added claim mapping: - - ````csharp - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier); - ```` - - -* Help! I am getting ***System.ArgumentNullException: Value cannot be null. (Parameter 'userName')*** error! - - - * This occurs when you use Azure Authority **v2.0 endpoint** without requesting `email` scope. [Abp checks unique email to create user](https://github.com/abpframework/abp/blob/037ef9abe024c03c1f89ab6c933710bcfe3f5c93/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L208). Simply add - - ````csharp - options.Scope.Add("email"); - ```` - - to your openid configuration. - -* Help! I keep getting ***AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application*** error! - - * If you set your **CallbackPath** in appsettings as: - - ````csharp - "AzureAd": { - ... - "CallbackPath": "/signin-azuread-oidc" - } - ```` - - your **Redirect URI** of your application in azure portal must be with domain like `https://localhost:44320/signin-azuread-oidc`, not only `/signin-azuread-oidc`. - -* Help! I keep getting ***AADSTS700051: The response_type 'token' is not enabled for the application.*** error! - - * This error occurs when you request **token** (access token) along with **id_token** without enabling Access tokens on Azure portal app registrations. Simply tick **Access tokens** checkbox located on top of ID tokens to be able to request token aswell. - -* Help! I keep getting ***AADSTS7000218: The request body must contain the following parameter: 'client_assertion' or 'client_secret*** error! - - * This error occurs when you request **code** along with **id_token**. You need to add **client secret** on azure portal app registrations, under **Certificates & secrets** menu. Afterwards, you need to add openid configuration option like: - - ````csharp - options.ClientSecret = "Value of your secret on azure portal"; - ```` - -* How can I **debug/watch** which claims I get before they get mapped? - - * You can add a simple event under openid configuration to debug before mapping like: - - ````csharp - options.Events.OnTokenValidated = (async context => - { - var claimsFromOidcProvider = context.Principal.Claims.ToList(); - await Task.CompletedTask; - }); - ```` - -## See Also - -* [How to Customize the Login Page for MVC / Razor Page Applications](Customize-Login-Page-MVC.md). -* [How to Customize the SignIn Manager for ABP Applications](Customize-SignIn-Manager.md). \ No newline at end of file diff --git a/docs/en/How-To/Customize-Login-Page-MVC.md b/docs/en/How-To/Customize-Login-Page-MVC.md deleted file mode 100644 index 2b4769b5c4..0000000000 --- a/docs/en/How-To/Customize-Login-Page-MVC.md +++ /dev/null @@ -1,112 +0,0 @@ -# How to Customize the Login Page for MVC / Razor Page Applications - -When you create a new application using the [application startup template](../Startup-Templates/Application.md), source code of the login page will not be inside your solution, so you can not directly change it. The login page comes from the [Account Module](../Modules/Account.md) that is used a [NuGet package](https://www.nuget.org/packages/Volo.Abp.Account.Web) reference. - -This document explains how to customize the login page for your own application. - -## Create a Login PageModel - -Create a new class inheriting from the [LoginModel](https://github.com/abpframework/abp/blob/037ef9abe024c03c1f89ab6c933710bcfe3f5c93/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs) of the Account module. - -````csharp -public class CustomLoginModel : LoginModel -{ - public CustomLoginModel( - Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemeProvider, - Microsoft.Extensions.Options.IOptions accountOptions) - : base(schemeProvider, accountOptions) - { - } -} -```` - -> Naming convention is important here. If your class name doesn't end with `LoginModel`, you need to manually replace the `LoginModel` using the [dependency injection](../Dependency-Injection.md) system. - -Then you can override any method you need and add new methods and properties needed by the UI. - -## Overriding the Login Page UI - -Create folder named **Account** under **Pages** directory and create a **Login.cshtml** under this folder. It will automatically override the `Login.cshtml` file defined in the Account Module. - -A good way to customize a page is to copy its source code. [Click here](https://github.com/abpframework/abp/blob/dev/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml) for the source code of the login page. At the time this document has been written, the source code was like below: - -````xml -@page -@using Volo.Abp.Account.Settings -@using Volo.Abp.Settings -@model Acme.BookStore.Web.Pages.Account.CustomLoginModel -@inject Volo.Abp.Settings.ISettingProvider SettingProvider -@if (Model.EnableLocalLogin) -{ -
    -
    -

    @L["Login"]

    - @if (await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled)) - { - - @L["AreYouANewUser"] - @L["Register"] - - } -
    - - -
    - - - -
    -
    - - - -
    -
    - -
    - @L["Login"] -
    -
    - - -
    -} - -@if (Model.VisibleExternalProviders.Any()) -{ -
    -

    @L["UseAnotherServiceToLogIn"]

    -
    - - - @foreach (var provider in Model.VisibleExternalProviders) - { - - } -
    -
    -} - -@if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any()) -{ -
    - @L["InvalidLoginRequest"] - @L["ThereAreNoLoginSchemesConfiguredForThisClient"] -
    -} -```` - -Just changed the `@model` to `Acme.BookStore.Web.Pages.Account.CustomLoginModel` to use the customized `PageModel` class. You can change it however your application needs. - -## The Source Code - -You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization). - -## See Also - -* [ASP.NET Core (MVC / Razor Pages) User Interface Customization Guide](../UI/AspNetCore/Customization-User-Interface.md). diff --git a/docs/en/How-To/Customize-SignIn-Manager.md b/docs/en/How-To/Customize-SignIn-Manager.md deleted file mode 100644 index 03c1f06dd8..0000000000 --- a/docs/en/How-To/Customize-SignIn-Manager.md +++ /dev/null @@ -1,101 +0,0 @@ -# How to Customize the SignIn Manager for ABP Applications - -After creating a new application using the [application startup template](../Startup-Templates/Application.md), you may want extend or change the default behavior of the SignIn Manager for your authentication and registration flow needs. ABP [Account Module](../Modules/Account.md) uses the [Identity Management Module](../Modules/Identity.md) for SignIn Manager and the [Identity Management Module](../Modules/Identity.md) uses default [Microsoft Identity SignIn Manager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) ([see here](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs#L17)). - -To write your Custom SignIn Manager, you need to extend [Microsoft Identity SignIn Manager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) class and register it to the DI container. - -This document explains how to customize the SignIn Manager for your own application. - -## Create a CustomSignInManager - -Create a new class inheriting the [SignInMager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) of Microsoft Identity package. - -````csharp -public class CustomSignInManager : Microsoft.AspNetCore.Identity.SignInManager -{ - public CustomSignInManager( - Microsoft.AspNetCore.Identity.UserManager userManager, - Microsoft.AspNetCore.Http.IHttpContextAccessor contextAccessor, - Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory claimsFactory, - Microsoft.Extensions.Options.IOptions optionsAccessor, - Microsoft.Extensions.Logging.ILogger> logger, - Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemes, - Microsoft.AspNetCore.Identity.IUserConfirmation confirmation) - : base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation) - { - } -} -```` - -> It is important to use **Volo.Abp.Identity.IdentityUser** type for SignInManager to inherit, not the AppUser of your application. - -Afterwards you can override any of the SignIn Manager methods you need and add new methods and properties needed for your authentication or registration flow. - -## Overriding the GetExternalLoginInfoAsync Method - -In this case we'll be overriding the `GetExternalLoginInfoAsync` method which is invoked when a third party authentication is implemented. - -A good way to override a method is copying its [source code](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Identity/Core/src/SignInManager.cs#L638-L674). In this case, we will be using a minorly modified version of the source code which explicitly shows the namespaces of the methods and properties to help better understanding of the concept. - -````csharp -public override async Task GetExternalLoginInfoAsync(string expectedXsrf = null) -{ - var auth = await Context.AuthenticateAsync(Microsoft.AspNetCore.Identity.IdentityConstants.ExternalScheme); - var items = auth?.Properties?.Items; - if (auth?.Principal == null || items == null || !items.ContainsKey(LoginProviderKey)) - { - return null; - } - - if (expectedXsrf != null) - { - if (!items.ContainsKey(XsrfKey)) - { - return null; - } - var userId = items[XsrfKey] as string; - if (userId != expectedXsrf) - { - return null; - } - } - - var providerKey = auth.Principal.FindFirstValue(ClaimTypes.NameIdentifier); - var provider = items[LoginProviderKey] as string; - if (providerKey == null || provider == null) - { - return null; - } - - var providerDisplayName = (await GetExternalAuthenticationSchemesAsync()).FirstOrDefault(p => p.Name == provider)?.DisplayName - ?? provider; - return new Microsoft.AspNetCore.Identity.ExternalLoginInfo(auth.Principal, provider, providerKey, providerDisplayName) - { - AuthenticationTokens = auth.Properties.GetTokens() - }; -} -```` - -To get your overridden method invoked and your customized SignIn Manager class to work, you need to register your class to the [Dependency Injection System](../Dependency-Injection.md). - -## Register to Dependency Injection - -Registering `CustomSignInManager` should be done with adding **AddSignInManager** extension method of the [IdentityBuilderExtensions](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/IdentityBuilderExtensions.cs) of the [IdentityBuilder](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Extensions.Core/src/IdentityBuilder.cs). - -Inside your `.Web` project, locate the `YourProjectNameWebModule` and add the following code under the `PreConfigureServices` method to replace the old `SignInManager` with your customized one: - -````csharp -PreConfigure(identityBuilder => -{ - identityBuilder.AddSignInManager(); -}); -```` - -## The Source Code - -You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization). - -## See Also - -* [How to Customize the Login Page for MVC / Razor Page Applications](Customize-Login-Page-MVC.md). -* [Identity Management Module](../Modules/Identity.md). \ No newline at end of file diff --git a/docs/en/How-To/Index.md b/docs/en/How-To/Index.md deleted file mode 100644 index da209d4bcb..0000000000 --- a/docs/en/How-To/Index.md +++ /dev/null @@ -1,9 +0,0 @@ -# "How To" Guides - -This section contains "how to" guides for some specific questions frequently asked. While some of them are common development tasks and not directly related to the ABP Framework, we think it is useful to have some concrete examples those directly work with your ABP based applications. - -## Authentication - -* [How to Customize the Login Page for MVC / Razor Page Applications](Customize-Login-Page-MVC.md) -* [How to Use the Azure Active Directory Authentication for MVC / Razor Page Applications](Azure-Active-Directory-Authentication-MVC.md) -* [How to Customize the SignIn Manager for ABP Applications](Customize-SignIn-Manager.md) \ No newline at end of file diff --git a/docs/en/Local-Event-Bus.md b/docs/en/Local-Event-Bus.md index d0f494faea..d937a1caff 100644 --- a/docs/en/Local-Event-Bus.md +++ b/docs/en/Local-Event-Bus.md @@ -154,6 +154,8 @@ That's all. `MyHandler` is **automatically discovered** by the ABP Framework and * **Zero or more handlers** can subscribe to the same event. * A single event handler class can **subscribe to multiple events** but implementing the `ILocalEventHandler` interface for each event type. +If you perform **database operations** and use the [repositories](Repositories.md) inside the event handler, you may need to create a [unit of work](Unit-Of-Work.md), because some repository methods need to work inside an **active unit of work**. Make the handle method `virtual` and add a `[UnitOfWork]` attribute for the method, or manually use the `IUnitOfWorkManager` to create a unit of work scope. + > The handler class must be registered to the dependency injection (DI). The sample above uses the `ITransientDependency` to accomplish it. See the [DI document](Dependency-Injection.md) for more options. ## Transaction & Exception Behavior diff --git a/docs/en/Multi-Tenancy.md b/docs/en/Multi-Tenancy.md index 77ca49961b..901a5c5cff 100644 --- a/docs/en/Multi-Tenancy.md +++ b/docs/en/Multi-Tenancy.md @@ -355,7 +355,7 @@ namespace MyCompany.MyProject } ```` -{0} is the the placeholder to determine current tenant's unique name. +{0} is the placeholder to determine current tenant's unique name. Instead of ``options.TenantResolvers.Insert(1, new DomainTenantResolveContributor("{0}.mydomain.com"));`` you can use this shortcut: diff --git a/docs/en/Samples/Index.md b/docs/en/Samples/Index.md index e715d3d1ca..f298ea49f5 100644 --- a/docs/en/Samples/Index.md +++ b/docs/en/Samples/Index.md @@ -15,17 +15,12 @@ A complete solution to demonstrate how to build systems based on the microservic A simple CRUD application to show basic principles of developing an application with the ABP Framework. The same sample was implemented with different technologies: * **Book Store: Razor Pages UI & Entity Framework Core** - - * [Tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=MVC) - * [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore) - + * [Tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=MVC&DB=EF) + * [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) * **Book Store: Angular UI & MongoDB** - - * [Tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=NG) + * [Tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=NG&DB=Mongo) * [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) - * **Book Store: Modular application (Razor Pages UI & EF Core)** - * [Source code](https://github.com/abpframework/abp-samples/tree/master/BookStore-Modular) While there is no Razor Pages & MongoDB combination, you can check both documents to understand it since DB & UI selection don't effect each other. @@ -33,11 +28,14 @@ While there is no Razor Pages & MongoDB combination, you can check both document ### Other Samples * **Entity Framework Migrations**: A solution to demonstrate how to split your application into multiple databases each database contains different modules. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/DashboardDemo) + * [Source code](https://github.com/abpframework/abp-samples/tree/master/EfCoreMigrationDemo) * [EF Core database migrations document](../Entity-Framework-Core-Migrations.md) * **SignalR Demo**: A simple chat application that allows to send and receive messages among authenticated users. * [Source code](https://github.com/abpframework/abp-samples/tree/master/SignalRDemo) * [SignalR Integration document](../SignalR-Integration.md) +* **Real Time Messaging In A Distributed Architecture** (using SingalR & RabbitMQ) + * [Source code](https://github.com/abpframework/abp-samples/tree/master/SignalRTieredDemo) + * [Article](https://community.abp.io/articles/real-time-messaging-in-a-distributed-architecture-using-abp-framework-singalr-rabbitmq-daf47e17) * **Dashboard Demo**: A simple application to show how to use the widget system for the ASP.NET Core MVC UI. * [Source code](https://github.com/abpframework/abp-samples/tree/master/DashboardDemo) * [Widget documentation](../UI/AspNetCore/Widgets.md) @@ -50,15 +48,17 @@ While there is no Razor Pages & MongoDB combination, you can check both document * [Text templating documentation](../Text-Templating.md) * **Stored Procedure Demo**: Demonstrates how to use stored procedures, database views and functions with best practices. * [Source code](https://github.com/abpframework/abp-samples/tree/master/StoredProcedureDemo) +* **Passwordless Authentication**: Shows how to add a custom token provider to authenticate a user with a link, instead of entering a password. + * [Source code](https://github.com/abpframework/abp-samples/tree/master/PasswordlessAuthentication) + * [Article](https://community.abp.io/articles/implementing-passwordless-authentication-with-asp.net-core-identity-c25l8koj) * **Authentication Customization**: A solution to show how to customize the authentication for ASP.NET Core MVC / Razor Pages applications. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization) - * Related "[How To](../How-To/Index.md)" documents: - * [Azure Active Directory Authentication](../How-To/Azure-Active-Directory-Authentication-MVC.md) - * [Customize the Login Page](../How-To/Customize-Login-Page-MVC.md) - * [Customize the SignIn Manager](../How-To/Customize-SignIn-Manager.md) + * [Source code](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization) + * Related articles: + * [Azure Active Directory Authentication](https://community.abp.io/articles/how-to-use-the-azure-active-directory-authentication-for-mvc-razor-page-applications-4603b9cf) + * [Customize the Login Page](https://community.abp.io/articles/how-to-customize-the-login-page-for-mvc-razor-page-applications-9a40f3cd) + * [Customize the SignIn Manager](https://community.abp.io/articles/how-to-customize-the-signin-manager-3e858753) +* **GRPC Demo**: Shows how to add a gRPC service to an ABP Framework based web application and consume it from a console application. + * [Source code](https://github.com/abpframework/abp-samples/tree/master/GrpcDemo) * **Empty ASP.NET Core Application**: The most basic ASP.NET Core application with the ABP Framework installed. * [Source code](https://github.com/abpframework/abp-samples/tree/master/BasicAspNetCoreApplication) - * [Documentation](../Getting-Started-AspNetCore-Application.md) -* **Empty Console Application**: The most basic console application with the ABP Framework installed. - * [Source code](https://github.com/abpframework/abp-samples/tree/master/BasicConsoleApplication) - * [Documentation](../Getting-Started-Console-Application.md) \ No newline at end of file + * [Documentation](../Getting-Started-AspNetCore-Application.md) \ No newline at end of file diff --git a/docs/en/Samples/Microservice-Demo.md b/docs/en/Samples/Microservice-Demo.md index 30059a9379..9b4c622c4e 100644 --- a/docs/en/Samples/Microservice-Demo.md +++ b/docs/en/Samples/Microservice-Demo.md @@ -43,6 +43,12 @@ To be able to run the solution from source code, following tools should be insta * [ElasticSearch](https://www.elastic.co/downloads/elasticsearch) 6.6+ * [Kibana](https://www.elastic.co/downloads/kibana) 6.6+ (optional, recommended to show logs) +### Running Infrastructure + +* Docker-compose is used to run the pre requirements with ease as default. If you don't have it, you can download and start using [Docker for Windows](https://docs.docker.com/docker-for-windows/) from [here](https://docs.docker.com/docker-for-windows/install/) on windows environment. +* Run the command `docker-compose -f docker-compose.infrastructure.yml -f docker-compose.infrastructure.override.yml up -d` at `MicroserviceDemo` directory or run the powershell script `__Run_Infrastructure.ps1` located at `MicroserviceDemo/_run` directory. +* If you don't want to use docker for pre required services and install them on your local development, you need to update `appsettings.json` files of the projects in the MicroserviceDemo solution accordingly. + ### Open & Build the Visual Studio Solution * Open the `samples\MicroserviceDemo\MicroserviceDemo.sln` in Visual Studio 2017 (15.9.0+). diff --git a/docs/en/Startup-Templates/Application.md b/docs/en/Startup-Templates/Application.md index af76bb14ff..7495b8f006 100644 --- a/docs/en/Startup-Templates/Application.md +++ b/docs/en/Startup-Templates/Application.md @@ -437,7 +437,11 @@ See the [Testing Overview](https://reactjs.org/docs/testing.html) document. * [Formik](https://github.com/jaredpalmer/formik) is used to build forms. * [Yup](https://github.com/jquense/yup) is used for form validations. +## Social / External Logins + +If you want to configure social/external logins for your application, please follow the [Social/External Logins](../Authentication/Social-External-Logins.md) document. + ## What's Next? -- [The getting started document](../Getting-Started-With-Startup-Templates.md) explains how to create a new application in a few minutes. -- [The application development tutorial](../Tutorials/Part-1) explains step by step application development. +- [The getting started document](../Getting-Started.md) explains how to create a new application in a few minutes. +- [The application development tutorial](../Tutorials/Part-1.md) explains step by step application development. diff --git a/docs/en/Startup-Templates/Console.md b/docs/en/Startup-Templates/Console.md index d42d6f913a..b002eb3e5e 100644 --- a/docs/en/Startup-Templates/Console.md +++ b/docs/en/Startup-Templates/Console.md @@ -18,4 +18,10 @@ abp new Acme.MyConsoleApp -t console `Acme.MyConsoleApp` is the solution name, like *YourCompany.YourProduct*. You can use single level, two-levels or three-levels naming. -### \ No newline at end of file +## Solution Structure + +After you use the above command to create a solution, you will have a solution like shown below: + +![basic-console-application-solution](../images/basic-console-application-solution.png) + +* `HelloWorldService` is a sample service that implements the `ITransientDependency` interface to register this service to the [dependency injection](../Dependency-Injection.md) system. \ No newline at end of file diff --git a/docs/en/Startup-Templates/Module.md b/docs/en/Startup-Templates/Module.md index ea2fe5acca..2dc0896c65 100644 --- a/docs/en/Startup-Templates/Module.md +++ b/docs/en/Startup-Templates/Module.md @@ -149,6 +149,10 @@ The diagram below shows the relation of the applications: `.Web.Host` project uses OpenId Connect Authentication to get identity and access tokens for the current user from the `.IdentityServer`. Then uses the access token to call the `.HttpApi.Host`. HTTP API server uses bearer token authentication to obtain claims from the access token to authorize the current user. +##### Pre-requirements + +* [Redis](https://redis.io/): The applications use Redis as as distributed cache. So, you need to have Redis installed & running. + ##### How to Run? You should run the application with the given order: diff --git a/docs/en/Tutorials/Part-1.md b/docs/en/Tutorials/Part-1.md index 3f83fde1d2..98263f64f7 100644 --- a/docs/en/Tutorials/Part-1.md +++ b/docs/en/Tutorials/Part-1.md @@ -45,11 +45,19 @@ This tutorial is organized as the following parts; ### Download the Source Code -This tutorials has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded: +This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded: * [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) * [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) +{{if UI == "MVC" && DB == "EF"}} + +### Video Tutorial + +This part is also recorded as a video tutorial and **published on YouTube**. + +{{end}} + ## Creating the Solution Before starting to the development, create a new solution named `Acme.BookStore` and run it by following the [getting started tutorial](../Getting-Started.md). @@ -92,7 +100,7 @@ namespace Acme.BookStore.Books ### BookType Enum -The `Book` entity uses the `BookType` enum. Create the `BookType` in the `Acme.BookStore.Domain.Shared` project: +The `Book` entity uses the `BookType` enum. Create a `Books` folder (namespace) in the `Acme.BookStore.Domain.Shared` project and add a `BookType` inside it: ````csharp namespace Acme.BookStore.Books @@ -293,7 +301,7 @@ In this section, you will create an application service to get, create, update a ### BookDto -`CrudAppService` base class requires to define the fundamental DTOs for the entity. Create a DTO class named `BookDto` into the `Acme.BookStore.Application.Contracts` project: +`CrudAppService` base class requires to define the fundamental DTOs for the entity. Create a `Books` folder (namespace) in the `Acme.BookStore.Application.Contracts` project and add a `BookDto` class inside it: ````csharp using System; @@ -340,7 +348,7 @@ namespace Acme.BookStore ### CreateUpdateBookDto -Create another DTO class named `CreateUpdateBookDto` into the `Acme.BookStore.Application.Contracts` project: +Create a `CreateUpdateBookDto` class in the `Books` folder (namespace) of the `Acme.BookStore.Application.Contracts` project: ````csharp using System; @@ -391,7 +399,7 @@ namespace Acme.BookStore ### IBookAppService -Next step is to define an interface for the application service. Create an interface named `IBookAppService` in the `Acme.BookStore.Application.Contracts` project: +Next step is to define an interface for the application service. Create an `IBookAppService` interface in the `Books` folder (namespace) of the `Acme.BookStore.Application.Contracts` project: ````csharp using System; @@ -418,7 +426,7 @@ namespace Acme.BookStore.Books ### BookAppService -Implement the `IBookAppService`, as named `BookAppService`, in the `Acme.BookStore.Application` project: +It is time to implement the `IBookAppService` interface. Create a new class, named `BookAppService` in the `Books` namespace (folder) of the Acme.BookStore.Application project: ````csharp using System; @@ -456,7 +464,7 @@ In a typical ASP.NET Core application, you create **API Controllers** to expose ABP can [**automagically**](../API/Auto-API-Controllers.md) configures your application services as MVC API Controllers by convention. -#### Swagger UI +### Swagger UI The startup template is configured to run the [Swagger UI](https://swagger.io/tools/swagger-ui/) using the [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) library. Run the application by pressing `CTRL+F5` and navigate to `https://localhost:/swagger/` on your browser. (Replace `` with your own port number.) diff --git a/docs/en/Tutorials/Part-2.md b/docs/en/Tutorials/Part-2.md index 1f3b6c84d1..c947f4495d 100644 --- a/docs/en/Tutorials/Part-2.md +++ b/docs/en/Tutorials/Part-2.md @@ -45,11 +45,19 @@ This tutorial is organized as the following parts; ### Download the Source Code -This tutorials has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded: +This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded: * [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) * [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) +{{if UI == "MVC" && DB == "EF"}} + +### Video Tutorial + +This part is also recorded as a video tutorial and **published on YouTube**. + +{{end}} + {{if UI == "MVC"}} ## Dynamic JavaScript Proxies diff --git a/docs/en/Tutorials/Part-3.md b/docs/en/Tutorials/Part-3.md index a0e839186c..13aa5df8c3 100644 --- a/docs/en/Tutorials/Part-3.md +++ b/docs/en/Tutorials/Part-3.md @@ -45,11 +45,19 @@ This tutorial is organized as the following parts; ### Download the Source Code -This tutorials has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded: +This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded: * [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) * [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) +{{if UI == "MVC" && DB == "EF"}} + +### Video Tutorial + +This part is also recorded as a video tutorial and **published on YouTube**. + +{{end}} + {{if UI == "MVC"}} ## Creating a New Book diff --git a/docs/en/Tutorials/Part-4.md b/docs/en/Tutorials/Part-4.md index acbce65c3e..2868df8ba6 100644 --- a/docs/en/Tutorials/Part-4.md +++ b/docs/en/Tutorials/Part-4.md @@ -45,11 +45,19 @@ This tutorial is organized as the following parts; ### Download the Source Code -This tutorials has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded: +This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded: * [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) * [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) +{{if UI == "MVC" && DB == "EF"}} + +### Video Tutorial + +This part is also recorded as a video tutorial and **published on YouTube**. + +{{end}} + ## Test Projects in the Solution This part covers the **server side** tests. There are several test projects in the solution: @@ -78,7 +86,7 @@ If you had created a data seed contributor as described in the [first part](Part ## Testing the BookAppService -Create a test class named `BookAppService_Tests` in the `Acme.BookStore.Application.Tests` project: +Add a new test class, named `BookAppService_Tests` in the `Books` namespace (folder) of the `Acme.BookStore.Application.Tests` project: ````csharp using System.Threading.Tasks; diff --git a/docs/en/Tutorials/Part-5.md b/docs/en/Tutorials/Part-5.md index 5674cf83d6..5b2dfc0431 100644 --- a/docs/en/Tutorials/Part-5.md +++ b/docs/en/Tutorials/Part-5.md @@ -45,11 +45,19 @@ This tutorial is organized as the following parts; ### Download the Source Code -This tutorials has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded: +This tutorial has multiple versions based on your **UI** and **Database** preferences. We've prepared two combinations of the source code to be downloaded: * [MVC (Razor Pages) UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) * [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) +{{if UI == "MVC" && DB == "EF"}} + +### Video Tutorial + +This part is also recorded as a video tutorial and **published on YouTube**. + +{{end}} + ## Permissions ABP Framework provides an [authorization system](../Authorization.md) based on the ASP.NET Core's [authorization infrastructure](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction). One major feature added on top of the standard authorization infrastructure is the **permission system** which allows to define permissions and enable/disable per role, user or client. @@ -58,7 +66,7 @@ ABP Framework provides an [authorization system](../Authorization.md) based on t A permission must have a unique name (a `string`). The best way is to define it as a `const`, so we can reuse the permission name. -Open the `BookStorePermissions` class inside the `Acme.BookStore.Application.Contracts` project and change the content as shown below: +Open the `BookStorePermissions` class inside the `Acme.BookStore.Application.Contracts` project (in the `Permissions` folder) and change the content as shown below: ````csharp namespace Acme.BookStore.Permissions @@ -84,7 +92,7 @@ This is a hierarchical way of defining permission names. For example, "create bo You should define permissions before using them. -Open the `BookStorePermissionDefinitionProvider` class inside the `Acme.BookStore.Application.Contracts` project and change the content as shown below: +Open the `BookStorePermissionDefinitionProvider` class inside the `Acme.BookStore.Application.Contracts` project (in the `Permissions` folder) and change the content as shown below: ````csharp using Acme.BookStore.Localization; diff --git a/docs/en/Tutorials/Part-6.md b/docs/en/Tutorials/Part-6.md index 1bb07007b6..b1b38f18dc 100644 --- a/docs/en/Tutorials/Part-6.md +++ b/docs/en/Tutorials/Part-6.md @@ -204,7 +204,7 @@ namespace Acme.BookStore.Authors > **DDD tip**: Do not introduce domain service methods unless they are really needed and perform some core business rules. For this case, we needed to this service to be able to force the unique name constraint. -Both methods checks if there is already an author with the given name and throws a special business exception, `AuthorAlreadyExistsException`, defined in the `Acme.BookStore.Domain` project as shown below: +Both methods checks if there is already an author with the given name and throws a special business exception, `AuthorAlreadyExistsException`, defined in the `Acme.BookStore.Domain` project (in the `Authors` folder) as shown below: ````csharp using Volo.Abp; @@ -242,6 +242,15 @@ This is a unique string represents the error code thrown by your application and "BookStore:00001": "There is already an author with the same name: {name}" ```` +Then open the `BookStoreDomainSharedModule` and add the following code block inside the `ConfigureServices` method: + +````csharp +Configure(options => +{ + options.MapCodeNamespace("BookStore", typeof(BookStoreResource)); +}); +```` + Whenever you throw an `AuthorAlreadyExistsException`, the end use will see a nice error message on the UI. ## IAuthorRepository diff --git a/docs/en/Tutorials/Part-8.md b/docs/en/Tutorials/Part-8.md index cf75994c14..fc10a799f2 100644 --- a/docs/en/Tutorials/Part-8.md +++ b/docs/en/Tutorials/Part-8.md @@ -339,7 +339,7 @@ public async Task DeleteAsync(Guid id) You can't compile the code since it is expecting some constants declared in the `BookStorePermissions` class. -Open the `BookStorePermissions` class inside the `Acme.BookStore.Application.Contracts` project and change the content as shown below: +Open the `BookStorePermissions` class inside the `Acme.BookStore.Application.Contracts` project (in the `Permissions` folder) and change the content as shown below: ````csharp namespace Acme.BookStore.Permissions @@ -489,6 +489,16 @@ namespace Acme.BookStore } ```` +{{if DB=="EF"}} + +You can now run the `.DbMigrator` console application to **migrate** the **database schema** and **seed** the initial data. + +{{else if DB=="Mongo"}} + +You can now run the `.DbMigrator` console application to **seed** the initial data. + +{{end}} + ## Testing the Author Application Service Finally, we can write some tests for the `IAuthorAppService`. Add a new class, named `AuthorAppService_Tests` in the `Authors` namespace (folder) of the `Acme.BookStore.Application.Tests` project: diff --git a/docs/en/Tutorials/images/youtube.png b/docs/en/Tutorials/images/youtube.png new file mode 100644 index 0000000000..cf9a67548d Binary files /dev/null and b/docs/en/Tutorials/images/youtube.png differ diff --git a/docs/en/UI/Angular/Config-State.md b/docs/en/UI/Angular/Config-State.md index 089697f064..bda3250b32 100644 --- a/docs/en/UI/Angular/Config-State.md +++ b/docs/en/UI/Angular/Config-State.md @@ -127,6 +127,17 @@ const defaultLang = this.config.getSetting("Abp.Localization.DefaultLanguage"); // 'en' ``` +### How to Get a Specific Feature From the Store + +You can use the `getFeature` method of `ConfigStateService` to get a specific feature from the configuration state. Here is an example: + +```js +// this.config is instance of ConfigStateService + +const isChatEnabled = this.config.getFeature("Chat.Enable"); +// 'en' +``` + ### How to Get a Specific Permission From the Store You can use the `getGrantedPolicy` method of `ConfigStateService` to get a specific permission from the configuration state. For that, you should pass a policy key as parameter to the method. diff --git a/docs/en/UI/Angular/Localization.md b/docs/en/UI/Angular/Localization.md index 1ee68ace82..eec4db0cb4 100644 --- a/docs/en/UI/Angular/Localization.md +++ b/docs/en/UI/Angular/Localization.md @@ -199,7 +199,7 @@ Some of the culture names defined in .NET do not match Angular locales. In such ![locale-error](./images/locale-error.png) -If you see an error like this, you should pass the `cultureNameToLocaleFileNameMapping` property like below to CoreModule's forRoot static method. +If you see an error like this, you should pass the `cultureNameLocaleFileMap` property like below to CoreModule's forRoot static method. ```js // app.module.ts @@ -209,7 +209,7 @@ If you see an error like this, you should pass the `cultureNameToLocaleFileNameM // other imports CoreModule.forRoot({ // other options - cultureNameToLocaleFileNameMapping: { + cultureNameLocaleFileMap: { "DotnetCultureName": "AngularLocaleFileName", "pt-BR": "pt" // example } diff --git a/docs/en/UI/Angular/Multi-Tenancy.md b/docs/en/UI/Angular/Multi-Tenancy.md new file mode 100644 index 0000000000..fb9fb54f76 --- /dev/null +++ b/docs/en/UI/Angular/Multi-Tenancy.md @@ -0,0 +1,135 @@ +# Multi Tenancy in Angular UI + +ABP Angular UI supports the multi-tenancy. The following features related to multi-tenancy are available in the startup templates. + + +![Tenants Page](./images/tenants-page.png) +

    Tenants page

    + +On the page above, you can; + +- See the all tenants. +- Create a new tenant. +- Edit an existing tenant. +- Delete a tenant. + + +![Tenant Switching Component](./images/tenant-switching-box.png) +

    Tenant Switching Component

    + +You can switch between existing tenants by using the tenant switching component in the child pages of the `AccountLayoutComponent` (like Login page). Angular UI sends the selected tenant id to the backend as `__tenant` header on each request. + + +## Domain Tenant Resolver + +Angular UI can get the tenant name from the app running URL. You can determine the current tenant by subdomain (like mytenant1.mydomain.com) or by the whole domain (like mytenant.com). To do this, you need to set the `application.baseUrl` property in the environment: + +Subdomain resolver: + +```js +// environment.prod.ts + +export const environment = { + //... + application: { + baseUrl: 'https://{0}.mydomain.com/' + }, + //... +} +``` + +**{0}** is the placeholder to determine current tenant's unique name. + +After the configuration above, if your app runs on the `mytenant1.mydomain.com`, the app will get the tenant name as **mytenant1**. Then, the app will call the `/api/abp/multi-tenancy/tenants/by-name/mytenant1` endpoint to check if the tenant exists. If the tenant (mytenant1) exists, the app will keep this tenant data and send its `id` as `__tenant` header to the backend on each request. If the tenant does not exist, the app will not send `__tenant` header to the backend. + +> **Important Note:** If you define the `baseUrl` with the placeholder (**{0}**), the tenant switching component in the child pages of the `AccountLayoutComponent` (like Login page) will be hidden. + + +Domain resolver: + +```js +// environment.prod.ts + +export const environment = { + //... + application: { + baseUrl: 'https://{0}.com/' + }, + //... +} +``` + +After the configuration above, if your app runs on the `mytenant.com`, the app will get the tenant name as **mytenant**. + +### Tenant Specific Remote Endpoints + +The **{0}** placeholder can be put to the API URLs in the environment to determine tenant specific endpoints. + +```js +// environment.prod.ts + +export const environment = { + //... + application: { + baseUrl: 'https://{0}.mydomain.com/', + //... + }, + oAuthConfig: { + issuer: 'https://{0}.ids.mydomain.com', + //... + }, + apis: { + default: { + url: 'https://{0}.api.mydomain.com', + }, + AbpIdentity: { + url: 'https://{0}.identity.mydomain.com', + }, + }, +} +``` + +> **Important Note:** The `application.baseUrl` and the `{0}` placeholder in the value of the `baseUrl` property are required to be able to get tenant from running URL. Other placeholders in API URLs are optional. + +After the configuration above, if your app runs on the `mytenant1.mydomain.com`, the app will get tenant name as **mytenant1** and replace the environment object in `ConfigState` on app initialization as follows: + + +```js +// environment object in ConfigState + +{ + //... + application: { + baseUrl: 'https://mytenant1.mydomain.com/', + //... + }, + oAuthConfig: { + issuer: 'https://mytenant1.ids.mydomain.com', + //... + }, + apis: { + default: { + url: 'https://mytenant1.api.mydomain.com', + }, + AbpIdentity: { + url: 'https://mytenant1.identity.mydomain.com', + }, + }, +} +``` + +After this replacement, the app will use the following URLs: + +- `https://mytenant1.ids.mydomain.com` as IdentityServer URL. +- `https://mytenant1.api.mydomain.com` as default URL. +- `https://mytenant1.identity.mydomain.com` as `AbpIdentity` remote endpoint URL. + +The app sends the `__tenant` header that contains the current tenant id on each request. + +## See Also + +* [Multi Tenancy in ABP](../../Multi-Tenancy.md) + +## What's Next? + +- [Confirmation Popup](./Confirmation-Service.md) diff --git a/docs/en/UI/Angular/Permission-Management.md b/docs/en/UI/Angular/Permission-Management.md index 61169c85b8..8cc7325afd 100644 --- a/docs/en/UI/Angular/Permission-Management.md +++ b/docs/en/UI/Angular/Permission-Management.md @@ -78,4 +78,4 @@ Granted Policies are stored in the `auth` property of `ConfigState`. ## What's Next? -- [Confirmation Popup](./Confirmation-Service.md) \ No newline at end of file +* [Multi Tenancy](./Multi-Tenancy.md) \ No newline at end of file diff --git a/docs/en/UI/Angular/Toaster-Service.md b/docs/en/UI/Angular/Toaster-Service.md index b194811d9a..faa5632ab8 100644 --- a/docs/en/UI/Angular/Toaster-Service.md +++ b/docs/en/UI/Angular/Toaster-Service.md @@ -26,7 +26,7 @@ You can use the `success`, `warn`, `error`, and `info` methods of `ToasterServic ### How to Display a Toast Overlay ```js -this.toast.success('Message', 'Title'); +this.toaster.success('Message', 'Title'); ``` - The `ToasterService` methods accept three parameters that are `message`, `title`, and `options`. @@ -72,17 +72,18 @@ With the options above, the toast overlay looks like this: The open toast overlay can be removed manually via the `remove` method by passing the `id` of toast: ```js -const toastId = this.toast.success('Message', 'Title') +const toastId = this.toaster.success('Message', 'Title') -this.toast.remove(toastId); +this.toaster.remove(toastId); ``` -### How to Remove All Toasts + +### How to Remove All Toasts The all open toasts can be removed manually via the `clear` method: ```js -this.toast.clear(); +this.toaster.clear(); ``` ## API diff --git a/docs/en/UI/Angular/images/tenant-switching-box.png b/docs/en/UI/Angular/images/tenant-switching-box.png new file mode 100644 index 0000000000..ed4d299c55 Binary files /dev/null and b/docs/en/UI/Angular/images/tenant-switching-box.png differ diff --git a/docs/en/UI/Angular/images/tenants-page.png b/docs/en/UI/Angular/images/tenants-page.png new file mode 100644 index 0000000000..8882d58d2f Binary files /dev/null and b/docs/en/UI/Angular/images/tenants-page.png differ diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index fdd765c710..9bf7083d0d 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -4,21 +4,16 @@ "text": "Getting Started", "items": [ { - "text": "From Startup Templates", + "text": "Web Application", "path": "Getting-Started.md" }, { - "text": "From Empty Projects", - "items": [ - { - "text": "With ASP.NET Core Web Application", - "path": "Getting-Started-AspNetCore-Application.md" - }, - { - "text": "With Console Application", - "path": "Getting-Started-Console-Application.md" - } - ] + "text": "Console Application", + "path": "Startup-Templates/Console.md" + }, + { + "text": "Empty Web Project", + "path": "Getting-Started-AspNetCore-Application.md" } ] }, @@ -93,10 +88,6 @@ } ] }, - { - "text": "\"How to\" Guides", - "path": "How-To/Index.md" - }, { "text": "Migrating from the ASP.NET Boilerplate", "path": "AspNet-Boilerplate-Migration-Guide.md" @@ -107,6 +98,15 @@ "text": "CLI", "path": "CLI.md" }, + { + "text": "Authentication", + "items": [ + { + "text": "Social/External Logins", + "path": "Authentication/Social-External-Logins.md" + } + ] + }, { "text": "Fundamentals", "items": [ @@ -236,12 +236,24 @@ "text": "Azure Provider", "path": "Blob-Storing-Azure.md" }, + { + "text": "Aliyun Provider", + "path": "Blob-Storing-Aliyun.md" + }, + { + "text": "Minio Provider", + "path": "Blob-Storing-Minio.md" + }, + { + "text": "AWS Provider", + "path": "Blob-Storing-Aws.md" + }, { "text": "Create a Custom Provider", "path": "Blob-Storing-Custom-Provider.md" } ] - } + } ] }, { @@ -414,6 +426,10 @@ "text": "Permission Management", "path": "UI/Angular/Permission-Management.md" }, + { + "text": "Multi Tenancy", + "path": "UI/Angular/Multi-Tenancy.md" + }, { "text": "Confirmation Popup", "path": "UI/Angular/Confirmation-Service.md" diff --git a/docs/en/images/basic-console-application-solution.png b/docs/en/images/basic-console-application-solution.png new file mode 100644 index 0000000000..788bad0381 Binary files /dev/null and b/docs/en/images/basic-console-application-solution.png differ diff --git a/docs/zh-Hans/API/Application-Configuration.md b/docs/zh-Hans/API/Application-Configuration.md index ba062d9cef..50e74d5bd7 100644 --- a/docs/zh-Hans/API/Application-Configuration.md +++ b/docs/zh-Hans/API/Application-Configuration.md @@ -2,12 +2,12 @@ ABP框架提供了一个预构建的标准端点,其中包含一些有关应用程序/服务的有用信息. 这里是此端点的一些基本信息的列表: -* [本地化](Localization.md)值, 支持应用程序的当前语言. -* 当前用户可用和已授予的[策略](Authorization.md)(权限). -* 当前用户的[设置](Settings.md)值. -* 关于[当前用户](CurrentUser.md)的信息 (如 id 和用户名). -* 关于当前[租户](Multi-Tenancy.md)的信息 (如 id 和名称). -* 当前用户的[时区](Timing.md)信息和应用程序的[时钟](Timing.md)类型. +* [本地化](../Localization.md)值, 支持应用程序的当前语言. +* 当前用户可用和已授予的[策略](../Authorization.md)(权限). +* 当前用户的[设置](../Settings.md)值. +* 关于[当前用户](../CurrentUser.md)的信息 (如 id 和用户名). +* 关于当前[租户](../Multi-Tenancy.md)的信息 (如 id 和名称). +* 当前用户的[时区](../Timing.md)信息和应用程序的[时钟](../Timing.md)类型. ## HTTP API diff --git a/docs/zh-Hans/Authentication/Social-External-Logins.md b/docs/zh-Hans/Authentication/Social-External-Logins.md new file mode 100644 index 0000000000..3d599a962d --- /dev/null +++ b/docs/zh-Hans/Authentication/Social-External-Logins.md @@ -0,0 +1,30 @@ +# 社交/外部登录 + +## ASP.NET Core MVC / Razor Pages UI + +[帐户模块](../Modules/Account.md)已配置为开箱即用的处理社交或外部登录. 你可以按照ASP.NET Core文档向你的应用程序添加社交/外部登录提供程序. + +### 示例: Facebook 认证 + +按照[ASP.NET Core Facebook集成文档](https://docs.microsoft.com/zh-cn/aspnet/core/security/authentication/social/facebook-logins)向你应用程序添加Facebook登录. + +#### 添加NuGet包 + +添加[Microsoft.AspNetCore.Authentication.Facebook]包到你的项目. 基于你的架构,可能是 `.Web`,`.IdentityServer`(对于分层启动)或 `.Host` 项目. + +#### 配置提供程序 + +在你模块的 `ConfigureServices` 方法中使用 `.AddFacebook(...)` 扩展方法来配置客户端: + +````csharp +context.Services.AddAuthentication() + .AddFacebook(facebook => + { + facebook.AppId = "..."; + facebook.AppSecret = "..."; + facebook.Scope.Add("email"); + facebook.Scope.Add("public_profile"); + }); +```` + +> 最佳实践是使用 `appsettings.json` 或ASP.NET Core用户机密系统来存储你的凭据,而不是像这样硬编码值. 请参阅[微软](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/facebook-logins)文档了解如何使用用户机密. \ No newline at end of file diff --git a/docs/zh-Hans/Authorization.md b/docs/zh-Hans/Authorization.md index 99139be3ac..96caae1404 100644 --- a/docs/zh-Hans/Authorization.md +++ b/docs/zh-Hans/Authorization.md @@ -285,8 +285,6 @@ public async Task CreateAsync(CreateAuthorDto input) abp.auth.isGranted('MyPermissionName'); ```` -参阅 [abp.auth](AspNetCore/JavaScript-API/Auth.md) API 文档了解详情. - ## 权限管理 通常权限管理是管理员用户使用权限管理模态框进行授权: diff --git a/docs/zh-Hans/Background-Jobs-Hangfire.md b/docs/zh-Hans/Background-Jobs-Hangfire.md index bbe9428bb9..b1bf7b8c79 100644 --- a/docs/zh-Hans/Background-Jobs-Hangfire.md +++ b/docs/zh-Hans/Background-Jobs-Hangfire.md @@ -66,9 +66,7 @@ public class YourModule : AbpModule } ```` -1. 我们需要在 `Module` 类的 `OnApplicationInitialization` 方法添加 `UseHangfireServer` 方法调用. - -如果你想要使用Hangfire的面板,你可以使用: `UseHangfireDashboard` +1. 如果你想要使用Hangfire的面板,你可以在 `Module` 类的 `OnApplicationInitialization` 方法添加: `UseHangfireDashboard` ````csharp public override void OnApplicationInitialization(ApplicationInitializationContext context) @@ -77,7 +75,6 @@ public class YourModule : AbpModule // ... others - app.UseHangfireServer(); app.UseHangfireDashboard(); } diff --git a/docs/zh-Hans/Background-Workers-Quartz.md b/docs/zh-Hans/Background-Workers-Quartz.md index 196d0e1d07..9c4a0721e4 100644 --- a/docs/zh-Hans/Background-Workers-Quartz.md +++ b/docs/zh-Hans/Background-Workers-Quartz.md @@ -36,6 +36,9 @@ public class YourModule : AbpModule } ```` +> Quartz后台工作者集成提供了 `QuartzPeriodicBackgroundWorkerAdapter` 来适配 `PeriodicBackgroundWorkerBase` 和 `AsyncPeriodicBackgroundWorkerBase` 派生类. 所以你依然可以按照[后台工作者文档](Background-Workers.md)来定义后台作业. +> `BackgroundJobWorker` 每5秒检查待执行作业,但是长时间的作业不会阻塞quartz. 所以安装Quartz后台工作者集成后,你同时需要安装[Quartz后台作业](Background-Jobs-Quartz.md)或[Hangfire后台作业](Background-Jobs-Hangfire.md)以避免重复执行作业. + ## 配置 参阅[配置](Background-Jobs-Quartz.md#配置). diff --git a/docs/zh-Hans/Best-Practices/Application-Services.md b/docs/zh-Hans/Best-Practices/Application-Services.md index 6c681edde1..82a5dc8aba 100644 --- a/docs/zh-Hans/Best-Practices/Application-Services.md +++ b/docs/zh-Hans/Best-Practices/Application-Services.md @@ -203,7 +203,7 @@ Task VoteAsync(Guid id, VoteType type); #### 额外的属性 -* **推荐** 使用 `MapExtraPropertiesTo` 扩展方法 ([参阅](Object-Extensions.md)) 或配置对象映射 (`MapExtraProperties`) 以允许应用开发人员能够扩展对象和服务. +* **推荐** 使用 `MapExtraPropertiesTo` 扩展方法 ([参阅](../Object-Extensions.md)) 或配置对象映射 (`MapExtraProperties`) 以允许应用开发人员能够扩展对象和服务. #### 操作/删除 实体 diff --git a/docs/zh-Hans/Blob-Storing-Aliyun.md b/docs/zh-Hans/Blob-Storing-Aliyun.md new file mode 100644 index 0000000000..ded28fbc93 --- /dev/null +++ b/docs/zh-Hans/Blob-Storing-Aliyun.md @@ -0,0 +1,77 @@ +# BLOB Storing Aliyun提供程序 + +BLOB存储Aliyun提供程序可以将BLOB存储在[Aliyun Blob storage](https://help.aliyun.com/product/31815.html)中. + +> 阅读[BLOB存储文档](Blob-Storing.md)了解如何使用BLOB存储系统. 本文档仅介绍如何为容器配置Aliyun提供程序. + +## 安装 + +使用ABP CLI添加[Volo.Abp.BlobStoring.Aliyun](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Aliyun)NuGet包到你的项目: + +* 安装 [ABP CLI](https://docs.abp.io/en/abp/latest/CLI), 如果你还没有安装. +* 在要添加 `Volo.Abp.BlobStoring.Aliyun` 包的 `.csproj` 文件目录打开命令行. +* 运行 `Volo.Abp.BlobStoring.Aliyun` 命令. + +如果要手动安装,在你的项目中安装 `Volo.Abp.BlobStoring.Aliyun` NuGet包然后将`[DependsOn(typeof(AbpBlobStoringAliyunModule))]`添加到项目内的[ABP模块](Module-Development-Basics.md)类中. + +## 配置 + +如同[BLOB存储文档](Blob-Storing.md)所述,配置是在[模块](Module-Development-Basics.md)类的 `ConfigureServices` 方法完成的. + +**示例: 配置为默认使用Aliyun存储提供程序** + +````csharp +Configure(options => +{ + options.Containers.ConfigureDefault(container => + { + container.UseAliyun(aliyun => + { + aliyun.AccessKeyId = "your aliyun access key id"; + aliyun.AccessKeySecret = "your aliyun access key secret"; + aliyun.Endpoint = "your oss endpoint"; + aliyun.RegionId = "your sts region id"; + aliyun.RoleArn = "the arn of ram role"; + aliyun.RoleSessionName = "the name of the certificate"; + aliyun.Policy = "policy"; + aliyun.DurationSeconds = "expiration date"; + aliyun.ContainerName = "your aliyun container name"; + aliyun.CreateContainerIfNotExists = false; + }); + }); +}); +```` + +> 参阅[BLOB存储文档](Blob-Storing.md) 学习如何为指定容器配置提供程序. + +### 选项 + +* **AccessKeyId** ([NotNull]string): 云账号AccessKey是访问阿里云API的密钥,具有该账户完全的权限,请你务必妥善保管!强烈建议遵循[阿里云安全最佳实践](https://help.aliyun.com/document_detail/102600.html),使用RAM子用户AccessKey来进行API调用. +* **AccessKeySecret** ([NotNull]string): 同上. +* **Endpoint** ([NotNull]string): Endpoint表示OSS对外服务的访问域名. [访问域名和数据中心](https://help.aliyun.com/document_detail/31837.html) +* **UseSecurityTokenService** (bool): 是否使用STS临时授权访问OSS,默认false. [STS临时授权访问OSS](https://help.aliyun.com/document_detail/100624.html) +* **RegionId** (string): STS服务的接入地址,每个地址的功能都相同,请尽量在同地域进行调用. [接入地址](https://help.aliyun.com/document_detail/66053.html) +* **RoleArn** ([NotNull]string): STS所需角色ARN. +* **RoleSessionName** ([NotNull]string): 用来标识临时访问凭证的名称,建议使用不同的应用程序用户来区分. +* **Policy** (string): 在扮演角色的时候额外添加的权限限制. 请参见[基于RAM Policy的权限控制](https://help.aliyun.com/document_detail/100680.html). +* **DurationSeconds** (int): 设置临时访问凭证的有效期,单位是s,最小为900,最大为3600. +* **ContainerName** (string): 你可以在aliyun中指定容器名称. 如果没有指定它将使用 `BlogContainerName` 属性定义的BLOB容器的名称(请参阅[BLOB存储文档](Blob-Storing.md)). 请注意Aliyun有一些**命名容器的规则**,容器名称必须是有效的DNS名称,[符合以下命名规则](https://help.aliyun.com/knowledge_detail/39668.html): + * 只能包含小写字母,数字和短横线(-) + * 必须以小写字母和数字开头和结尾 + * Bucket名称的长度限制在**3**到**63**个字符之间 +* **CreateContainerIfNotExists** (bool): 默认值为 `false`, 如果aliyun中不存在容器, `AliyunBlobProvider` 将尝试创建它. +* **TemporaryCredentialsCacheKey** (bool): STS凭证缓存Key,默认Guid.NewGuid().ToString("N"). + +## Aliyun BLOB 名称计算器 + +Aliyun BLOB提供程序组织BLOB名称并实现一些约定. 默认情况下BLOB的全名由以下规则确定: + +* 如果当前租户为 `null`(或容器禁用多租户 - 请参阅[BLOB存储文档](Blob-Storing.md) 了解如何禁用容器的多租户),则追加 `host` 字符串. +* 如果当前租户不为 `null`,则追加 `tenants/` 字符串. +* 追加 BLOB 名称. + +## 其他服务 + +* `AliyunBlobProvider` 是实现Aliyun BLOB存储提供程序的主要服务,如果你想要通过[依赖注入](Dependency-Injection.md)覆盖/替换它(不要替换 `IBlobProvider` 接口,而是替换 `AliyunBlobProvider` 类). +* `IAliyunBlobNameCalculator` 服务用于计算文件路径. 默认实现是 `DefaultAliyunBlobNameCalculator`. 如果你想自定义文件路径计算,可以替换/覆盖它. +* `IOssClientFactory` 服务用于生成OSS客户端. 默认实现是 `DefaultOssClientFactory` . 如果你想自定义OSS客户端生成,可以替换/覆盖它. diff --git a/docs/zh-Hans/Blob-Storing-Aws.md b/docs/zh-Hans/Blob-Storing-Aws.md new file mode 100644 index 0000000000..0ecb59bbf5 --- /dev/null +++ b/docs/zh-Hans/Blob-Storing-Aws.md @@ -0,0 +1,84 @@ +# BLOB Storing Aws提供程序 + +BLOB存储Aws提供程序可以将BLOB存储在[Amazon Simple Storage Service](https://aws.amazon.com/cn/s3/)中. + +> 阅读[BLOB存储文档](Blob-Storing.md)了解如何使用BLOB存储系统. 本文档仅介绍如何为容器配置Aws提供程序. + +## 安装 + +使用ABP CLI添加[Volo.Abp.BlobStoring.Aws](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Aws)NuGet包到你的项目: + +* 安装 [ABP CLI](https://docs.abp.io/en/abp/latest/CLI), 如果你还没有安装. +* 在要添加 `Volo.Abp.BlobStoring.Aws` 包的 `.csproj` 文件目录打开命令行. +* 运行 `Volo.Abp.BlobStoring.Aws` 命令. + +如果要手动安装,在你的项目中安装 `Volo.Abp.BlobStoring.Aws` NuGet包然后将`[DependsOn(typeof(AbpBlobStoringAwsModule))]`添加到项目内的[ABP模块](Module-Development-Basics.md)类中. + +## 配置 + +如同[BLOB存储文档](Blob-Storing.md)所述,配置是在[模块](Module-Development-Basics.md)类的 `ConfigureServices` 方法完成的. + +**示例: 配置为默认使用Aws存储提供程序** + +````csharp +Configure(options => +{ + options.Containers.ConfigureDefault(container => + { + container.UseAws(Aws => + { + Aws.AccessKeyId = "your Aws access key id"; + Aws.SecretAccessKey = "your Aws access key secret"; + Aws.UseCredentials = "set true to use credentials"; + Aws.UseTemporaryCredentials = "set true to use temporary credentials"; + Aws.UseTemporaryFederatedCredentials = "set true to use temporary federated credentials"; + Aws.ProfileName = "the name of the profile to get credentials from"; + Aws.ProfilesLocation = "the path to the aws credentials file to look at"; + Aws.Region = "the system name of the service"; + Aws.Name = "the name of the federated user"; + Aws.Policy = "policy"; + Aws.DurationSeconds = "expiration date"; + Aws.ContainerName = "your Aws container name"; + Aws.CreateContainerIfNotExists = false; + }); + }); +}); +```` + +> 参阅[BLOB存储文档](Blob-Storing.md) 学习如何为指定容器配置提供程序. + +### 选项 + +* **AccessKeyId** (string): AWS Access Key ID. +* **SecretAccessKey** (string): AWS Secret Access Key. +* **UseCredentials** (bool): 使用[本地凭证](https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/dev/AuthUsingAcctOrUserCredentials.html)访问AWS服务,默认: `false`. +* **UseTemporaryCredentials** (bool): 使用[临时凭证](https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/dev/AuthUsingTempSessionToken.html)访问AWS服务,默认: `false`. +* **UseTemporaryFederatedCredentials** (bool): 使用[联合身份用户临时凭证](https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/dev/AuthUsingTempFederationToken.html)访问AWS服务, 默认: `false`. +* **ProfileName** (string): [本地凭证配置文件](https://docs.aws.amazon.com/zh_cn/sdk-for-net/v3/developer-guide/net-dg-config-creds.html)名称. +* **ProfilesLocation** (string): 本地配置文件位置. +* **Region** (string): 服务的地区名称. +* **Policy** (string): JSON格式的IAM策略. +* **DurationSeconds** (int): 设置临时访问凭证的有效期,单位是s,最小为900,最大为129600. +* **ContainerName** (string): 你可以在Aws中指定容器名称. 如果没有指定它将使用 `BlogContainerName` 属性定义的BLOB容器的名称(请参阅[BLOB存储文档](Blob-Storing.md)). 请注意Aws有一些**命名容器的规则**,容器名称必须是有效的DNS名称,[符合以下命名规则](https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html): + * Bucket名称必须介于 3 到 63 个字符之间. + * Bucket名称只能由小写字母、数字、句点 (.) 和连字符 (-) 组成. + * Bucket名称必须以字母或数字开头和结尾. + * Bucket名称不能是ip (例如, 192.168.5.4). + * Bucket名称不能以 **xn--** 开头, (对于2020年2月以后创建的 Bucket). + * Bucket名称在分区中必须唯一 . + * Bucket 与 Amazon S3 Transfer Acceleration 一起使用时名称中不能有句点 (.). +* **CreateContainerIfNotExists** (bool): 默认值为 `false`, 如果Aws中不存在容器, `AwsBlobProvider` 将尝试创建它. + +## Aws BLOB 名称计算器 + +Aws BLOB提供程序组织BLOB名称并实现一些约定. 默认情况下BLOB的全名由以下规则确定: + +* 如果当前租户为 `null`(或容器禁用多租户 - 请参阅[BLOB存储文档](Blob-Storing.md) 了解如何禁用容器的多租户),则追加 `host` 字符串. +* 如果当前租户不为 `null`,则追加 `tenants/` 字符串. +* 追加 BLOB 名称. + +## 其他服务 + +* `AwsBlobProvider` 是实现Aws BLOB存储提供程序的主要服务,如果你想要通过[依赖注入](Dependency-Injection.md)覆盖/替换它(不要替换 `IBlobProvider` 接口,而是替换 `AwsBlobProvider` 类). +* `IAwsBlobNameCalculator` 服务用于计算文件路径. 默认实现是 `DefaultAwsBlobNameCalculator`. 如果你想自定义文件路径计算,可以替换/覆盖它. +* `IAmazonS3ClientFactory` 服务用于生成AWS S3客户端. 默认实现是 `DefaultAmazonS3ClientFactory` . 如果你想自定义AWS S3客户端生成,可以替换/覆盖它. diff --git a/docs/zh-Hans/Blob-Storing-Azure.md b/docs/zh-Hans/Blob-Storing-Azure.md index 892d299fc0..b6f54cd72c 100644 --- a/docs/zh-Hans/Blob-Storing-Azure.md +++ b/docs/zh-Hans/Blob-Storing-Azure.md @@ -23,11 +23,14 @@ BLOB存储Azure提供程序可以将BLOB存储在[Azure Blob storage](https://az ````csharp Configure(options => { - options.Containerscontainer.UseAzure(azure => + options.Containers.ConfigureDefault(container => { - azure.ConnectionString = "your azure connection string"; - azure.ContainerName = "your azure container name"; - azure.CreateContainerIfNotExists = false; + container.UseAzure(azure => + { + azure.ConnectionString = "your azure connection string"; + azure.ContainerName = "your azure container name"; + azure.CreateContainerIfNotExists = false; + }); }); }); ```` diff --git a/docs/zh-Hans/Blob-Storing-Database.md b/docs/zh-Hans/Blob-Storing-Database.md index e45c79d179..cc688e4396 100644 --- a/docs/zh-Hans/Blob-Storing-Database.md +++ b/docs/zh-Hans/Blob-Storing-Database.md @@ -27,7 +27,7 @@ abp add-module Volo.Abp.BlobStoring.Database 这里是此提供程序定义的所有包: -* [Volo.Abp.BlobStoring.Database.Domain.Shared](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Domain.Shared) +* [Volo.Abp.BlobStoring.Database.Domain.Shared](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.Domain.Shared) * [Volo.Abp.BlobStoring.Database.Domain](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.Domain) * [Volo.Abp.BlobStoring.Database.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.EntityFrameworkCore) * [Volo.Abp.BlobStoring.Database.MongoDB](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.MongoDB) diff --git a/docs/zh-Hans/Blob-Storing-Minio.md b/docs/zh-Hans/Blob-Storing-Minio.md new file mode 100644 index 0000000000..3c4bec721d --- /dev/null +++ b/docs/zh-Hans/Blob-Storing-Minio.md @@ -0,0 +1,69 @@ +# BLOB Storing Minio 提供程序 + +BLOB Storing Minio提供程序帮助你存储对象到 [MinIO Object storage](https://min.io/), + +> 阅读[BLOB存储文档](Blob-Storing.md)了解如何使用BLOB存储系统, 本文档仅介绍如何为容器配置Minio提供程序, + +## 安装 + +使用 ABP CLI 来安装 [Volo.Abp.BlobStoring.Minio](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Minio) NuGet 包到你的项目: + +* 如果你没有安装ABP CLI,请先安装 [ABP CLI](https://docs.abp.io/en/abp/latest/CLI), +* 在要添加 `Volo.Abp.BlobStoring.Minio` 包的 `.csproj` 文件目录打开命令行, +* 执行 `abp add-package Volo.Abp.BlobStoring.Minio` 命令, + +如果你要手动安装, 通过NuGet安装 [Volo.Abp.BlobStoring.Minio](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Minio) 到你的项目,然后添加 `[DependsOn(typeof(AbpBlobStoringMinioModule))]` 特性到你的 [ABP module](Module-Development-Basics.md) 类上, + +## 配置 + +配置在你的[module](Module-Development-Basics.md)类中的`ConfigureServices`方法中完成, + +**例: 配置使用Minio存储** + +````csharp +Configure(options => +{ + options.Containers.ConfigureDefault(container => + { + container.UseMinio(minio => + { + minio.EndPoint = "你的 minio endPoint"; + minio.AccessKey = "你的 minio accessKey"; + minio.SecretKey = "你的 minio secretKey"; + minio.BucketName = "你的 minio bucketName"; + }); + }); +}); +```` + +> 参阅[BLOB存储文档](Blob-Storing.md) 学习如何为指定容器配置提供程序, + +### 选项 + +* **EndPoint** (string): 你的Minio对象存储服务的URL, 查看文档:https://docs.min.io/docs/dotnet-client-quickstart-guide.html +* **AccessKey** (string): Access key是唯一标识你的账户的用户ID, +* **SecretKey** (string): Access key是唯一标识你的账户的用户ID +* **BucketName** (string):你可以指定bucket名称,如果没有指定,将使用 `BlogContainerName` 属性定义的BLOB容器的名称(查阅[BLOB storing document](Blob-Storing.md)),MinIO完全兼容S3标准,所以有一些 **bucket命名规则**,必须符合[规则](https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html): + * Bucket名称必须介于 3 到 63 个字符之间. + * Bucket名称只能由小写字母、数字、句点 (.) 和连字符 (-) 组成. + * Bucket名称必须以字母或数字开头和结尾. + * Bucket名称不能是ip (例如, 192.168.5.4). + * Bucket名称不能以 **xn--** 开头, (对于2020年2月以后创建的 Bucket). + * Bucket名称在分区中必须唯一 . + * Bucket 与 Amazon S3 Transfer Acceleration 一起使用时名称中不能有句点 (.). +* **WithSSL** (bool): 默认 `false`,代表使用HTTPS, +* **CreateContainerIfNotExists** (bool): 默认 `false`,如果不存在bucket, `MinioBlobProvider` 将会创建一个, + + +## Minio BLOB 名称计算器 + +默认情况下BLOB的全名由以下规则确定: + +* 如果当前租户为 `null`(或容器禁用多租户 - 请参阅[BLOB存储文档](Blob-Storing.md) 了解如何禁用容器的多租户),则追加 `host` 字符串, +* 如果当前租户不为 `null`,则追加 `tenants/` 字符串, +* 追加 BLOB 名称, + +## 其他服务 + +* `MinioBlobProvider` 是实现Minio BLOB存储提供程序的主要服务,如果你想要通过[依赖注入](Dependency-Injection.md)覆盖/替换它(不要替换 `IBlobProvider` 接口,而是替换 `MinioBlobProvider` 类). +* `IMinioBlobNameCalculator` 服务用于计算文件路径. 默认实现是 `DefaultMinioBlobNameCalculator`. 如果你想自定义文件路径计算,可以替换/覆盖它. diff --git a/docs/zh-Hans/Blob-Storing.md b/docs/zh-Hans/Blob-Storing.md index 43b2ebb96b..99688ca47a 100644 --- a/docs/zh-Hans/Blob-Storing.md +++ b/docs/zh-Hans/Blob-Storing.md @@ -19,6 +19,9 @@ ABP框架已经有以下存储提供程序的实现; * [File System](Blob-Storing-File-System.md):将BLOB作为标准文件存储在本地文件系统的文件夹中. * [Database](Blob-Storing-Database.md): 将BLOB存储在数据库中. * [Azure](Blob-Storing-Azure.md): 将BLOG存储在 [Azure BLOB storage](https://azure.microsoft.com/en-us/services/storage/blobs/)中. +* [Aliyun](Blob-Storing-Aliyun.md): 将BLOB存储在[Aliyun Storage Service](https://help.aliyun.com/product/31815.html)中. +* [Ninio](Blob-Storing-Minio.md): 将BLOB存储在[MinIO Object storage](https://min.io/)中. +* [Aws](Blob-Storing-Aws.md): 将BLOB存储在[Amazon Simple Storage Service](https://min.io/)中. 以后会实现更多的提供程序,你可以为自己喜欢的提供程序创建[请求](https://github.com/abpframework/abp/issues/new),或者你也可以[自己实现](Blob-Storing-Custom-Provider.md)它并[贡献](Contribution/Index.md)到ABP框架. diff --git a/docs/zh-Hans/Blog-Posts/2020-07-01 v3_0_Release/Post.md b/docs/zh-Hans/Blog-Posts/2020-07-01 v3_0_Release/Post.md new file mode 100644 index 0000000000..1175875d16 --- /dev/null +++ b/docs/zh-Hans/Blog-Posts/2020-07-01 v3_0_Release/Post.md @@ -0,0 +1,177 @@ +# ABP框架 v3.0 已发布 + +我们很高兴地宣布,**ABP框架**和**ABP商业版**3.0版已经发布.与常规的2周发布一个版本不同的是, 这个版本用了4周的时间.关闭了**119个[issue](https://github.com/abpframework/abp/issues?q=is%3Aopen+is%3Aissue+milestone%3A3.0)**,合并了**89个[pull request](https://github.com/abpframework/abp/pulls?q=is%3Aopen+is%3Apr+milestone%3A3.0)** 和主框架[仓库](https://github.com/abpframework/abp)中的**798次提交**. + +由于这是一个**主要版本**,它也包括了一些**重大更改**.不要害怕,这些变化都容易对应,并且下面会详细解释. + +> 详细更改日志请查看[GitHub的发布说明](https://github.com/abpframework/abp/releases/tag/3.0.0). + +## ABP框架3.0有哪些新增内容? + +这篇文章将只包括重要功能/更改.你可在[GitHub的发布说明](https://github.com/abpframework/abp/releases/tag/3.0.0)上看到所有的更改. + +### Angular 10! + +Angular 10刚刚[发布](https://blog.angular.io/version-10-of-angular-now-available-78960babd41),我们就立即迁移了[启动模板](https://docs.abp.io/en/abp/latest/Startup-Templates/Application)到Angular 10!所以,当你以Angular UI[创建一个新的解决方案](https://abp.io/get-started)时, 你将获得新Angular的优点. + +我们为想要迁移到Angular 10的老版本项目准备了一份[迁移指南](https://github.com/abpframework/abp/blob/dev/docs/en/UI/Angular/Migration-Guide-v3.md). + +### Oracle集成包 + +之前我们为使用Devart库, 基于EF Core for Oracle的应用程序创建了[一个集成包](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle.Devart), 因为官方的Oracle EF Core包还未支持EF Core 3.1. 现在它以[beta发布](https://www.nuget.org/packages/Oracle.EntityFrameworkCore/3.19.0-beta1).虽然是beta版,但我们已经创建了它的[集成包](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle),这样你就可以在你的应用程序中使用它. + +更多细节请查看[文档](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Oracle). + +### Azure BLOB存储提供器 + +在上一个版本中我们创建了一个[Blob存储系统](https://docs.abp.io/en/abp/latest/Blob-Storing), 包含一个文件系统和数据库存储提供器. 本次发布我们引入了Azure BLOB存储提供器.请查看[文档](https://docs.abp.io/en/abp/latest/Blob-Storing-Azure). + +### 分布式缓存批量操作和新的Redis缓存包 + +ASP.NET Core的[标准IDistributeCache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed)接口不包含**批量操作**,如使用单独方法/服务器调用设置多个项目. ABP框架引入了新的方法可用于在ABP的`IDistributedCache`接口中进行批量操作: + +* GetManyAsync / GetMany +* SetManyAsync / SetMany + +然后,我们需要为Redis缓存实现这些新方法并且[必须创建](https://github.com/abpframework/abp/issues/4483)一个Redis集成包用于扩展微软的实现. + +这些方法也被ABP框架所使用, 用来缓存某个用户/角色/租户的设置,功能和权限,并且带来了**显著的性能提升**. + +更多细节请查看[缓存文档](https://docs.abp.io/en/abp/latest/Caching). + +### 为虚拟文件系统支持嵌入文件清单 + +虚拟文件系统现在支持在项目中使用`GenerateEmbeddedFilesManifest`来添加编译程序集中嵌入资源的**真实的文件/目录结构**.所以,你现在可以访问文件,而没有任何文件名的限制(以前,目录名称中的一些特殊字符,比如`.`在某些情况会有问题) + +查看[文档](https://docs.abp.io/en/abp/latest/Virtual-File-System)学习如何利用新系统的优点. + +### 新示例 + +基于社区的需求,我们准备了两个新的示例应用程序: + +* [StoredProcedureDemo](https://github.com/abpframework/abp-samples/tree/master/StoredProcedureDemo)演示了如何在自定义仓储库中调用存储过程,视图和函数. +* [OrganizationUnitSample](https://github.com/abpframework/abp-samples/tree/master/OrganizationUnitSample)演示了如何为你的实体使用[Identity模块](https://docs.abp.io/en/abp/latest/Modules/Identity)的组织单元系统. + +### DynamicStringLength 和 DynamicMaxLength 属性 + +当类被用作模型或[DTO](https://docs.abp.io/en/abp/latest/Data-Transfer-Objects)时, 标准的`StringLength`和`MaxLength`数据注解属性用来验证类中的属性是很有用的. 但是, 就像任何其它的属性一样, 长度的值应该是一个**编译时**已知的字面值(常量). + +**示例: 使用`StringLength`** + +````csharp +public class CreateBookDto +{ + public const int MaxNameLength = 128; //常量! + + [StringLength(MaxNameLength)] + public string Name { get; set; } +} +```` + +ABP框架现在有`DynamicStringLength` 和 `DynamicMaxLength`属性允许在**运行时**确定长度. + +**示例: 使用`DynamicStringLength`** + +````csharp +public class CreateBookDto +{ + public static int MaxNameLength { get; set; } = 128; + + [DynamicStringLength(typeof(CreateBookDto), nameof(MaxNameLength))] + public string Name { get; set; } +} +```` + +`DynamicStringLength`通过类的**类型**和这个类的一个静态属性的**名字**来读取最大长度(也有一个最小长度的选项, 就像`StringLength`). + +这样就允许你从配置中读取最大值并且在应用程序启动时设置(一般在[模块](https://docs.abp.io/en/abp/latest/Module-Development-Basics)的`PreConfigureServices`方法) + +````csharp +CreateBookDto.MaxNameLength = 200; +```` + +[预建应用程序模块](https://docs.abp.io/en/abp/latest/Modules/Index)使用了这个功能,所以你现在可以覆盖这些模块中定义的属性的最大长度了. + +### 自动分布式事件 + +ABP可以为所有的实体在创建,更新和删除时**自动发布分布式事件**. 这很有用因为在分布式系统中你通常会对这些基础事件感兴趣. + +这个功能在v3.0中**已成熟并[文档化了](https://docs.abp.io/en/abp/latest/Distributed-Event-Bus#pre-defined-events)**. 你可以轻松地配置一部分或全部要发布事件的实体. + +### IAsyncQueryableExecuter + +使用LINQ扩展方法,当你在查询时需要调用`ToListAsync()`,`FirstOrDefaultAsync()`... 不幸的是,这些方法并不是**标准的**LINQ扩展方法.它们定义在[Microsoft.EntityFrameworkCore](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore)包中(或在[MongoDB.Driver](https://www.nuget.org/packages/MongoDB.Driver/),如果你使用MongoDB的话). + +所以,如果你想使用异步扩展方法, 你需要依赖这个包.这样做破坏了分层,并且使你的应用程序或领域层依赖于EF Core / MongoDB的包. + +`IAsyncQueryableExecuter`是ABP框架定义的服务, 用来**不依赖特定提供器地执行异步查询**(EF Core / MongoDB包). + +查看[文档](https://docs.abp.io/en/abp/latest/Repositories#iqueryable-async-operations)来获取细节和了解我们的建议. + +### API 文档 + +现在我们为每次发布ABP框架和模块都公开[API文档](https://docs.abp.io/api-docs/abp/3.0/api/index.html).所以,你可以更容易地查看ABP框架的类.点击[文档](https://docs.abp.io/en/abp/latest/)导航菜单中的**API文档**的链接. + +### 包列表 + +我们[创建了一个页面](http://abp.io/packages)列出所有ABP相关的官方NuGet和NPM包. + +### 其他 + +* 为Identity Server实现了前端通道的服务端客户[登出](https://identityserver4.readthedocs.io/en/latest/topics/signout.html) +* `abp.currentUser`(Angular UI中的`CurrentUser`服务)现在有一个包含当前用户的角色名的`roles`数组. +* 升级所有的Nu​​Get和NPM软件包的依赖. +* 引入了`ReadOnlyAppService`基类(只包含get操作), 做为`CrudAppService`基类(其中有get,create,update和delete操作)的补充. + +其他更新请查看[GitHub发行说明](https://github.com/abpframework/abp/releases/tag/3.0.0). + +## ABP商业版3.0有哪些新增内容? + +除了所有ABP框架的功能以外,ABP商业版同样在本次发布中有一些额外的功能.本节介绍3.0版本中[ABP商业版](https://commercial.abp.io/)的亮点. + +### 新的文件管理模块 + +我们创建了一个用于在应用程序中存储和管理文件的新模块.这个新模块基于[BLOB存储系统](https://docs.abp.io/en/abp/latest/Blob-Storing),因此它可以使用不同的存储提供器来存储文件内容. + +**示例截图** + +![file-management-ui](file-management-ui.png) + +您可以上传,下载和以分层文件夹结构组织文件.它也兼容多租户,你可以为租户限制总文件大小.在未来的版本中,我们将致力于“共享”系统, 用于以一个更可控的方式在用户之间或通过公开链接与你的客户共享文件. + +> 文件管理模块是目前仅适用于MVC / Razor Pages UI.我们正工作于Angular UI,它会在未来的版本中发布. + +## 重大更改 + +由于这是一个主要版本,我们重新设计了一些API,并引入了一些"容易解决"的重大更改. + +### ABP框架 + +* 更改了一些预建应用程序模块的**常量**为静态属性, 这样你就可在你的代码中对它们修改. 如果你在属性中使用了这些常量,那么请使用上面介绍的`DynamicStringLength`. +* 更改`ConcurrencyStamp`最大长度为40. 你需要在更新ABP框架后**添加一个数据库迁移**并且更新数据库. +* NPM软件包的依赖关系使用`~`代替`^`,为了更加稳定. + +### ABP商业版 + +* 更改了应用程序标识的文件名.此前,它为每个主题使用了单独的标识文件,如`theme1.png`,`theme1-reverse.png`,`theme2.png`,`theme2-reverse.png`(...`6`).现在,我们只有两个标识文件:`logo-light.png`和`logo-dark.png`.所以,重命名MVC UI的`wwwroot/images/logo/`和Angular UI的`/src/assets/images/logo/`下的标识. +* 我们也增加了ABP商业版的[API文档](https://docs.abp.io/api-docs/commercial/3.0/api/index.html). + +> **此外,Angular UI请查看[迁移指南](https://github.com/abpframework/abp/blob/dev/docs/en/UI/Angular/Migration-Guide-v3.md)**. + +## 已知问题 + +* 3.0.0版本有一个分层架构的问题.查看[这个issue](https://github.com/abpframework/abp/pull/4564)为你的应用程序解决它,直到我们发布V3.0.1. + +## 关于未来的版本 + +我们将继续在每两个星期发布一个新的小功能版本.所以,下一个预计发布日期是**2020年7月16日**的**3.1**版本. + +在接下来的几个版本中,我们将专注于**Blazor UI**,如[路线图](https://docs.abp.io/en/abp/latest/Road-Map)中承诺的.我们将继续改进文档,创建示例,添加其他新功能和改善.关注[ABP框架Twitter账号](https://twitter.com/abpframework)获取最新消息... + +## 彩蛋: 文章! + +除了开发我们的产品,我们的团队一直在不断书写各种主题的文章/教程.你可能感兴趣的最新文章: + +* [Angular 10有哪些新增内容?](https://volosoft.com/blog/what-is-new-in-angular-10) +* [在分布式架构中使用ABP, SignalR和RabbitMQ构建实时消息系统](https://volosoft.com/blog/RealTime-Messaging-Distributed-Architecture-Abp-SingalR-RabbitMQ) +* [如何在Angular模板中使用Attribute Directives来避免重复](https://volosoft.com/blog/attribute-directives-to-avoid-repetition-in-angular-templates) diff --git a/docs/zh-Hans/Blog-Posts/2020-07-01 v3_0_Release/file-management-ui.png b/docs/zh-Hans/Blog-Posts/2020-07-01 v3_0_Release/file-management-ui.png new file mode 100644 index 0000000000..b5d5ea54e4 Binary files /dev/null and b/docs/zh-Hans/Blog-Posts/2020-07-01 v3_0_Release/file-management-ui.png differ diff --git a/docs/zh-Hans/CLI.md b/docs/zh-Hans/CLI.md index e733b6e2e5..4af252e89f 100644 --- a/docs/zh-Hans/CLI.md +++ b/docs/zh-Hans/CLI.md @@ -90,9 +90,10 @@ abp new Acme.BookStore * **`console`**: [Console template](Startup-Templates/Console.md). * `--output-folder` 或者 `-o`: 指定输出文件夹,默认是当前目录. * `--version` 或者 `-v`: 指定ABP和模板的版本.它可以是 [release tag](https://github.com/abpframework/abp/releases) 或者 [branch name](https://github.com/abpframework/abp/branches). 如果没有指定,则使用最新版本.大多数情况下,你会希望使用最新的版本. -* `--template-source` 或者 `-ts`: 指定自定义模板源用于生成项目,可以使用本地源和网络源(例如 `D\localTemplate` 或 `https://.zip`). +* `--preview`: 使用最新的预发行版本 (仅在未指定 `--version` 且最新稳定版本之后至少有一个预发行版时). +* `--template-source` 或者 `-ts`: 指定自定义模板源用于生成项目,可以使用本地源和网络源(例如 `D:\local-templat` 或 `https://.../my-template-file.zip`). * `--create-solution-folder` 或者 `-csf`: 指定项目是在输出文件夹中的新文件夹中还是直接在输出文件夹中. -* `--connection-string` 或者 `-cs`: 重写所有 `appsettings.json` 文件的默认连接字符串. 默认连接字符串是 `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true`. 如果你不想使用默认,你可以设置自己的连接字符串. 默认的数据库提供程序是 `SQL Server`, 所以你只能输入SQL Server连接字符串! +* `--connection-string` 或者 `-cs`: 重写所有 `appsettings.json` 文件的默认连接字符串. 默认连接字符串是 `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true`. 默认的数据库提供程序是 `SQL Server`. 如果你使用EF Core但需要更改DBMS,可以按[这里所述](Entity-Framework-Core-Other-DBMS.md)进行更改(创建解决方案之后). * `--local-framework-ref --abp-path`: 使用对项目的本地引用,而不是替换为NuGet包引用. ### add-package @@ -173,15 +174,15 @@ abp update [options] ### 切换到每晚构建(预览)包 -想要切换到ABP框架的最新预览版可以使用此命令. +想要切换到ABP框架的最新**每晚构建**预览版可以使用此命令. 用法: ````bash -abp switch-to-preview [options] +abp switch-to-nightly [options] ```` -你也可以使用切换回稳定版本: +你也可以使用切换回最新稳定版本: ````bash abp switch-to-stable [options] diff --git a/docs/zh-Hans/Connection-Strings.md b/docs/zh-Hans/Connection-Strings.md index 4694040309..cf3aed11fa 100644 --- a/docs/zh-Hans/Connection-Strings.md +++ b/docs/zh-Hans/Connection-Strings.md @@ -84,7 +84,6 @@ public class IdentityServerDbContext ABP定义了 `IConnectionStringResolver`,并在需要连接字符串时使用它. 有两个预构建的实现: * `DefaultConnectionStringResolver` 根据上面"配置连接字符串"一节中定义的规则,使用 `AbpDbConnectionOptions` 选择连接字符串. -* `MultiTenantConnectionStringResolver` used for multi-tenant applications and tries to get the configured connection string for the current tenant if available. It uses the `ITenantStore` to find the connection strings. It inherits from the `DefaultConnectionStringResolver` and fallbacks to the base logic if no connection string specified for the current tenant. * `MultiTenantConnectionStringResolver` 用于多租户应用程序,并尝试获取当前租户的已配置连接字符串(如果有). 它使用 `ITenantStore` 查找连接字符串. 它继承了 `DefaultConnectionStringResolver`, 如果没有为当前租户指定连接字符串则回退到基本逻辑. -如果需要自定义逻辑来确定连接字符串,可以实现 `IConnectionStringResolver` 接口(也可以从现有类派生)并使用[依赖注入](Dependency-Injection.md)系统替换现有实现. \ No newline at end of file +如果需要自定义逻辑来确定连接字符串,可以实现 `IConnectionStringResolver` 接口(也可以从现有类派生)并使用[依赖注入](Dependency-Injection.md)系统替换现有实现. diff --git a/docs/zh-Hans/CurrentUser.md b/docs/zh-Hans/CurrentUser.md index 49ebaaaba0..8d930b0911 100644 --- a/docs/zh-Hans/CurrentUser.md +++ b/docs/zh-Hans/CurrentUser.md @@ -86,7 +86,7 @@ namespace AbpDemo ## ICurrentPrincipalAccessor -`ICurrentPrincipalAccessor` 是当需要当前用户的principle时使用的服务(由ABP框架和你的应用程序代码使用). +`ICurrentPrincipalAccessor` 是当需要当前用户的Principal时使用的服务(由ABP框架和你的应用程序代码使用). 对于Web应用程序, 它获取当前 `HttpContext` 的 `User` 属性,对于非Web应用程序它将返回 `Thread.CurrentPrincipal`. @@ -114,9 +114,9 @@ public class MyService : ITransientDependency } ```` -### 更改当前Principle +### 更改当前Principal -除了某些高级场景外,你不需要设置或更改当前principle. 如果需要可以使用 `ICurrentPrincipalAccessor` 的 `Change` 方法. 它接受一个 `ClaimsPrinciple` 对象并使其成为作用域的"当前"对象. +除了某些高级场景外,你不需要设置或更改当前Principal. 如果需要可以使用 `ICurrentPrincipalAccessor` 的 `Change` 方法. 它接受一个 `ClaimsPrincipal` 对象并使其成为作用域的"当前"对象. 示例: @@ -132,7 +132,7 @@ public class MyAppService : ApplicationService public void Foo() { - var newPrinciple = new ClaimsPrincipal( + var newPrincipal = new ClaimsPrincipal( new ClaimsIdentity( new Claim[] { @@ -143,7 +143,7 @@ public class MyAppService : ApplicationService ) ); - using (_currentPrincipalAccessor.Change(newPrinciple)) + using (_currentPrincipalAccessor.Change(newPrincipal)) { var userName = CurrentUser.UserName; //returns "john" //... @@ -164,4 +164,4 @@ public class MyAppService : ApplicationService * 其他属性,如 `EmailVerified`, `PhoneNumber`, `TenantId` ...是由ABP框架通过尽可能遵循标准名称来定义的. -建议使用这个类的属性来代替声明名称的魔术字符串. \ No newline at end of file +建议使用这个类的属性来代替声明名称的魔术字符串. diff --git a/docs/zh-Hans/Entity-Framework-Core-Migrations.md b/docs/zh-Hans/Entity-Framework-Core-Migrations.md index 77f5ac5cd7..876d98721b 100644 --- a/docs/zh-Hans/Entity-Framework-Core-Migrations.md +++ b/docs/zh-Hans/Entity-Framework-Core-Migrations.md @@ -3,7 +3,7 @@ 本文首先介绍[应用程序启动模板](Startup-Templates/Application.md)提供的**默认结构**,并讨论你可能希望为自己的应用程序实现的**各种场景**. -> 本文档适用于希望完全理解和自定义[应用程序启动模板](Startup-Templates/Application.md)附带的数据库结构的人员. 如果你只是想创建实体和管理代码优先(code first)迁移,只需要遵循[启动教程](Tutorials/Index.md). +> 本文档适用于希望完全理解和自定义[应用程序启动模板](Startup-Templates/Application.md)附带的数据库结构的人员. 如果你只是想创建实体和管理代码优先(code first)迁移,只需要遵循[启动教程](Tutorials/Part-1.md). ### 源码 diff --git a/docs/zh-Hans/Getting-Started-Console-Application.md b/docs/zh-Hans/Getting-Started-Console-Application.md index cac3d2f153..1bf48cefd4 100644 --- a/docs/zh-Hans/Getting-Started-Console-Application.md +++ b/docs/zh-Hans/Getting-Started-Console-Application.md @@ -1,124 +1,3 @@ ## 在控制台应用中使用ABP -本教程将介绍如何从头开始以最小的依赖关系启动ABP. 你通常希望以 **[启动模板](https://abp.io/Templates)** 开头. - -### 创建一个新项目 - -使用Visual Studio创建一个新的.Net Core Console应用程序: - -![](images/create-new-net-core-console-application.png) - -### 安装 Volo.Abp 包 - -Volo.Abp.Core是创建基于ABP的应用程序的核心nuget包. 所以,将它安装到你的项目中: - -```` -Install-Package Volo.Abp.Core -```` - -### 创建第一个ABP模块 - -ABP是一个模块化框架, 它需要一个从``AbpModule``类派生的 **启动(根)模块** 类: - -````C# -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Modularity; - -namespace AbpConsoleDemo -{ - public class AppModule : AbpModule - { - - } -} -```` - -``AppModule`` 是应用程序启动模块的好名称. - -### 初始化应用程序 - -下一步是使用上面创建的启动模块引导应用程序: - -````C# -using System; -using Volo.Abp; - -namespace AbpConsoleDemo -{ - class Program - { - static void Main(string[] args) - { - using (var application = AbpApplicationFactory.Create()) - { - application.Initialize(); - - Console.WriteLine("Press ENTER to stop application..."); - Console.ReadLine(); - } - } - } -} - -```` - -``AbpApplicationFactory`` 用于创建应用程序并加载所有以``AppModule``作为启动模块的模块. ``Initialize()``方法启动应用程序. - -### Hello World! - -上面的应用程序什么都不做, 让我们创建一个服务做一些事情: - -````C# -using System; -using Volo.Abp.DependencyInjection; - -namespace AbpConsoleDemo -{ - public class HelloWorldService : ITransientDependency - { - public void SayHello() - { - Console.WriteLine("Hello World!"); - } - } -} - -```` - -``ITransientDependency``是ABP的一个特殊接口, 它自动将服务注册为**Transient**(参见[依赖注入文档](Dependency-Injection.md)). - -现在,我们可以解析``HelloWorldService``并调用``SayHello``. 更改Program.cs, 如下所示: - -````C# -using System; -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp; - -namespace AbpConsoleDemo -{ - class Program - { - static void Main(string[] args) - { - using (var application = AbpApplicationFactory.Create()) - { - application.Initialize(); - - // 解析服务并使用它 - var helloWorldService = - application.ServiceProvider.GetService(); - helloWorldService.SayHello(); - - Console.WriteLine("Press ENTER to stop application..."); - Console.ReadLine(); - } - } - } -} -```` - -这对于这个简单的代码示例已足够, 如果是直接从``IServiceProvider``解析建议创建**Scoped**依赖.(参见[依赖注入文档](Dependency-Injection.md)). - -### 源码 - -从[这里](https://github.com/abpframework/abp-samples/tree/master/BasicConsoleApplication)获取本教程中创建的示例项目的源代码. \ No newline at end of file +ABP提供了控制台应用程序启动模板. 参阅[控制台应用程序启动模板]文档了解更多信息. \ No newline at end of file diff --git a/docs/zh-Hans/Getting-Started-React-Native.md b/docs/zh-Hans/Getting-Started-React-Native.md new file mode 100644 index 0000000000..54d093cb7c --- /dev/null +++ b/docs/zh-Hans/Getting-Started-React-Native.md @@ -0,0 +1,70 @@ +# React Native入门 + +ABP平台提供了[React Native](https://reactnative.dev/)模板用于开发移动应用程序. + +当你按照[入门文档](Getting-Started.md)中所述**创建新应用程序**时,解决方案默认将React Native应用程序包含在 `react-native` 文件夹中. + +## 配置你的本地IP地址 + +运行在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上的[Android Studio模拟器](https://docs.expo.io/workflow/android-simulator/)和[iOS模拟器文档](https://docs.expo.io/workflow/ios-simulator/). + +![React Native login screen on iPhone 11](images/rn-login-iphone.png) + +输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序. + +应用程序已经启动并执行,你可以基于该启动模板开发应用程序. \ No newline at end of file diff --git a/docs/zh-Hans/Getting-Started.md b/docs/zh-Hans/Getting-Started.md index adf9517eec..fe1a9ec6f9 100644 --- a/docs/zh-Hans/Getting-Started.md +++ b/docs/zh-Hans/Getting-Started.md @@ -19,24 +19,27 @@ 你需要安装以下工具: -* [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/) +* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/) for Windows / [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/).[1](#f-editor) +* [.NET Core 3.1+](https://www.microsoft.com/net/download/dotnet-core/) -* [Node v12+](https://nodejs.org) +* [Node v12 或 v14](https://nodejs.org/en/) * [Yarn v1.19+](https://classic.yarnpkg.com/) +* [Yarn v1.20+ (not v2)](https://classic.yarnpkg.com/en/docs/install) [2](#f-yarn) 或 npm v6+ (与Node一起安装) {{ if Tiered == "Yes" }} -* [Redis](https://redis.io/): 应用程序将Redis用作[分布式缓存](../Caching.md). 因此你需要安装并运行Redis. +* [Redis](https://redis.io/): 入门解决方案将Redis用作[分布式缓存](Caching.md). 因此你需要安装并运行Redis. {{ end }} -> 你可以也使用其他支持.NET Core 和 ASP.NET Core的编辑器. +1 _只要支持.NET Core和ASP.NET Core,就可以使用其他编辑器代替Visual Studio._ [↩](#a-editor) + +2 _Yarn v2 的工作方式不同,不受支持._ [↩](#a-yarn) ### 安装ABP CLI -[ABP CLI](./CLI.md)是一个命令行页面,用于为基于ABP的应用程序验证和自动化一些任务. +[ABP CLI](./CLI.md)是一个命令行页面,用于自动执行一些基于ABP的应用程序的常见任务. -> ABP CLI是[ABP框架](https://abp.io/)一个免费开源的工具. +> ABP CLI是ABP框架一个免费开源的工具. 你需要使用以下命令安排ABP CLI: @@ -59,10 +62,10 @@ dotnet tool update -g Volo.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}} +abp new Acme.BookStore{{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). +* 此命令还会在解决方案文件夹内创建一个React Native移动应用程序. 如果你不想要它,可以安全地删除它,或者在 `abp new` 命令中指定 `-m none` 选项,以使其完全不包含在解决方案中. {{ if UI == "NG" }} @@ -70,7 +73,7 @@ abp new Acme.BookStore -t app{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mon {{ if Tiered == "Yes" }} -* `--separate-identity-server` 参数用于将Identity服务器应用程序与API主机应用程序分隔开. 如果未指定,你将只有一个端点. +* `--separate-identity-server` 参数用于将Identity服务器应用程序与API主机应用程序分隔开. 如果未指定,则服务器上将只有一个端点. {{ end }} @@ -90,6 +93,12 @@ abp new Acme.BookStore -t app{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mon > 你可以使用不同级别的命令空间; 例如. BookStore, Acme.BookStore or Acme.Retail.BookStore. +#### ABP CLI 命令 & 选项 + +[ABP CLI文档](./CLI.md)涵盖了ABP CLI的所有可用命令和选项. 本文档使用[应用程序启动模板](Startup-Templates/Application.md)创建新的Web应用程序. 有关其他模板,请参见[ABP启动模板](Startup-Templates/Index.md)文档. + +> 或者,您可以从[ABP Framework网站](https://abp.io/get-started)中选择"直接下载"选项卡创建新的解决方案. + ## 解决方案结构 {{ if UI == "MVC" }} @@ -146,7 +155,7 @@ abp new Acme.BookStore -t app{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mon ## 创建数据库 -### 数据库连接字符串 +### 连接字符串 检查 {{if UI == "MVC"}}{{if Tiered == "Yes"}}`.IdentityServer` 和 `.HttpApi.Host` 项目{{else}}`.Web` 项目{{end}}{{else if UI == "NG" }}`.HttpApi.Host` 项目{{end}}下 `appsettings.json` 文件中的 **链接字符串**: @@ -214,7 +223,9 @@ Ef Core具有`Update-Database`命令, 可根据需要创建数据库并应用挂 这将基于配置的连接字符串创建新数据库. -> 使用`.Migrator`工具是建议的方法, 因为它还能初始化初始数据能够正确运行Web应用程序. +> **使用`.DbMigrator`工具是建议的方法**, 因为它能初始化初始数据能够正确运行Web应用程序. +> +> 如果你只是使用 `Update-Database` 命令,你会得到一个空数据库,所以你无法登录到应用程序因为数据库中没有初始管理用户. 不需要种子数据库时,可以在开发期间使用 `Update-Database` 命令. 但是使用 `.DbMigrator` 应用程序会更简单,你始终可以使用它来迁移模式并为数据库添加种子. {{ else if DB == "Mongo" }} @@ -240,7 +251,7 @@ Ef Core具有`Update-Database`命令, 可根据需要创建数据库并应用挂 ![db-migrator-output](images/db-migrator-output.png) -> 数据库创建后会初始化种子数据, 其中包含用于登录的 `admin` 用户. 所以你至少使用 `.DbMigrator` 一次. +> 数据库创建后会初始化[种子数据](Data-Seeding.md), 其中包含用于登录的 `admin` 用户. 所以你至少使用 `.DbMigrator` 一次. {{ end }} @@ -336,83 +347,18 @@ yarn start {{ end }} -输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序. - -![bookstore-home](images/bookstore-home.png) - -应用程序已经启动并执行,你可以基于该启动模板开发应用程序. +输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序,应用程序已经启动并执行,你可以基于此启动模板开始开发应用程序. #### 移动开发 -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上的[Android Studio模拟器](https://docs.expo.io/workflow/android-simulator/)和[iOS模拟器文档](https://docs.expo.io/workflow/ios-simulator/). - -![React Native login screen on iPhone 11](images/rn-login-iphone.png) +当你创建一个新的应用程序时.该解决方案默认包含 `react-native`文件夹. 这是一个基础的[React Native](https://reactnative.dev/)启动模板,用于开发与基于ABP的后端集成的移动应用程序. -输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序. +如果你不计划使用React Native开发移动应用程序,你可以忽略并删除 `react-native` 文件夹. -应用程序已经启动并执行,你可以基于该启动模板开发应用程序. +> 你可以在ABP CLI中指定 `-m none` 选项,以使 `react-native` 目录完全不包含在解决方案中 -> [应用程序启动模板](Startup-Templates/Application.md) 包含租户管理和Identity模块. +请参阅"[React Native入门](Getting-Started-React-Native.md)"文档了解如何配置和运行React Native应用程序. ## 下一步是什么? -[应用程序开发教程](Tutorials/Part-1.md) +[Web应用程序开发教程](Tutorials/Part-1.md) diff --git a/docs/zh-Hans/How-To/Azure-Active-Directory-Authentication-MVC.md b/docs/zh-Hans/How-To/Azure-Active-Directory-Authentication-MVC.md index 34aab9fa46..7117cc01b0 100644 --- a/docs/zh-Hans/How-To/Azure-Active-Directory-Authentication-MVC.md +++ b/docs/zh-Hans/How-To/Azure-Active-Directory-Authentication-MVC.md @@ -136,7 +136,7 @@ private void ConfigureAuthentication(ServiceConfigurationContext context, IConfi ## 本文的源代码 -你可以在[这里](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization)找到已完成的示例源码. +你可以在[这里](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization)找到已完成的示例源码. # FAQ diff --git a/docs/zh-Hans/How-To/Customize-Login-Page-MVC.md b/docs/zh-Hans/How-To/Customize-Login-Page-MVC.md index 3414887c9d..bba8e80b75 100644 --- a/docs/zh-Hans/How-To/Customize-Login-Page-MVC.md +++ b/docs/zh-Hans/How-To/Customize-Login-Page-MVC.md @@ -105,7 +105,7 @@ public class CustomLoginModel : LoginModel ## 本文的源代码 -你可以在[这里](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization)找到已完成的示例源码. +你可以在[这里](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization)找到已完成的示例源码. ## 另请参阅 diff --git a/docs/zh-Hans/How-To/Customize-SignIn-Manager.md b/docs/zh-Hans/How-To/Customize-SignIn-Manager.md index 2b6f7a612f..73cbcd2ce7 100644 --- a/docs/zh-Hans/How-To/Customize-SignIn-Manager.md +++ b/docs/zh-Hans/How-To/Customize-SignIn-Manager.md @@ -93,7 +93,7 @@ PreConfigure(identityBuilder => ## 本文的源代码 -你可以在[这里](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization)找到已完成的示例源码. +你可以在[这里](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization)找到已完成的示例源码. ## 另请参阅 diff --git a/docs/zh-Hans/Index.md b/docs/zh-Hans/Index.md index 3dd728c846..d409f1e8ab 100644 --- a/docs/zh-Hans/Index.md +++ b/docs/zh-Hans/Index.md @@ -4,23 +4,17 @@ ABP是一个**开源应用程序框架**,专注于基于ASP.NET Core的Web应用程序开发,但也支持开发其他类型的应用程序. -浏览左侧导航菜单以深入了解文档. +浏览导航菜单以深入了解文档. ## 入门 -使用ABP开发新项目的最简单方法是使用启动模板: +使用ABP开发新Web应用程序的最简单方法是使用[入门](Getting-Started.md)教程: -* [ASP.NET Core MVC (Razor Pages) UI 启动模板](Getting-Started.md?UI=MVC&DB=EF&Tiered=No) -* [Angular UI 启动模板](Getting-Started.md?UI=NG&DB=EF&Tiered=No) - -如果你想从头开始(使用空项目),请手动安装ABP框架并使用以下教程: - -* [控制台应用程序](Getting-Started-Console-Application.md) -* [ASP.NET Core Web 应用程序](Getting-Started-AspNetCore-Application.md) +然后你可以继续进行[Web应用程序开发教程](Tutorials/Part-1.md). ## 源码 -ABP托管在GitHub上, 参见[源代码](https://github.com/abpframework/abp). +ABP托管在GitHub上, 参见[源代码](https://github.com/abpframework). ## 贡献代码 diff --git a/docs/zh-Hans/Local-Event-Bus.md b/docs/zh-Hans/Local-Event-Bus.md index b5c472734b..44e21d0384 100644 --- a/docs/zh-Hans/Local-Event-Bus.md +++ b/docs/zh-Hans/Local-Event-Bus.md @@ -203,9 +203,9 @@ namespace AbpDemo 事件类型; -* `EntityCreatedEventData` 当实体创建创建成功后发布. -* `EntityUpdatedEventData` 当实体创建更新成功后发布. -* `EntityDeletedEventData` 当实体创建删除成功后发布. +* `EntityCreatedEventData` 当实体创建成功后发布. +* `EntityUpdatedEventData` 当实体更新成功后发布. +* `EntityDeletedEventData` 当实体删除成功后发布. * `EntityChangedEventData` 当实体创建,更新,删除后发布. 如果你需要监听任何类型的更改,它是一种快捷方式 - 而不是订阅单个事件. ### 用于进行时态事件 @@ -224,4 +224,4 @@ namespace AbpDemo 在将更改保存到数据库时发布预构建事件; * 对于 EF Core, 他们在 `DbContext.SaveChanges` 发布. -* 对于 MongoDB, 在你调用仓储的 `InsertAsync`, `UpdateAsync` 或 `DeleteAsync` 方法发布(因为MongoDB没有更改追踪系统). \ No newline at end of file +* 对于 MongoDB, 在你调用仓储的 `InsertAsync`, `UpdateAsync` 或 `DeleteAsync` 方法发布(因为MongoDB没有更改追踪系统). diff --git a/docs/zh-Hans/Modules/Docs.md b/docs/zh-Hans/Modules/Docs.md index 63759d1462..acc956be1b 100644 --- a/docs/zh-Hans/Modules/Docs.md +++ b/docs/zh-Hans/Modules/Docs.md @@ -611,7 +611,7 @@ Configure(options => 应用程序启动后如果`Index`不存在则会自动创建`Index`. `DefaultElasticClientProvider`负责创建`IElasticClient`, 默认情况下它会从`IConfiguration`中读取Elastic Search的`Url`. -如果你的IElasticClient需要其它配置请使用重写IElasticClientProvider服务并在依赖注入系统中替换它. +如果你的 `IElasticClient` 需要其它配置请使用重写 `IElasticClientProvider` 服务并在[依赖注入](../Dependency-Injection.md)系统中替换它. ``` { "ElasticSearch": { diff --git a/docs/zh-Hans/Modules/Identity.md b/docs/zh-Hans/Modules/Identity.md index ea50006de9..f396de00c1 100644 --- a/docs/zh-Hans/Modules/Identity.md +++ b/docs/zh-Hans/Modules/Identity.md @@ -1,3 +1,5 @@ # 身份管理模块 +身份模块基于Microsoft Identity 库用于管理[组织单元](Organization-Units.md), 角色, 用户和他们的权限. + 参阅 [源码](https://github.com/abpframework/abp/tree/dev/modules/identity). 文档很快会被完善. \ No newline at end of file diff --git a/docs/zh-Hans/Modules/Index.md b/docs/zh-Hans/Modules/Index.md index a24a95c121..dab2f838df 100644 --- a/docs/zh-Hans/Modules/Index.md +++ b/docs/zh-Hans/Modules/Index.md @@ -17,7 +17,7 @@ ABP是一个 **模块化的应用程序框架** 由十多个 **nuget packages** * **Blogging**: 用于创建精美的博客. ABP的[博客](https://blog.abp.io/) 就使用了此模块. * [**Docs**](Docs.md): 用于创建技术文档页面. ABP的[文档](https://abp.io/documents/) 就使用了此模块. * **Feature Management**: 用于保存和管理功能. -* **Identity**: 基于Microsoft Identity管理角色,用户和他们的权限. +* [**Identity**](Identity.md): 基于Microsoft Identity管理组织单元,角色,用户和他们的权限. * **Identity Server**: 集成了IdentityServer4. * **Permission Management**: 用于保存权限. * **Setting Management**: 用于保存设置. diff --git a/docs/zh-Hans/Modules/Organization-Units.md b/docs/zh-Hans/Modules/Organization-Units.md new file mode 100644 index 0000000000..8047b6a48d --- /dev/null +++ b/docs/zh-Hans/Modules/Organization-Units.md @@ -0,0 +1,47 @@ +# 组织单元管理 + +组织单元(OU)是"身份模块"的一部分,用于**对用户和实体进行分层分组**. + +### OrganizationUnit 实体 + +OU由 **OrganizationUnit** 实体表示. 实体有以下基本属性: + +- **TenantId**: OU租户的ID,为null代表是宿主OU. +- **ParentId**: OU的父亲Id,为null为根OU. +- **Code**: 租户唯一的分层字符串代码. +- **DisplayName**: OU的显示名称. + +OrganizationUnit实体的主键(Id)是 **Guid** 类型,派生自[**FullAuditedAggregateRoot**](../Entities.md)类. + +#### Organization 树 + +因为OU可以有父亲,租户所有的OU是一个**树**结构. 树有一些规则: + +- 可以有多个根(`ParentId` 为 `null`). +- OU的第一级子级数有限制(面说明的固定OU代码单位长度). + +#### OU Code + +OU代码由OrganizationUnit Manager自动生成和维护. 看起来像这样的字符串: + +"**00001.00042.00005**" + +此代码可用于轻松查询数据库中OU的所有子级(递归). 代码有一些规则: + +- 必须[租户](../Multi-Tenancy.md)**唯一**的. +- 同一OU的所有子代均以**父OU的代码开头**. +- 它是**固定长度**的,并且基于树中OU的级别,如示例中所示. +- 虽然OU代码是唯一的,但是如果移动OU,它是**可更改的**. +- 你必须通过Id而不是代码引用OU. + +### OrganizationUnit Manager + +可以注入 **OrganizationUnitManager** 管理OU. 常见的用例有: + +- 创建,更改或删除OU. +- 在OU树中移动OU. +- 获取有关OU树及其项的信息 + +#### 多租户 + +`OrganizationUnitManager` 设置为一次性为 **单个租户** 工作,默认是 **当前租户**. \ No newline at end of file diff --git a/docs/zh-Hans/Nightly-Builds.md b/docs/zh-Hans/Nightly-Builds.md index 84ea243b05..020d69e02e 100644 --- a/docs/zh-Hans/Nightly-Builds.md +++ b/docs/zh-Hans/Nightly-Builds.md @@ -29,13 +29,13 @@ 预览NPM包的最新版本可以通过在应用程序的根文件夹命令运行命令安装: ```bash -abp switch-to-preview +abp switch-to-preview --npm ``` 如果你正在使用ABP框架预览包,你可以使用此命令切换回稳定版本: ```bash -abp switch-to-stable +abp switch-to-stable --npm ``` 参阅 [ABP CLI 文档](./CLI.md) 了解更多信息. \ No newline at end of file diff --git a/docs/zh-Hans/Options.md b/docs/zh-Hans/Options.md index c88ff0adda..5bb4b29a8b 100644 --- a/docs/zh-Hans/Options.md +++ b/docs/zh-Hans/Options.md @@ -82,7 +82,7 @@ public class MyService : ITransientDependency 对于此类情况,ABP为 `IServiceCollection` 引入了 `PreConfigure` 和 `ExecutePreConfiguredActions` 扩展方法. 该模式的工作原理如下所述. -1. 你的模块中定义计划选项类. 例: +在你的模块中定义预先选项类. 例: ````csharp public class MyPreOptions diff --git a/docs/zh-Hans/Repositories.md b/docs/zh-Hans/Repositories.md index 18bfa5f77d..ee48186985 100644 --- a/docs/zh-Hans/Repositories.md +++ b/docs/zh-Hans/Repositories.md @@ -193,8 +193,6 @@ var people = ((IMongoQueryable)_personRepository ### 选项-3: IAsyncQueryableExecuter -> 注意,此功能在ABP框架3.0以之后的版本可用,虽然它也可以用于较早的版本,但它提供的方法非常有限. - `IAsyncQueryableExecuter` 是一个用于异步执行 `IQueryable` 对象的服务,**不依赖于实际的数据库提供程序**. **示例: 注入并使用 `IAsyncQueryableExecuter.ToListAsync()` 方法** diff --git a/docs/zh-Hans/Road-Map.md b/docs/zh-Hans/Road-Map.md index f18441cb41..d8a277ac0e 100644 --- a/docs/zh-Hans/Road-Map.md +++ b/docs/zh-Hans/Road-Map.md @@ -9,7 +9,6 @@ 除了中期目录,还有一些[积压](https://github.com/abpframework/abp/milestone/2)的功能, 这里积压中重要功能的列表: -* [#4098](https://github.com/abpframework/abp/issues/4098) / Blob存储Azure提供者. * [#2882](https://github.com/abpframework/abp/issues/2882) / 提供 **gRPC集成** 基础设施 (虽然[已经可以](https://github.com/abpframework/abp-samples/tree/master/GrpcDemo)为你的应用程序创建和使用gRPC端点,但我们计划为所有[标准应用程序模块](https://docs.abp.io/en/abp/latest/Modules/Index)创建端点) * [#236](https://github.com/abpframework/abp/issues/236) 基于权限系统的资源 * [#1754](https://github.com/abpframework/abp/issues/1754) / 多语言实体 diff --git a/docs/zh-Hans/Samples/Index.md b/docs/zh-Hans/Samples/Index.md index 12fcf0ac97..94e5392b51 100644 --- a/docs/zh-Hans/Samples/Index.md +++ b/docs/zh-Hans/Samples/Index.md @@ -7,7 +7,7 @@ 演示如何基于微服务体系结构构建系统的完整解决方案. * [示例的文档](Microservice-Demo.md) -* [源码](https://github.com/abpframework/abp/tree/dev/samples/MicroserviceDemo) +* [源码](https://github.com/abpframework/abp-samples/tree/master/MicroserviceDemo) * [微服务架构文档](../Microservice-Architecture.md) ### Book Store @@ -48,8 +48,10 @@ * **文本模板 Demo**: 文本模板系统的不同用例. * [源码](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo) * [文本模板文档](../Text-Templating.md) +* **存储过程 Demo**: 演示如何以最佳实践使用存储过程,数据库视图和函数. + * [源码](https://github.com/abpframework/abp-samples/tree/master/StoredProcedureDemo) * **自定义认证**: 如何为ASP.NET Core MVC / Razor Pages应用程序自定义身份验证的解决方案. - * [源码](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization) + * [源码](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization) * 相关 "[How To](../How-To/Index.md)" 文档: * [Azure Active Directory 认证](../How-To/Azure-Active-Directory-Authentication-MVC.md) * [自定义登录页面](../How-To/Customize-Login-Page-MVC.md) diff --git a/docs/zh-Hans/Samples/Microservice-Demo.md b/docs/zh-Hans/Samples/Microservice-Demo.md index b0134eddde..59977908a1 100644 --- a/docs/zh-Hans/Samples/Microservice-Demo.md +++ b/docs/zh-Hans/Samples/Microservice-Demo.md @@ -28,7 +28,7 @@ ABP框架的主要目标之一就是提供[便捷的基础设施来创建微服 ### 源码 -你可以从[GitHub仓库](https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo)获取源码. +你可以从[GitHub仓库](https://github.com/abpframework/abp-samples/tree/master/MicroserviceDemo)获取源码. ## 运行解决方案 @@ -43,6 +43,12 @@ ABP框架的主要目标之一就是提供[便捷的基础设施来创建微服 * [ElasticSearch](https://www.elastic.co/downloads/elasticsearch) 6.6+ * [Kibana](https://www.elastic.co/downloads/kibana) 6.6+ (可选,建议显示日志) +### 运行基础设施 + +- 默认情况下运行Docker-compose可以轻松运行预先要求. 如果你没有安装,在windows环境下你可以在[这里](https://docs.docker.com/docker-for-windows/install/)下载并使用[Docker for Windows](https://docs.docker.com/docker-for-windows/). +* 在 `MicroserviceDemo` 目录下运行命令 `docker-compose -f docker-compose.infrastructure.yml -f docker-compose.infrastructure.override.yml up -d` 或使用powershell运行 `MicroserviceDemo/_run` 文件夹下的 `__Run_Infrastructure.ps1` 脚本. +* 如果你不想使用docker运行预先要求的服务在你的本地环境,你需要更改微服务示例解决方案项目中的 `appsettings.json` 文件. + ### 打开并构建Visual Studio解决方案 * 在Visual Studio 2017 (15.9.0+)中打开`samples\MicroserviceDemo\MicroserviceDemo.sln`. diff --git a/docs/zh-Hans/SignalR-Integration.md b/docs/zh-Hans/SignalR-Integration.md index 5e17b1c75c..bdf4a7a786 100644 --- a/docs/zh-Hans/SignalR-Integration.md +++ b/docs/zh-Hans/SignalR-Integration.md @@ -115,7 +115,7 @@ public class MessagingHub : Hub } ```` -`MessasingHub` 集线器的路由为 `/signalr-hubs/messasing`: +`MessagingHub` 集线器的路由为 `/signalr-hubs/messaging`: * 添加了标准 `/signalr-hubs/` 前缀. * 使用**驼峰命名**集线器名称,不包含 `Hub` 后缀. @@ -123,7 +123,7 @@ public class MessagingHub : Hub 如果你想指定路由,你可以使用 `HubRoute` attribute: ````csharp -[HubRoute("/my-messasing-hub")] +[HubRoute("/my-messaging-hub")] public class MessagingHub : Hub { //... diff --git a/docs/zh-Hans/Startup-Templates/Application.md b/docs/zh-Hans/Startup-Templates/Application.md index 4e2c3f6700..44ea5adfaf 100644 --- a/docs/zh-Hans/Startup-Templates/Application.md +++ b/docs/zh-Hans/Startup-Templates/Application.md @@ -423,7 +423,11 @@ Screens 是通过在 `src/screens` 文件夹中创建将名称分开的文件夹 * [Formik](https://github.com/jaredpalmer/formik) 用于构建表单. * [Yup](https://github.com/jquense/yup) 用于表单验证. +## 社交/外部登录 + +如果你想要为你的应用程序配置社交/外部登录,请参阅[社交/外部登录](../Authentication/Social-External-Logins.md)文档. + ## 下一步是什么? -* 参阅[ASP.NET Core MVC 模板入门](../Getting-Started-AspNetCore-MVC-Template.md)创建此模板的新解决方案并运行它. -* 参阅[ASP.NET Core MVC 教程](../Tutorials/AspNetCore-Mvc/Part-I.md)学习使用此模板开发应用程序. +* [入门文档](../Getting-Started.md)介绍了如何在几分钟内创建新应用程序. +* [Web应用程序开发教程](../Tutorials/Part-1.md)逐步介绍了应用程序开发. diff --git a/docs/zh-Hans/Startup-Templates/Console.md b/docs/zh-Hans/Startup-Templates/Console.md index caeb036d3d..c7299cd938 100644 --- a/docs/zh-Hans/Startup-Templates/Console.md +++ b/docs/zh-Hans/Startup-Templates/Console.md @@ -16,4 +16,12 @@ dotnet tool install -g Volo.Abp.Cli abp new Acme.MyConsoleApp -t console ```` -`Acme.MyConsoleApp` 是解决方案的名称, 如*YourCompany.YourProduct*. 你可以使用单级或多级名称. \ No newline at end of file +`Acme.MyConsoleApp` 是解决方案的名称, 如*YourCompany.YourProduct*. 你可以使用单级或多级名称. + +## 解决方案结构 + +使用以上命令创建解决方案后,你会得到如下所示的解决方案: + +![basic-console-application-solution](../images/basic-console-application-solution.png) + +* `HelloWorldService` 是一个实现了 `ITransientDependency` 接口的示例服务. 它会自动注册到[依赖注入](../Dependency-Injection.md)系统. \ No newline at end of file diff --git a/docs/zh-Hans/Startup-Templates/Module.md b/docs/zh-Hans/Startup-Templates/Module.md index a6706ad783..03f87a5c0c 100644 --- a/docs/zh-Hans/Startup-Templates/Module.md +++ b/docs/zh-Hans/Startup-Templates/Module.md @@ -56,7 +56,7 @@ abp new Acme.IssueManagement -t module --no-ui ### .Domain 项目 -解决方案的领域层. 它主要包含 [实体, 集合根](../Entities.md), [领域服务](../Domain-Services.md), [值类型](../Value-Types.md), [仓储接口](../Repositories.md) 和解决方案的其他领域对象. +解决方案的领域层. 它主要包含 [实体, 集合根](../Entities.md), [领域服务](../Domain-Services.md), 值类型, [仓储接口](../Repositories.md) 和解决方案的其他领域对象. 例如 `Issue` 实体, `IssueManager` 领域服务和 `IIssueRepository` 接口都适合放在这个项目中. @@ -149,6 +149,10 @@ abp new Acme.IssueManagement -t module --no-ui `.Web.Host` 项目使用OpenId Connect身份认证从`.IdentityServer`获取当前用户的身份和访问令牌. 然后使用访问令牌调用 `.HttpApi.Host`. HTTP API 服务器使用bearer token验证访问令牌获取当前用户声明并授权用户. +##### 前置条件 + +* [Redis](https://redis.io/): 应用程序使用Redis做分布式缓存,你需要安装并运行Redis. + ##### 如何运行? 你需要按照以下顺序运行应用程序: diff --git a/docs/zh-Hans/Text-Templating.md b/docs/zh-Hans/Text-Templating.md index 0785dbba29..be9cbb3a79 100644 --- a/docs/zh-Hans/Text-Templating.md +++ b/docs/zh-Hans/Text-Templating.md @@ -4,7 +4,7 @@ ABP框架提供了一个简单有效的文本模板系统,文本模板用于动态渲染基于模板和模型(数据对象)内容: -***TEMPLATE + MODEL ==render==> RENDERED CONTENT*** +Template + Model =renderer=> Rendered Content 它非常类似于 ASP.NET Core Razor View (或 Page): diff --git a/docs/zh-Hans/Tutorials/Part-1.md b/docs/zh-Hans/Tutorials/Part-1.md index 9a71643a51..101835559b 100644 --- a/docs/zh-Hans/Tutorials/Part-1.md +++ b/docs/zh-Hans/Tutorials/Part-1.md @@ -1,128 +1,75 @@ -## ASP.NET Core {{UI_Value}} 教程 - 第一章 +# Web应用程序开发教程 - 第一章: 创建服务端 ````json //[doc-params] { - "UI": ["MVC","NG"] + "UI": ["MVC","NG"], + "DB": ["EF","Mongo"] } ```` {{ 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 +if DB == "EF" + DB_Text="Entity Framework Core" +else if DB == "Mongo" + DB_Text="MongoDB" +else + DB_Text="?" +end }} -### 关于本教程 - -在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的应用程序. **{{DB_Text}}**将用作ORM提供者,前端使用{{UI_Value}} 和 JavaScript. - -ASP.NET Core {{UI_Value}} 系列教程包括三个3个部分: - -- **Part-1: 创建项目和书籍列表页面(本章)** -- [Part-2: 创建,编辑,删除书籍](Part-2.md) -- [Part-3: 集成测试](Part-3.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` 命令应用迁移. +## 关于本教程 -#### 初始化数据库表 +在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的: -![Initial database tables](./images/bookstore-database-tables-{{DB}}.png) +* **{{DB_Text}}** 做为ORM提供程序. +* **{{UI_Value}}** 做为UI框架. -### 运行应用程序 +本教程分为以下部分: -右键单击{{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}} +- **Part 1: 创建服务端 (本章)** +- [Part 2: 图书列表页面](Part-2.md) +- [Part 3: 创建,更新和删除图书](Part-3.md) +- [Part 4: 集成测试](Part-4.md) +- [Part 5: 授权](Part-5.md) +- [Part 6: 作者: 领域层](Part-6.md) +- [Part 7: 作者: 数据库集成](Part-7.md) +- [Part 8: 作者: 应用服务层](Part-8.md) +- [Part 9: 作者: 用户页面](Part-9.md) +- [Part 10: 图书到作者的关系](Part-10.md) -更多信息,参阅[入门教程](../../Getting-Started?UI={{UI}})的运行应用程序部分. +## 下载源码 -![Set as startup project](./images/bookstore-start-project-{{UI_Text}}.png) +本教程根据你的**UI** 和 **Database**偏好有多个版,我们准备了两种可供下载的源码组合: -{{if UI == "NG"}} - -在 `angular` 下打开命令行终端,执行 `yarn` 命令: - -```bash -yarn -``` +* [MVC (Razor Pages) UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) +* [Angular UI 与 MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) -所有的模块加载后,执行 `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}} +在开始开发之前,请按照[入门教程](../Getting-Started.md)创建名为 `Acme.BookStore` 的新解决方案. -默认的登录凭证: +## 创建Book实体 -* **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`的类,如下所示: +在解决方案的**领域层**(`Acme.BookStore.Domain`项目)中定义你的实体. -````C# +该应用程序的主要实体是`Book`. 在`Acme.BookStore.Domain`项目中创建一个 `Books` 文件夹并在其中添加一个名为 `Book` 的类,如下所示: + +````csharp using System; using Volo.Abp.Domain.Entities.Auditing; -namespace Acme.BookStore +namespace Acme.BookStore.Books { public class Book : AuditedAggregateRoot { @@ -133,33 +80,22 @@ namespace Acme.BookStore 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` 等). +* ABP为实体提供了两个基本的基类: `AggregateRoot`和`Entity`. **Aggregate Root**是**[领域驱动设计](./Domain-Driven-Design.md)** 概念之一. 可以视为直接查询和处理的根实体(请参阅[实体文档](./Entities.md)). +* `Book`实体继承了`AuditedAggregateRoot`,`AuditedAggregateRoot`类在`AggregateRoot`类的基础上添加了一些审计属性(`CreationTime`, `CreatorId`, `LastModificationTime` 等). ABP框架自动为你管理这些属性. * `Guid`是`Book`实体的主键类型. -* 使用 **数据注解** 为EF Core添加映射.或者你也可以使用 EF Core 自带的[fluent mapping API](https://docs.microsoft.com/en-us/ef/core/modeling). -#### BookType枚举 +> 为了保持简单,本教程将实体属性保留为 **public get/set** . 如果你想了解关于DDD最佳实践,请参阅[实体文档](../Entities.md). -上面所用到的`BookType`枚举定义如下: +### BookType枚举 -````C# -namespace Acme.BookStore +上面所用到了 `BookType` 枚举,在 `Acme.BookStore.Domain.Shared` 项目创建 `BookType`. + +````csharp +namespace Acme.BookStore.Books { public enum BookType { @@ -176,69 +112,112 @@ namespace Acme.BookStore } ```` -#### 将Book实体添加到DbContext中 +最终的文件夹/文件结构应该如下所示: + +![bookstore-book-and-booktype](images/bookstore-book-and-booktype.png) + +### 将Book实体添加到DbContext中 -{{if DB == "ef"}} +{{if DB == "EF"}} EF Core需要你将实体和 `DbContext` 建立关联.最简单的做法是在`Acme.BookStore.EntityFrameworkCore`项目的`BookStoreDbContext`类中添加`DbSet`属性.如下所示: -````C# +````csharp public class BookStoreDbContext : AbpDbContext { - public DbSet Book { get; set; } - ... + public DbSet Books { get; set; } + //... } ```` {{end}} -{{if DB == "mongodb"}} +{{if DB == "Mongo"}} 添加 `IMongoCollection Book` 属性到 `Acme.BookStore.MongoDB` 项目的 `BookStoreMongoDbContext` 中. ```csharp public class BookStoreMongoDbContext : AbpMongoDbContext { - public IMongoCollection Users => Collection(); - public IMongoCollection Book => Collection();//<--added this line--> - //... + public IMongoCollection Books => Collection(); + //... } ``` {{end}} -{{if DB == "ef"}} +{{if DB == "EF"}} -#### 配置你的Book实体 +### 将Book实体映射到数据库表 -在 `Acme.BookStore.EntityFrameworkCore` 项目中打开 `BookStoreDbContextModelCreatingExtensions.cs` 文件,并将以下代码添加到 `ConfigureBookStore` 方法的末尾以配置Book实体: +在 `Acme.BookStore.EntityFrameworkCore` 项目中打开 `BookStoreDbContextModelCreatingExtensions.cs` 文件,添加 `Book` 实体的映射代码. 最终类应为: ````csharp -builder.Entity(b => +using Acme.BookStore.Books; +using Microsoft.EntityFrameworkCore; +using Volo.Abp; +using Volo.Abp.EntityFrameworkCore.Modeling; + +namespace Acme.BookStore.EntityFrameworkCore { - b.ToTable(BookStoreConsts.DbTablePrefix + "Book", BookStoreConsts.DbSchema); - b.ConfigureByConvention(); //auto configure for the base class props - b.Property(x => x.Name).IsRequired().HasMaxLength(128); -}); + public static class BookStoreDbContextModelCreatingExtensions + { + public static void ConfigureBookStore(this ModelBuilder builder) + { + Check.NotNull(builder, nameof(builder)); + + /* Configure your own tables/entities inside here */ + + 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` 扩展方法. +* `BookStoreConsts` 含有用于表的架构和表前缀的常量值. 你不必使用它,但建议在单点控制表前缀. +* `ConfigureByConvention()` 方法优雅的配置/映射继承的属性,应始终对你所有的实体使用它. -{{end}} +### 添加数据迁移 -{{if DB == "mongodb"}} +启动模板使用[EF Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/)创建和维护数据库架构. 打开菜单*工具 > NuGet包管理器*下的**程序包管理控制台 (PMC)**. + +![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) + +它会在 `Acme.BookStore.EntityFrameworkCore.DbMigrations` 项目中的 `Migrations` 文件内创建一个新的迁移类. + +在更新数据库之前,请阅读下面的部分了解如何将一些初始数据插入到数据库. + +> 如果你使用其他IDE而不是Visual Studio, 你可以使用 [`dotnet-ef]`(https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli#create-a-migration) 工具. + +{{end}} #### 添加种子数据 -添加种子数据是可选的,但第一次运行时最好将初始数据添加到数据库中. ABP提供了[数据种子系统](https://docs.abp.io/en/abp/latest/Data-Seeding). 在 `*.Domain` 项目下创建派生 `IDataSeedContributor` 的类: +> >在运行应用程序之前最好将初始数据添加到数据库中. 本节介绍ABP框架的[数据种子系统](../Data-Seeding.md). 如果你不想创建种子数据可以跳过本节,但是建议你遵循它来学习这个有用的ABP Framework功能。 + +在 `*.Domain` 项目下创建派生 `IDataSeedContributor` 的类,并且拷贝以下代码: ```csharp using System; using System.Threading.Tasks; +using Acme.BookStore.Books; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Repositories; -using Volo.Abp.Guids; namespace Acme.BookStore { @@ -246,102 +225,75 @@ namespace Acme.BookStore : IDataSeedContributor, ITransientDependency { private readonly IRepository _bookRepository; - private readonly IGuidGenerator _guidGenerator; - public BookStoreDataSeederContributor( - IRepository bookRepository, - IGuidGenerator guidGenerator) + public BookStoreDataSeederContributor(IRepository bookRepository) { _bookRepository = bookRepository; - _guidGenerator = guidGenerator; } public async Task SeedAsync(DataSeedContext context) { - if (await _bookRepository.GetCountAsync() > 0) + if (await _bookRepository.GetCountAsync() <= 0) { - return; + await _bookRepository.InsertAsync( + new Book + { + Name = "1984", + Type = BookType.Dystopia, + PublishDate = new DateTime(1949, 6, 8), + Price = 19.84f + }, + autoSave: true + ); + + await _bookRepository.InsertAsync( + new Book + { + Name = "The Hitchhiker's Guide to the Galaxy", + Type = BookType.ScienceFiction, + PublishDate = new DateTime(1995, 9, 27), + Price = 42.0f + }, + autoSave: true + ); } - - 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}} +* 如果数据库中当前没有图书,则此代码使用 `IRepository`(默认为[repository](../Repositories.md)将两本书插入数据库. -{{if DB == "ef"}} +### 更新数据库 -#### 添加新的Migration并更新数据库 +运行 `Acme.BookStore.DbMigrator` 应用程序来更新数据库: -这个启动模板使用了[EF Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/)来创建并维护数据库结构.打开 **程序包管理器控制台(Package Manager Console) (PMC)** (工具/Nuget包管理器菜单) +![bookstore-dbmigrator-on-solution](images/bookstore-dbmigrator-on-solution.png) -![Open Package Manager Console](./images/bookstore-open-package-manager-console.png) +{{if DB == "EF"}} -选择 `Acme.BookStore.EntityFrameworkCore.DbMigrations`作为默认的项目然后执行下面的命令: +`.DbMigrator` 是一个控制台使用程序,可以在**开发**和**生产**环境**迁移数据库架构**和**初始化种子数据**. -```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`命令在数据库中创建了`AppBook`表. 打开数据库并输入几个示例行,以便在页面上显示它们: - -```mssql -INSERT INTO AppBook (Id,CreationTime,[Name],[Type],PublishDate,Price) VALUES -('f3c04764-6bfd-49e2-859e-3f9bfda6183e', '2018-07-01', '1984',3,'1949-06-08','19.84') - -INSERT INTO AppBook (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') +{{end}} -INSERT INTO AppBook (Id,CreationTime,[Name],[Type],PublishDate,Price) VALUES -('4fa024a1-95ac-49c6-a709-6af9e4d54b54', '2018-07-02', 'Pet Sematary',5,'1983-11-14','23.7') -``` +{{if DB == "Mongo"}} -![bookstore-book-table](./images/bookstore-book-table.png) +尽管MongoDB**不需要**数据库架构迁移,但仍建议运行该应用程序,因为它可以在数据库上播种初始数据. 可以在**开发**和**生产**环境中使用. {{end}} -### 创建应用服务 +## 创建应用程序 -下一步是创建[应用服务](../Application-Services.md)来管理(创建,列出,更新,删除)书籍. 启动模板中的应用程序层分为两个项目: +应用程序层由两个分离的项目组成: -* `Acme.BookStore.Application.Contracts`主要包含你的DTO和应用程序服务接口. -* `Acme.BookStore.Application`包含应用程序服务的实现. +* `Acme.BookStore.Application.Contracts 包含你的[DTO](../Data-Transfer-Objects.md)和[应用服务](../Application-Services.md)接口. +* `Acme.BookStore.Application` 包含你的应用服务实现. -#### BookDto +在本部分中,你将创建一个应用程序服务,使用ABP Framework的 `CrudAppService` 基类来获取,创建,更新和删除书籍. -在`Acme.BookStore.Application.Contracts`项目中创建一个名为`BookDto`的DTO类: +### BookDto + +`CrudAppService` 基类需要定义实体的基本DTO. 在 `Acme.BookStore.Application.Contracts` 项目中创建一个名为 `BookDto` 的DTO类: ````C# using System; @@ -364,11 +316,12 @@ namespace Acme.BookStore * **DTO**类被用来在 **表示层** 和 **应用层** **传递数据**.查看[DTO文档](https://docs.abp.io/zh-Hans/abp/latest/Data-Transfer-Objects)查看更多信息. * 为了在页面上展示书籍信息,`BookDto`被用来将书籍数据传递到表示层. -* `BookDto`继承自 `AuditedEntityDto`.跟上面定义的`Book`类一样具有一些审计属性. +* `BookDto`继承自 `AuditedEntityDto`.跟上面定义的 `Book` 实体一样具有一些审计属性. 在将书籍返回到表示层时,需要将`Book`实体转换为`BookDto`对象. [AutoMapper](https://automapper.org)库可以在定义了正确的映射时自动执行此转换. 启动模板配置了AutoMapper,因此你只需在`Acme.BookStore.Application`项目的`BookStoreApplicationAutoMapperProfile`类中定义映射: ````csharp +using Acme.BookStore.Books; using AutoMapper; namespace Acme.BookStore @@ -383,16 +336,17 @@ namespace Acme.BookStore } ```` -#### CreateUpdateBookDto +> 参阅 [对象对对象映射](../Object-To-Object-Mapping.md) 文档了解详情. + +### CreateUpdateBookDto -在`Acme.BookStore.Application.Contracts`项目中创建一个名为`CreateUpdateBookDto`的DTO类: +在`Acme.BookStore.Application.Contracts`项目中创建一个名为 `CreateUpdateBookDto` 的DTO类: -````c# +````csharp using System; using System.ComponentModel.DataAnnotations; -using Volo.Abp.AutoMapper; -namespace Acme.BookStore +namespace Acme.BookStore.Books { public class CreateUpdateBookDto { @@ -404,7 +358,8 @@ namespace Acme.BookStore public BookType Type { get; set; } = BookType.Undefined; [Required] - public DateTime PublishDate { get; set; } + [DataType(DataType.Date)] + public DateTime PublishDate { get; set; } = DateTime.Now; [Required] public float Price { get; set; } @@ -415,9 +370,11 @@ namespace Acme.BookStore * 这个DTO类被用于在创建或更新书籍的时候从用户界面获取图书信息. * 它定义了数据注释属性(如`[Required]`)来定义属性的验证. DTO由ABP框架[自动验证](https://docs.abp.io/zh-Hans/abp/latest/Validation). -就像上面的`BookDto`一样,创建一个从`CreateUpdateBookDto`对象到`Book`实体的映射: +就像上面的`BookDto`一样,创建一个从`CreateUpdateBookDto`对象到`Book`实体的映射,最终映射配置类如下: + ````csharp +using Acme.BookStore.Books; using AutoMapper; namespace Acme.BookStore @@ -427,30 +384,29 @@ namespace Acme.BookStore public BookStoreApplicationAutoMapperProfile() { CreateMap(); - CreateMap(); //<--added this line--> + CreateMap(); } } } ```` -#### IBookAppService +### IBookAppService -在`Acme.BookStore.Application.Contracts`项目中定义一个名为`IBookAppService`的接口: +下一步是为应用程序定义接口,在`Acme.BookStore.Application.Contracts`项目中定义一个名为`IBookAppService`的接口: -````C# +````csharp using System; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; -namespace Acme.BookStore +namespace Acme.BookStore.Books { public interface IBookAppService : - ICrudAppService< //定义了CRUD方法 - BookDto, //用来展示书籍 - Guid, //Book实体的主键 - PagedAndSortedResultRequestDto, //获取书籍的时候用于分页和排序 - CreateUpdateBookDto, //用于创建书籍 - CreateUpdateBookDto> //用于更新书籍 + ICrudAppService< //Defines CRUD methods + BookDto, //Used to show books + Guid, //Primary key of the book entity + PagedAndSortedResultRequestDto, //Used for paging/sorting + CreateUpdateBookDto> //Used to create/update a book { } @@ -458,25 +414,29 @@ namespace Acme.BookStore ```` * 框架定义应用程序服务的接口**不是必需的**. 但是,它被建议作为最佳实践. -* `ICrudAppService`定义了常见的**CRUD**方法:`GetAsync`,`GetListAsync`,`CreateAsync`,`UpdateAsync`和`DeleteAsync`. 你可以从空的`IApplicationService`接口继承并手动定义自己的方法. -* `ICrudAppService`有一些变体, 你可以在每个方法中使用单独的DTO,也可以分别单独指定. +* `ICrudAppService`定义了常见的**CRUD**方法:`GetAsync`,`GetListAsync`,`CreateAsync`,`UpdateAsync`和`DeleteAsync`. 你可以从空的`IApplicationService`接口继承并手动定义自己的方法(将在下一部分中完成). +* `ICrudAppService`有一些变体, 你可以在每个方法中使用单独的DTO,也可以分别单独指定(例如使用不同的DTO进行创建和更新). -#### BookAppService +### BookAppService -在`Acme.BookStore.Application`项目中实现名为`BookAppService`的`IBookAppService`: +在`Acme.BookStore.Application`项目中创建名为 `BookAppService` 的 `IBookAppService` 实现: -````C# +````csharp using System; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; using Volo.Abp.Domain.Repositories; -namespace Acme.BookStore +namespace Acme.BookStore.Books { public class BookAppService : - CrudAppService, - IBookAppService + CrudAppService< + Book, //The Book entity + BookDto, //Used to show books + Guid, //Primary key of the book entity + PagedAndSortedResultRequestDto, //Used for paging/sorting + CreateUpdateBookDto>, //Used to create/update a book + IBookAppService //implement the IBookAppService { public BookAppService(IRepository repository) : base(repository) @@ -487,15 +447,17 @@ namespace Acme.BookStore } ```` -* `BookAppService`继承了`CrudAppService<...>`.它实现了上面定义的CRUD方法. +* `BookAppService`继承了`CrudAppService<...>`.它实现了 `ICrudAppService` 定义的CRUD方法. * `BookAppService`注入`IRepository `,这是`Book`实体的默认仓储. ABP自动为每个聚合根(或实体)创建默认仓储. 请参阅[仓储文档](https://docs.abp.io/zh-Hans/abp/latest/Repositories) -* `BookAppService`使用`IObjectMapper`将`Book`对象转换为`BookDto`对象, 将`CreateUpdateBookDto`对象转换为`Book`对象. 启动模板使用[AutoMapper](http://automapper.org/)库作为对象映射提供程序. 你之前定义了映射, 因此它将按预期工作. +* `BookAppService`使用[`IObjectMapper`](../Object-To-Object-Mapping.md)将`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控制器. +你通常创建**Controller**以将应用程序服务公开为**HTTP API**端点. 因此允许浏览器或第三方客户端通过AJAX调用它们. + +ABP可以[**自动**](../API/Auto-API-Controllers.md)按照惯例将你的应用程序服务配置为MVC API控制器. -#### Swagger UI +### Swagger UI 启动模板配置为使用[Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)运行[swagger UI](https://swagger.io/tools/swagger-ui/). 运行应用程序并在浏览器中输入`https://localhost:XXXX/swagger/`(用你自己的端口替换XXXX)作为URL. @@ -503,484 +465,42 @@ namespace Acme.BookStore ![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 -```` - -检查数据库中的`Book`表以查看新书. 你可以自己尝试`get`,`update`和`delete`功能. - -### 创建书籍页面 - -现在我们来创建一些可见和可用的东西,取代经典的MVC,我们使用微软推荐的[Razor Pages UI](https://docs.microsoft.com/en-us/aspnet/core/tutorials/razor-pages/razor-pages-start). +Swagger有一个很好的UI来测试API. -在 `Acme.BookStore.Web`项目的`Pages`文件夹下创建一个新的文件夹叫`Book`并添加一个名为`Index.cshtml`的Razor Page. +你可以尝试执行`[GET] /api/app/book` API来获取书籍列表, 服务端会返回以下JSON结果: -![bookstore-add-index-page](./images/bookstore-add-index-page-v2.png) - -打开`Index.cshtml`并把内容修改成下面这样: - -**Index.cshtml:** - -````html -@page -@using Acme.BookStore.Web.Pages.Book -@model IndexModel - -

    Book

    -```` - -* 确保`IndexModel`(Index.cshtml.cs)具有`Acme.BookStore.Web.Pages.Book`命名空间,或者在`Index.cshtml`中更新它. - -**Index.cshtml.cs:** - -```csharp -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Acme.BookStore.Web.Pages.Book +````json { - public class IndexModel : PageModel + "totalCount": 2, + "items": [ { - public void OnGet() - { - - } - } -} -``` - -#### 将Book页面添加到主菜单 - -打开`Menus`文件夹中的 `BookStoreMenuContributor` 类,在`ConfigureMainMenuAsync`方法的底部添加如下代码: - -````csharp -//... -namespace Acme.BookStore.Web.Menus -{ - public class BookStoreMenuContributor : IMenuContributor + "name": "The Hitchhiker's Guide to the Galaxy", + "type": 7, + "publishDate": "1995-09-27T00:00:00", + "price": 42, + "lastModificationTime": null, + "lastModifierId": null, + "creationTime": "2020-07-03T21:04:18.4607218", + "creatorId": null, + "id": "86100bb6-cbc1-25be-6643-39f62806969c" + }, { - private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) - { - //<-- added the below code - context.Menu.AddItem( - new ApplicationMenuItem("BookStore", l["Menu:BookStore"]) - .AddItem( - new ApplicationMenuItem("BookStore.Book", l["Menu:Book"], url: "/Book") - ) - ); - //--> - } + "name": "1984", + "type": 3, + "publishDate": "1949-06-08T00:00:00", + "price": 19.84, + "lastModificationTime": null, + "lastModifierId": null, + "creationTime": "2020-07-03T21:04:18.3174016", + "creatorId": null, + "id": "41055277-cce8-37d7-bb37-39f62806960b" } + ] } ```` -{{end}} - -#### 本地化菜单 - -本地化文本位于`Acme.BookStore.Domain.Shared`项目的`Localization/BookStore`文件夹下: - -![bookstore-localization-files](./images/bookstore-localization-files-v2.png) - -打开`en.json`文件,将`Menu:BookStore`和`Menu:Book`键的本地化文本添加到文件末尾: - -````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:Book": "Book", - "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下Book子菜单项就会跳转到新增的书籍页面. - -#### 书籍列表 - -我们将使用[Datatables.net](https://datatables.net/)JQuery插件来显示页面上的表格列表. [Datatables](https://datatables.net/)可以完全通过AJAX工作,速度快,并提供良好的用户体验. Datatables插件在启动模板中配置,因此你可以直接在任何页面中使用它,而需要在页面中引用样式和脚本文件. - -##### Index.cshtml - -将`Pages/Book/Index.cshtml`改成下面的样子: - -````html -@page -@model Acme.BookStore.Web.Pages.Book.IndexModel -@section scripts -{ - -} - - -

    @L["Book"]

    -
    - - - - - @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/Book/`文件夹中创建 `index.js`文件 - -![bookstore-index-js-file](./images/bookstore-index-js-file-v2.png) - -`index.js`的内容如下: - -````js -$(function () { - var dataTable = $('#BookTable').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 开发 - -#### 创建book页面 - -是时候创建可见和可用的东西了!开发ABP Angular前端应用程序时,需要使用一些工具: - -- [Angular CLI](https://angular.io/cli) 用于创建模块,组件和服务. -- [Ng Bootstrap](https://ng-bootstrap.github.io/#/home) 用做UI组件库. -- [ngx-datatable](https://swimlane.gitbook.io/ngx-datatable/) 用做 datatable 类库. -- [Visual Studio Code](https://code.visualstudio.com/) 用做代码编辑器 (你可以选择自己喜欢的编辑器). - -#### 安装 NPM 包 - -在 `angular` 目录下打开命令行窗口,选择 `yarn` 命令安装NPM包: - -```bash -yarn -``` - -#### BookModule - -运行以下命令创建一个名为 `BookModule` 的新模块: - -```bash -yarn ng generate module book --routing true -``` - -![Generating book module](./images/bookstore-creating-book-module-terminal.png) - -#### 路由 - -打开位于 `src\app` 目录下的 `app-routing.module.ts` 文件. 添加新的路由: - -```js -const routes: Routes = [ -// ... -// added a new route to the routes array - { - path: 'books', - loadChildren: () => import('./book/book.module').then(m => m.BookModule) - } -] -``` - -* 我们添加了一个懒加载路由. 参阅 [嬾加載功能模块](https://angular.io/guide/lazy-loading-ngmodules#lazy-loading-feature-modules). - -打开位于 `src\app` 目录下的 `route.provider.ts` 文件,用以下内容替换它: - -```js -import { RoutesService, eLayoutType } from '@abp/ng.core'; -import { APP_INITIALIZER } from '@angular/core'; - -export const APP_ROUTE_PROVIDER = [ - { provide: APP_INITIALIZER, useFactory: configureRoutes, deps: [RoutesService], multi: true }, -]; - -function configureRoutes(routes: RoutesService) { - return () => { - routes.add([ - //... - // added below element - { - path: '/books', - name: '::Menu:Books', - iconClass: 'fas fa-book', - order: 101, - layout: eLayoutType.application, - }, - ]); - }; -} -``` - -* 我们添加了一个新的路由元素在菜单上显示为 "Books" 的导航元素. - * `path` 路由的URL. - * `name` 菜单项的名称,可以使用本地化Key. - * `iconClass` 菜单项的图标. - * `order` 菜单项的排序.我们定义了101,它显示在 "Administration" 项的后面. - * `layout` BooksModule路由的布局. 可以定义 `eLayoutType.application`, `eLayoutType.account` 或 `eLayoutType.empty`. - -更多信息请参阅[RoutesService 文档](https://docs.abp.io/en/abp/latest/UI/Angular/Modifying-the-Menu.md#via-routesservice). - -#### Book 列表组件 - -在命令行运行以下命令,生成名为 book-list 的新组件: - -```bash -yarn ng generate component book/book-list -``` - -![Creating book list](./images/bookstore-creating-book-list-terminal.png) - -打开 `app\book` 目录下的 `book.module.ts` 文件,使用以下内容替换它: - -```js -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { BookRoutingModule } from './book-routing.module'; -import { BookListComponent } from './book-list/book-list.component'; -import { SharedModule } from '../shared/shared.module'; //<== added this line ==> - -@NgModule({ - declarations: [BookListComponent], - imports: [ - CommonModule, - BookRoutingModule, - SharedModule, //<== added this line ==> - ], -}) -export class BookModule {} -``` - -* 我们导入了 `SharedModule` 并添加到 `imports` 数组. - -打开 `app\book` 目录下的 `book-routing.module.ts` 文件用以下内容替换它: - -```js -import { NgModule } from '@angular/core'; -import { Routes, RouterModule } from '@angular/router'; -import { BookListComponent } from './book-list/book-list.component'; // <== added this line ==> - -// <== replaced routes ==> -const routes: Routes = [ - { - path: '', - component: BookListComponent, - }, -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class BookRoutingModule { } -``` - -* 我们导入了 `BookListComponent` 并替换 `routes` 常量. - -运行 `yarn start`,等待Angular启动服务: - -```bash -yarn start -``` - -我们将看到book页面的 **book-list works!**: - -![Initial book list page](./images/bookstore-initial-book-list-page.png) - -#### 生成代理 - -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) - -#### BookListComponent - -打开 `app\book\book-list` 目录下的 `book-list.component.ts` 用以下内容替换它: - -```js -import { ListService, PagedResultDto } from '@abp/ng.core'; -import { Component, OnInit } from '@angular/core'; -import { BookDto, BookType } from '../models'; -import { BookService } from '../services'; - -@Component({ - selector: 'app-book-list', - templateUrl: './book-list.component.html', - styleUrls: ['./book-list.component.scss'], - providers: [ListService], -}) -export class BookListComponent implements OnInit { - book = { items: [], totalCount: 0 } as PagedResultDto; - - booksType = BookType; - - constructor(public readonly list: ListService, private bookService: BookService) {} - - ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getListByInput(query); - - this.list.hookToQuery(bookStreamCreator).subscribe((response) => { - this.book = response; - }); - } -} -``` - -* 我们注入了生成的 `BookService`. -* 我们实现了 [ListService](https://docs.abp.io/en/abp/latest/UI/Angular/List-Service),它是一个公用服务,提供了简单的分页,排序和搜索. - -打开 `app\book\book-list` 目录下的 `book-list.component.html` 用以下内容替换它: - -```html -
    -
    -
    -
    -
    - {%{{{ '::Menu:Books' | abpLocalization }}}%} -
    -
    -
    -
    -
    -
    - - - - - {%{{{ booksType[row.type] }}}%} - - - - - {%{{{ row.publishDate | date }}}%} - - - - - {%{{{ row.price | currency }}}%} - - - -
    -
    -``` - -* 我们添加了图书列表页面的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}} +这很酷,因为我们没有编写任何代码来创建API控制器,但是现在我们有了一个可以正常使用的REST API! -### 下一章 +## 下一章 -参阅[第二章](part-2.md)了解创建,更新和删除图书. +参阅教程的[下一章](part-2.md). diff --git a/docs/zh-Hans/Tutorials/Part-10.md b/docs/zh-Hans/Tutorials/Part-10.md new file mode 100644 index 0000000000..602306c484 --- /dev/null +++ b/docs/zh-Hans/Tutorials/Part-10.md @@ -0,0 +1 @@ +TODO.. \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/Part-2.md b/docs/zh-Hans/Tutorials/Part-2.md index 751bd85ece..decddfebff 100644 --- a/docs/zh-Hans/Tutorials/Part-2.md +++ b/docs/zh-Hans/Tutorials/Part-2.md @@ -1,8 +1,9 @@ -## ASP.NET Core {{UI_Value}} 教程 - 第二章 +# Web应用程序开发教程 - 第二章: 图书列表页面 ````json //[doc-params] { - "UI": ["MVC","NG"] + "UI": ["MVC","NG"], + "DB": ["EF","Mongo"] } ```` @@ -21,904 +22,476 @@ else end }} -### 关于本教程 +## 关于本教程 -这是ASP.NET Core{{UI_Value}}系列教程的第二章. 共有三章: +在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的: -- [Part-1: 创建项目和书籍列表页面](Part-1.md) -- **Part 2: 创建,编辑,删除书籍(本章)** -- [Part-3: 集成测试](Part-3.md) +* **{{DB_Text}}** 做为ORM提供程序. +* **{{UI_Value}}** 做为UI框架. -> 你也可以观看由ABP社区成员为本教程录制的[视频课程](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application). +本教程分为以下部分: -{{if UI == "MVC"}} - -### 新增 Book 实体 +- [Part 1: 创建服务端](Part-1.md) +- **Part 2: 图书列表页面**(本章) +- [Part 3: 创建,更新和删除图书](Part-3.md) +- [Part 4: 集成测试](Part-4.md) +- [Part 5: 授权](Part-5.md) +- [Part 6: 作者: 领域层](Part-6.md) +- [Part 7: 作者: 数据库集成](Part-7.md) +- [Part 8: 作者: 应用服务层](Part-8.md) +- [Part 9: 作者: 用户页面](Part-9.md) +- [Part 10: 图书到作者的关系](Part-10.md) -通过本节, 你将会了解如何创建一个 modal form 来实现新增书籍的功能. 最终成果如下图所示: +## 下载源码 -![bookstore-create-dialog](./images/bookstore-create-dialog-2.png) +本教程根据你的**UI** 和 **Database**偏好有多个版,我们准备了两种可供下载的源码组合: -#### 新建 modal form +* [MVC (Razor Pages) UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) +* [Angular UI 与 MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) -在 `Acme.BookStore.Web` 项目的 `Pages/Books` 目录下新建一个 `CreateModal.cshtml` Razor页面: +{{if UI == "MVC"}} -![bookstore-add-create-dialog](./images/bookstore-add-create-dialog-v2.png) +## 动态JavaScript代理 -##### CreateModal.cshtml.cs +通常在 **JavaScript** 端通过AJAX调用HTTP API端点. 你可以使用 `$.ajax` 或其他工具来调用端点. 但是ABP提供了更好的方法. -打开 `CreateModal.cshtml.cs` 代码文件,用如下代码替换 `CreateModalModel` 类的实现: +ABP**动态**为所有API端点创建 **[JavaScript代理](../UI/AspNetCore/Dynamic-JavaScript-Proxies.md)**. 所以你可以像调用**Javascript本地方法**一样使用任何**端点**. -````C# -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; +### 在开发者控制台中进行测试 -namespace Acme.BookStore.Web.Pages.Books -{ - public class CreateModalModel : BookStorePageModel - { - [BindProperty] - public CreateUpdateBookDto Book { get; set; } +你可以在自己喜欢的浏览器的**开发者控制台**轻松的测试JavaScript代理. 运行应用程序,打开浏览器的**开发者人员工具**(*快捷键通常是F12*),切换到**控制台**选项卡,输入以下代码然后按回车: - private readonly IBookAppService _bookAppService; - - public CreateModalModel(IBookAppService bookAppService) - { - _bookAppService = bookAppService; - } - - public async Task OnPostAsync() - { - await _bookAppService.CreateAsync(Book); - return NoContent(); - } - } -} +````js +acme.bookStore.books.book.getList({}).done(function (result) { console.log(result); }); ```` -* 该类派生于 `BookStorePageModel` 而非默认的 `PageModel`. `BookStorePageModel` 继承了 `PageModel` 并且添加了一些可以被你的page model类使用的通用属性和方法. -* `Book` 属性上的 `[BindProperty]` 特性将post请求提交上来的数据绑定到该属性上. -* 该类通过构造函数注入了 `IBookAppService` 应用服务,并且在 `OnPostAsync` 处理程序中调用了服务的 `CreateAsync` 方法. +* `acme.bookStore.books` 是 `BookAppService` 的命令空间转换成[小驼峰](https://en.wikipedia.org/wiki/Camel_case)形式. +* `book` 是 `BookAppService` 的约定名称(删除`AppService`后缀并且转换为小驼峰). +* `getList` 是 `CrudAppService` 基类定义的 `GetListAsync` 方法的约定名称(删除`Async`后缀并且转换为小驼峰). +* `{}` 参数将空对象发送到 `GetListAsync` 方法,该方法通常需要一个类型为 `PagedAndSortedResultRequestDto` 的对象,该对象用于将分页和排序选项发送到服务器(所有属性都是可选的,具有默认值. 因此你可以发送一个空对象). +* `getList` 函数返回一个 `promise`. 你可以传递一个回调到 `then`(或`done`)函数来获取从服务器返回的结果. -##### CreateModal.cshtml +运行该代码会产生以下输出: -打开 `CreateModal.cshtml` 文件并粘贴如下代码: +![bookstore-javascript-proxy-console](images/bookstore-javascript-proxy-console.png) -````html -@page -@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal -@model Acme.BookStore.Web.Pages.Books.CreateModalModel -@{ - Layout = null; -} - - - - - - - - - -```` +你可以看到服务端返回的 **图书列表**. 你也可以在开发者人员工具的 **网络** 选项卡查看客户端到服务端的通信: -* 这个 modal 使用 `abp-dynamic-form` Tag Helper 根据 `CreateBookViewModel` 类自动构建了表单. - * `abp-model` 指定了 `Book` 属性为模型对象. - * `data-ajaxForm` 设置了表单通过AJAX提交,而不是经典的页面回发. - * `abp-form-content` tag helper 作为表单控件渲染位置的占位符 (这是可选的,只有你在 `abp-dynamic-form` 中像本示例这样添加了其他内容才需要). +![bookstore-getlist-result-network](images/bookstore-getlist-result-network.png) -#### 添加 "New book" 按钮 +Let's **create a new book** using the `create` function: -打开 `Pages/Books/Index.cshtml` 并按如下代码修改 `abp-card-header` : +让我们使用 `create` 函数**创建一本书**: -````html - - - -

    @L["Books"]

    -
    - - - -
    -
    +````js +acme.bookStore.books.book.create({ + name: 'Foundation', + type: 7, + publishDate: '1951-05-24', + price: 21.5 + }).then(function (result) { + console.log('successfully created the book with id: ' + result.id); + }); ```` -如下图所示,只是在表格 **右上方** 添加了 **New book** 按钮: +您应该在控制台中看到类似以下的消息: -![bookstore-new-book-button](./images/bookstore-new-book-button.png) +````text +successfully created the book with id: 439b0ea8-923e-8e1e-5d97-39f2c7ac4246 +```` -打开 `Pages/book/index.js` 在 `datatable` 配置代码后面添加如下代码: +检查数据库中的 `Books` 表你会看到新的一行. 你可以自己尝试使用 `get`, `update` 和 `delete` 函数. -````js -var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); +我们将利用这些动态代理功能在接下来的章节来与服务器通信. -createModal.onResult(function () { - dataTable.ajax.reload(); -}); +{{end}} -$('#NewBookButton').click(function (e) { - e.preventDefault(); - createModal.open(); -}); -```` +## 本地化 -* `abp.ModalManager` 是一个在客户端打开和管理modal的辅助类.它基于Twitter Bootstrap的标准modal组件通过简化的API抽象隐藏了许多细节. +开始的UI开发之前,我们首先要准备本地化的文本(这是你通常在开发应用程序时需要做的). -现在,你可以 **运行程序** 通过新的 modal form 来创建书籍了. +本地化文本位于 `Acme.BookStore.Domain.Shared` 项目的 `Localization/BookStore` 文件夹下: -### 编辑更新已存在的 Book 实体 +![bookstore-localization-files](./images/bookstore-localization-files-v2.png) -在 `Acme.BookStore.Web` 项目的 `Pages/Books` 目录下新建一个名叫 `EditModal.cshtml` 的Razor页面: +打开 `en.json` (*英文翻译*)文件并更改内容,如下所示: -![bookstore-add-edit-dialog](./images/bookstore-add-edit-dialog.png) +````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", + "Close": "Close", + "Delete": "Delete", + "Edit": "Edit", + "PublishDate": "Publish date", + "NewBook": "New book", + "Name": "Name", + "Type": "Type", + "Price": "Price", + "CreationTime": "Creation time", + "AreYouSure": "Are you sure?", + "AreYouSureToDelete": "Are you sure you want to delete this item?", + "Enum:BookType:0": "Undefined", + "Enum:BookType:1": "Adventure", + "Enum:BookType:2": "Biography", + "Enum:BookType:3": "Dystopia", + "Enum:BookType:4": "Fantastic", + "Enum:BookType:5": "Horror", + "Enum:BookType:6": "Science", + "Enum:BookType:7": "Science fiction", + "Enum:BookType:8": "Poetry" + } +} +```` -#### EditModal.cshtml.cs +* 本地化关键字名称是任意的. 你可以设置任何名称. 对于特定的文本类型,我们更喜欢遵循一些约定: + * 为按钮项添加 `Menu:` 前缀. + * 使用 `Enum::` 命名约定来本地化枚举成员. 当您这样做时ABP可以在某些适当的情况下自动将枚举本地化. -展开 `EditModal.cshtml`,打开 `EditModal.cshtml.cs` 文件( `EditModalModel` 类) 并替换成以下代码: +如果未在本地化文件中定义文本,则文本将**回退**到本地化键(作为ASP.NET Core的标准行为). -````csharp -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; +> ABP本地化系统建立在[ASP.NET Core标准本地化](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization)系统之上,并以多种方式进行了扩展. 有关详细信息请参见[本地化文档](../Localization.md). -namespace Acme.BookStore.Web.Pages.Books -{ - public class EditModalModel : BookStorePageModel - { - [HiddenInput] - [BindProperty(SupportsGet = true)] - public Guid Id { get; set; } +{{if UI == "MVC"}} - [BindProperty] - public CreateUpdateBookDto Book { get; set; } +## 创建图书页面 - private readonly IBookAppService _bookAppService; +是时候创建可见的和可用的东西了! 代替经典的MVC,我们将使用微软推荐的[Razor Pages UI](https://docs.microsoft.com/zh-cn/aspnet/core/tutorials/razor-pages/razor-pages-start). - public EditModalModel(IBookAppService bookAppService) - { - _bookAppService = bookAppService; - } +在 `Acme.BookStore.Web` 项目的 `Pages` 文件夹下创建一个名为新的 `Books` 的文件夹. 然后在文件夹右键选择 **添加 > Razor Page** 菜单. 输入名称 `Index`: - public async Task OnGetAsync() - { - var bookDto = await _bookAppService.GetAsync(Id); - Book = ObjectMapper.Map(bookDto); - } +![bookstore-add-index-page](./images/bookstore-add-index-page-v2.png) - public async Task OnPostAsync() - { - await _bookAppService.UpdateAsync(Id, Book); - return NoContent(); - } - } -} -```` +打开 `Index.cshtml` 并把内容修改成下面这样: -* `[HiddenInput]` 和 `[BindProperty]` 是标准的 ASP.NET Core MVC 特性.这里启用 `SupportsGet` 从Http请求的查询字符串中获取Id的值. -* 在 `OnGetAsync` 方法中,将 `BookAppService.GetAsync` 方法返回的 `BookDto` 映射成 `CreateUpdateBookDto` 并赋值给Book属性. -* `OnPostAsync` 方法直接使用 `BookAppService.UpdateAsync` 来更新实体. +````html +@page +@using Acme.BookStore.Web.Pages.Books +@model IndexModel -#### BookDto到CreateUpdateBookDto对象映射 +

    Books

    +```` -为了执行`BookDto`到`CreateUpdateBookDto`对象映射,请打开`Acme.BookStore.Web`项目中的`BookStoreWebAutoMapperProfile.cs`并更改它,如下所示: +`Index.cshtml.cs` 内容应该是: -````csharp -using AutoMapper; +```csharp +using Microsoft.AspNetCore.Mvc.RazorPages; -namespace Acme.BookStore.Web +namespace Acme.BookStore.Web.Pages.Books { - public class BookStoreWebAutoMapperProfile : Profile + public class IndexModel : PageModel { - public BookStoreWebAutoMapperProfile() + public void OnGet() { - CreateMap(); + } } } -```` - -* 刚刚添加了`CreateMap();`作为映射定义. +``` -#### EditModal.cshtml +### 将Book页面添加到主菜单 -将 `EditModal.cshtml` 页面内容替换成如下代码: +打开 `Menus` 文件夹中的 `BookStoreMenuContributor` 类,在 `ConfigureMainMenuAsync` 方法的底部添加如下代码: -````html -@page -@using Acme.BookStore.Web.Pages.Books -@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal -@model EditModalModel -@{ - Layout = null; -} - - - - - - - - - - +````csharp +context.Menu.AddItem( + new ApplicationMenuItem( + "BooksStore", + l["Menu:BookStore"], + icon: "fa fa-book" + ).AddItem( + new ApplicationMenuItem( + "BooksStore.Books", + l["Menu:Books"], + url: "/Books" + ) + ) +); ```` -这个页面内容和 `CreateModal.cshtml` 非常相似,除了以下几点: +运行项目,使用用户名 `admin` 和密码 `1q2w3E*` 登录到应用程序. 看到新菜单项已添加到顶部栏: -* 它包含`id`属性的`abp-input`, 用于存储编辑书的 `id` (它是隐藏的Input) -* 此页面指定的post地址是`Books/EditModal`, 并用文本 *Update* 作为 modal 标题. +![bookstore-menu-items](./images/bookstore-new-menu-item.png) -#### 为表格添加 "操作(Actions)" 下拉菜单 - -我们将为表格每行添加下拉按钮 ("Actions") . 最终效果如下: - -![bookstore-book-table-actions](images/bookstore-book-table-actions.png) - -打开 `Pages/Books/Index.cshtml` 页面,并按下方所示修改表格部分的代码: - -````html - - - - @L["Actions"] - @L["Name"] - @L["Type"] - @L["PublishDate"] - @L["Price"] - @L["CreationTime"] - - - -```` +点击BookStore下的Books子菜单项就会跳转到空的图书页面. -* 只是为"Actions"增加了一个 `th` 标签. +### 图书列表 -打开 `Pages/book/index.js` 并用以下内容进行替换: +We will use the [Datatables.net](https://datatables.net/) jQuery library to show the book list. Datatables library completely work via AJAX, it is fast, popular and provides a good user experience. -````js -$(function () { +我们将使用[Datatables.net](https://datatables.net/)JQuery插件来显示页面上的表格列表. [Datatables](https://datatables.net/)可以完全通过AJAX工作,速度快,并提供良好的用户体验. - var l = abp.localization.getResource('BookStore'); +> Datatables插件在启动模板中配置,因此你可以直接在任何页面中使用它,无需在页面中引用样式和脚本文件. - var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); - var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal'); - - var dataTable = $('#BooksTable').DataTable(abp.libs.datatables.normalizeConfiguration({ - processing: true, - serverSide: true, - paging: true, - searching: false, - autoWidth: false, - scrollCollapse: true, - order: [[1, "asc"]], - ajax: abp.libs.datatables.createAjax(acme.bookStore.book.getList), - columnDefs: [ - { - rowAction: { - items: - [ - { - text: l('Edit'), - action: function (data) { - editModal.open({ id: data.record.id }); - } - } - ] - } - }, - { data: "name" }, - { data: "type" }, - { data: "publishDate" }, - { data: "price" }, - { data: "creationTime" } - ] - })); - - createModal.onResult(function () { - dataTable.ajax.reload(); - }); +#### Index.cshtml - editModal.onResult(function () { - dataTable.ajax.reload(); - }); +将 `Pages/Book/Index.cshtml` 改成下面的样子: - $('#NewBookButton').click(function (e) { - e.preventDefault(); - createModal.open(); - }); -}); +````html +@page +@using Acme.BookStore.Localization +@using Acme.BookStore.Web.Pages.Books +@using Microsoft.Extensions.Localization +@model IndexModel +@inject IStringLocalizer L +@section scripts +{ + +} + + +

    @L["Books"]

    +
    + + + +
    ```` -* 通过 `abp.localization.getResource('BookStore')` 可以在客户端使用服务器端定义的相同的本地化语言文本. -* 添加了一个名为 `createModal` 的新的 `ModalManager` 来打开创建用的 modal 对话框. -* 添加了一个名为 `editModal` 的新的 `ModalManager` 来打开编辑用的 modal 对话框. -* 在 `columnDefs` 起始处新增一列用于显示 "Actions" 下拉按钮. -* "New Book"动作只需调用`createModal.open`来打开创建对话框. -* "Edit" 操作只是简单调用 `editModal.open` 来打开编辑对话框. +* `abp-script` [tag helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro)用于将外部的 **脚本** 添加到页面中.它比标准的`script`标签多了很多额外的功能.它可以处理 **最小化**和 **版本**.查看[捆绑 & 压缩文档](../UI/AspNetCore/Bundling-Minification.md)获取更多信息. +* `abp-card` 和 `abp-table` 是为Twitter Bootstrap的[card component](http://getbootstrap.com/docs/4.5/components/card/)封装的 **tag helpers**.ABP中有很多tag helpers,可以很方便的使用大多数[bootstrap](https://getbootstrap.com/)组件.你也可以使用原生的HTML标签代替tag helpers.使用tag helper可以通过智能提示和编译时类型检查减少HTML代码并防止错误.查看[tag helpers 文档](../UI/AspNetCore/Tag-Helpers/Index.md). -现在,你可以运行程序,通过编辑操作来更新任一个book实体. +#### Index.js -### 删除一个已有的Book实体 +在 `Pages/Books/` 文件夹中创建 `index.js`文件 -打开 `Pages/book/index.js` 文件,在 `rowAction` `items` 下新增一项: +![bookstore-index-js-file](./images/bookstore-index-js-file-v3.png) -````js -{ - text: l('Delete'), - confirmMessage: function (data) { - return l('BookDeletionConfirmationMessage', data.record.name); - }, - action: function (data) { - acme.bookStore.book - .delete(data.record.id) - .then(function() { - abp.notify.info(l('SuccessfullyDeleted')); - dataTable.ajax.reload(); - }); - } -} -```` - -* `confirmMessage` 用来在实际执行 `action` 之前向用户进行确认. -* 通过javascript代理方法 `acme.bookStore.book.delete` 执行一个AJAX请求来删除一个book实体. -* `abp.notify.info` 用来在执行删除操作后显示一个toastr通知信息. - -最终的 `index.js` 文件内容如下所示: +`index.js` 的内容如下: ````js $(function () { - var l = abp.localization.getResource('BookStore'); - var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); - var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal'); - - var dataTable = $('#BooksTable').DataTable(abp.libs.datatables.normalizeConfiguration({ - processing: true, - serverSide: true, - paging: true, - searching: false, - autoWidth: false, - scrollCollapse: true, - order: [[1, "asc"]], - ajax: abp.libs.datatables.createAjax(acme.bookStore.book.getList), - columnDefs: [ - { - rowAction: { - items: - [ - { - text: l('Edit'), - action: function (data) { - editModal.open({ id: data.record.id }); - } - }, - { - text: l('Delete'), - confirmMessage: function (data) { - return l('BookDeletionConfirmationMessage', data.record.name); - }, - action: function (data) { - acme.bookStore.book - .delete(data.record.id) - .then(function() { - abp.notify.info(l('SuccessfullyDeleted')); - dataTable.ajax.reload(); - }); - } - } - ] + var dataTable = $('#BooksTable').DataTable( + abp.libs.datatables.normalizeConfiguration({ + serverSide: true, + paging: true, + order: [[1, "asc"]], + searching: false, + scrollX: true, + ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList), + columnDefs: [ + { + title: l('Name'), + data: "name" + }, + { + title: l('Type'), + data: "type", + render: function (data) { + return l('Enum:BookType:' + data); + } + }, + { + title: l('PublishDate'), + data: "publishDate", + render: function (data) { + return luxon + .DateTime + .fromISO(data, { + locale: abp.localization.currentCulture.name + }).toLocaleString(); + } + }, + { + title: l('Price'), + data: "price" + }, + { + title: l('CreationTime'), data: "creationTime", + render: function (data) { + return luxon + .DateTime + .fromISO(data, { + locale: abp.localization.currentCulture.name + }).toLocaleString(luxon.DateTime.DATETIME_SHORT); + } } - }, - { data: "name" }, - { data: "type" }, - { data: "publishDate" }, - { data: "price" }, - { data: "creationTime" } - ] - })); - - createModal.onResult(function () { - dataTable.ajax.reload(); - }); - - editModal.onResult(function () { - dataTable.ajax.reload(); - }); - - $('#NewBookButton').click(function (e) { - e.preventDefault(); - createModal.open(); - }); + ] + }) + ); }); ```` -打开`Acme.BookStore.Domain.Shared`项目中的`en.json`并添加以下行: - -````json -"BookDeletionConfirmationMessage": "Are you sure to delete the book {0}?", -"SuccessfullyDeleted": "Successfully deleted" -```` - -运行程序并尝试删除一个book实体. - -{{end}} - -{{if UI == "NG"}} - -### 新增 Book 实体 - -下面的章节中,你将学习到如何创建一个新的模态对话框来新增Book实体. - -#### 添加 modal 到 BookListComponent - - -Open `book-list.component.ts` file in `app\book\book-list` folder and replace the content as below: - -```js -import { ListService, PagedResultDto } from '@abp/ng.core'; -import { Component, OnInit } from '@angular/core'; -import { BookDto, BookType } from '../models'; -import { BookService } from '../services'; - -@Component({ - selector: 'app-book-list', - templateUrl: './book-list.component.html', - styleUrls: ['./book-list.component.scss'], - providers: [ListService], -}) -export class BookListComponent implements OnInit { - book = { items: [], totalCount: 0 } as PagedResultDto; - - booksType = BookType; - - isModalOpen = false; // <== added this line ==> - - constructor(public readonly list: ListService, private bookService: BookService) {} - - ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getListByInput(query); - - this.list.hookToQuery(bookStreamCreator).subscribe((response) => { - this.book = response; - }); - } - - // added createBook method - createBook() { - this.isModalOpen = true; - } -} -``` - -* 我们定义了一个名为 `isModalOpen` 的变量和 `createBook` 方法. +* `abp.localization.getResource` 获取一个函数,该函数用于使用服务器端定义的相同JSON文件对文本进行本地化. 通过这种方式你可以与客户端共享本地化值. +* `abp.libs.datatables.normalizeConfiguration`是另一个辅助方法.不是必须的, 但是它通过为缺少的选项提供常规值来简化数据表配置. +* `abp.libs.datatables.createAjax`是帮助ABP的动态JavaScript API代理跟[Datatable](https://datatables.net/)的格式相适应的辅助方法. +* `acme.bookStore.books.book.getList` 是动态JavaScript代理函数(上面已经介绍过了) +* [luxon](https://moment.github.io/luxon/) 库也是该解决方案中预先配置的标准库,你可以轻松地执行日期/时间操作. -打开 `app\book\book-list` 文件夹内的 `book-list.component.html` 文件,使用以下内容替换它: +> 查看 [Datatable文档](https://datatables.net/manual/) 了解更多配置项. -```html -
    -
    -
    -
    -
    {%{{{ '::Menu:Books' | abpLocalization }}}%}
    -
    - -
    -
    - -
    -
    -
    -
    -
    - - - - - {%{{{ booksType[row.type] }}}%} - - - - - {%{{{ row.publishDate | date }}}%} - - - - - {%{{{ row.price | currency }}}%} - - - -
    -
    - - - - -

    {%{{{ '::NewBook' | abpLocalization }}}%}

    -
    - - - - - - -
    -``` +## 运行最终应用程序 -* 我们添加了 `abp-modal` 渲染模态框,允许用户创建新书. -* `abp-modal` 是显示模态框的预构建组件. 你也可以使用其它方法显示模态框,但 `abp-modal` 提供了一些附加的好处. -* 我们添加了 `New book` 按钮到 `AbpContentToolbar`. - -你可以打开浏览器,点击**New book**按钮看到模态框. - -![Empty modal for new book](./images/bookstore-empty-new-book-modal.png) - -#### 添加响应式表单 - -[响应式表单](https://angular.io/guide/reactive-forms) 提供一种模型驱动的方法来处理其值随时间变化的表单输入. - -打开 `app\book\book-list` 文件夹下的 `book-list.component.ts` 文件,使用以下内容替换它: - -```js -import { ListService, PagedResultDto } from '@abp/ng.core'; -import { Component, OnInit } from '@angular/core'; -import { BookDto, BookType } from '../models'; -import { BookService } from '../services'; -import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // <== added this line ==> - -@Component({ - selector: 'app-book-list', - templateUrl: './book-list.component.html', - styleUrls: ['./book-list.component.scss'], - providers: [ListService], -}) -export class BookListComponent implements OnInit { - book = { items: [], totalCount: 0 } as PagedResultDto; +你可以运行应用程序!该部分的最终用户界面如下所示: - booksType = BookType; +![Book list](images/bookstore-book-list-3.png) - isModalOpen = false; +这是一个完全正常工作的服务端分页,排序和本地化的图书列表. - form: FormGroup; // <== added this line ==> +{{end}} - constructor( - public readonly list: ListService, - private bookService: BookService, - private fb: FormBuilder // <== injected FormBuilder ==> - ) {} +{{if UI == "NG"}} - ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getListByInput(query); +## 安装NPM包 - this.list.hookToQuery(bookStreamCreator).subscribe((response) => { - this.book = response; - }); - } +>注意: 本教程基于ABP Framework v3.0.3+. 如果你的项目版本较旧,请升级您的解决方案. 如果要升级现有的v2.x项目,请参阅[迁移指南](../UI/Angular/Migration-Guide-v3.md). - createBook() { - this.buildForm(); // <== added this line ==> - this.isModalOpen = true; - } +在 `angular` 目录下打开命令行窗口,选择 `yarn` 命令安装NPM包: - // added buildForm method - buildForm() { - this.form = this.fb.group({ - name: ['', Validators.required], - type: [null, Validators.required], - publishDate: [null, Validators.required], - price: [null, Validators.required], - }); - } -} +```bash +yarn ``` -* 我们导入了 `FormGroup, FormBuilder and Validators`. -* 我们添加了 `form: FormGroup` 变量. -* 我们注入了 `fb: FormBuilder` 服务到构造函数. [FormBuilder](https://angular.io/api/forms/FormBuilder) 服务为生成控件提供了方便的方法. 它减少了构建复杂表单所需的样板文件的数量. -* 我们添加了 `buildForm` 方法到文件末尾, 在 `createBook` 方法调用 `buildForm()` 方法. 该方法创建一个响应式表单去创建新书. - * `FormBuilder` 内的 `fb.group` 方法创建一个 `FormGroup`. - * 添加了 `Validators.required` 静态方法用于验证表单元素. +## 创建图书页面 -#### 创建表单的DOM元素 +是时候创建可见和可用的东西了!开发ABP Angular前端应用程序时,需要使用一些工具: -打开 `app\book\book-list` 文件夹下的 `book-list.component.html` 文件,使用以下内容替换 ` `: +- [Ng Bootstrap](https://ng-bootstrap.github.io/#/home) 用做UI组件库. +- [ngx-datatable](https://swimlane.gitbook.io/ngx-datatable/) 用做 datatable 类库. -```html - -
    -
    - * - -
    +### BookModule -
    - * - -
    - -
    - * - -
    +运行以下命令创建一个名为 `BookModule` 的新模块: -
    - * - -
    -
    -
    +```bash +yarn ng generate module book --module app --routing --route books ``` -- 模板创建了带有 `Name`, `Price`, `Type` 和 `Publish` 时间字段的表单. -- 我们在组件中使用了 [NgBootstrap datepicker](https://ng-bootstrap.github.io/#/components/datepicker/overview). +该命令应该产生以下的输出: + +````bash +> yarn ng generate module book --module app --routing --route books + +yarn run v1.19.1 +$ ng generate module book --module app --routing --route books +CREATE src/app/book/book-routing.module.ts (336 bytes) +CREATE src/app/book/book.module.ts (335 bytes) +CREATE src/app/book/book.component.html (19 bytes) +CREATE src/app/book/book.component.spec.ts (614 bytes) +CREATE src/app/book/book.component.ts (268 bytes) +CREATE src/app/book/book.component.scss (0 bytes) +UPDATE src/app/app-routing.module.ts (1289 bytes) +Done in 3.88s. +```` -#### Datepicker 要求 +### BookModule -打开 `app\book` 文件夹下的 `book.module.ts` 文件,使用以下内容替换它: +打开 `/src/app/book/book.module.ts` 并使用以下内容替换: -```js +````js import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; - -import { BooksRoutingModule } from './book-routing.module'; -import { BooksComponent } from './book.component'; -import { BookListComponent } from './book-list/book-list.component'; import { SharedModule } from '../shared/shared.module'; -import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap'; //<== added this line ==> +import { BookRoutingModule } from './book-routing.module'; +import { BookComponent } from './book.component'; @NgModule({ - declarations: [BooksComponent, BookListComponent], + declarations: [BookComponent], imports: [ - CommonModule, - BooksRoutingModule, - SharedModule, - NgbDatepickerModule //<== added this line ==> + BookRoutingModule, + SharedModule ] }) -export class BooksModule { } -``` - -* 我们导入了 `NgbDatepickerModule` 来使用日期选择器. - -打开 `app\book\book-list` 文件夹下的 `book-list.component.ts` 文件,使用以下内容替换它: - -```js -import { ListService, PagedResultDto } from '@abp/ng.core'; -import { Component, OnInit } from '@angular/core'; -import { BookDto, BookType } from '../models'; -import { BookService } from '../services'; -import { FormGroup, FormBuilder, Validators } from '@angular/forms'; -import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; // <== added this line ==> - -@Component({ - selector: 'app-book-list', - templateUrl: './book-list.component.html', - styleUrls: ['./book-list.component.scss'], - providers: [ListService, { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], // <== added a provide ==> -}) -export class BookListComponent implements OnInit { - book = { items: [], totalCount: 0 } as PagedResultDto; +export class BookModule { } - booksType = BookType; - - // <== added bookTypes array ==> - bookTypes = Object.keys(BookType).filter( - (bookType) => typeof this.booksType[bookType] === 'number' - ); +```` - isModalOpen = false; +* 添加了 `SharedModule`. `SharedModule` 导出了一些创建用户界面所需的通用模块. +* `SharedModule` 已经导出了 `CommonModule`,所以我们删除了 `CommonModule`. - form: FormGroup; +### 路由 - constructor( - public readonly list: ListService, - private bookService: BookService, - private fb: FormBuilder - ) {} +生成的代码将新的路由定义放在 `src/app/app-routing.module.ts` 文件中,如下所示: - ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getListByInput(query); - - this.list.hookToQuery(bookStreamCreator).subscribe((response) => { - this.book = response; - }); - } +````js +const routes: Routes = [ + // other route definitions... + { path: 'books', loadChildren: () => import('./book/book.module').then(m => m.BookModule) }, +]; +```` - createBook() { - this.buildForm(); - this.isModalOpen = true; - } +现在打开 `src/app/route.provider.ts` 以下替换 `configureRoutes` 函数: - buildForm() { - this.form = this.fb.group({ - name: ['', Validators.required], - type: [null, Validators.required], - publishDate: [null, Validators.required], - price: [null, Validators.required], - }); - } +```js +function configureRoutes(routes: RoutesService) { + return () => { + routes.add([ + { + path: '/', + name: '::Menu:Home', + iconClass: 'fas fa-home', + order: 1, + layout: eLayoutType.application, + }, + { + path: '/book-store', + name: '::Menu:BookStore', + iconClass: 'fas fa-book', + order: 2, + layout: eLayoutType.application, + }, + { + path: '/books', + name: '::Menu:Books', + parentName: '::Menu:BookStore', + layout: eLayoutType.application, + }, + ]); + }; } ``` -* 我们导入了 ` NgbDateNativeAdapter, NgbDateAdapter` - -* 我们添加了一个新的 `NgbDateAdapter` 提供程序,它将Datepicker值转换为Date类型. 有关更多详细信息,请参见[datepicker adapters](https://ng-bootstrap.github.io/#/components/datepicker/overview). - -* 我们添加了 `bookTypes` 数组,以便能够在combobox值中使用它. `bookTypes` 包含 `BookType` 枚举的字段. 得到的数组如下所示: - - ```js - ['Adventure', 'Biography', 'Dystopia', 'Fantastic' ...] - ``` - - 在先前的表单模板中 用 `ngFor` 使用这个数组. - -现在你可以打开浏览器看到以下变化: - -![New book modal](./images/bookstore-new-book-form.png) - -#### 保存图书 - -打开 `app\book\book-list` 文件夹下的 `book-list.component.ts` 文件,使用以下内容替换它: - -```js -import { ListService, PagedResultDto } from '@abp/ng.core'; -import { Component, OnInit } from '@angular/core'; -import { BookDto, BookType } from '../models'; -import { BookService } from '../services'; -import { FormGroup, FormBuilder, Validators } from '@angular/forms'; -import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; - -@Component({ - selector: 'app-book-list', - templateUrl: './book-list.component.html', - styleUrls: ['./book-list.component.scss'], - providers: [ListService, { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], -}) -export class BookListComponent implements OnInit { - book = { items: [], totalCount: 0 } as PagedResultDto; - - booksType = BookType; - - bookTypes = Object.keys(BookType).filter( - (bookType) => typeof this.booksType[bookType] === 'number' - ); - - isModalOpen = false; - - form: FormGroup; - - constructor( - public readonly list: ListService, - private bookService: BookService, - private fb: FormBuilder - ) {} - - ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getListByInput(query); - - this.list.hookToQuery(bookStreamCreator).subscribe((response) => { - this.book = response; - }); - } - - createBook() { - this.buildForm(); - this.isModalOpen = true; - } +`RoutesService` 是ABP框架提供的用于配置主菜单和路由的服务. - buildForm() { - this.form = this.fb.group({ - name: ['', Validators.required], - type: [null, Validators.required], - publishDate: [null, Validators.required], - price: [null, Validators.required], - }); - } - - // <== added save ==> - save() { - if (this.form.invalid) { - return; - } - - this.bookService.createByInput(this.form.value).subscribe(() => { - this.isModalOpen = false; - this.form.reset(); - this.list.get(); - }); - } -} -``` +* `path` 路由的URL. +* `name` 菜单项的名称(参阅[本地化文档](../UI/Angular/Localization.md)了解更多). +* `iconClass` 菜单项的图标(你可以使用默认的[Font Awesome](https://fontawesome.com/)图标). +* `order` 菜单项的排序.我们定义了101,它显示在 "Administration" 项的后面. +* `layout` BooksModule路由的布局. 可以定义 `eLayoutType.application`, `eLayoutType.account` 或 `eLayoutType.empty`. -* 我们添加了 `save` 方法. +更多信息请参阅[RoutesService 文档](https://docs.abp.io/en/abp/latest/UI/Angular/Modifying-the-Menu.md#via-routesservice). -打开 `app\book\book-list` 文件夹下的 `book-list.component.html` 文件, 找到 `` 元素,使用下面元素替换它: +### 生成代理 -```html - - - - - - -``` +ABP CLI提供了 `generate-proxy` 命令为你的服务HTTP API生成客户端代理简化客户端使用服务的成本. 运行 `generate-proxy` 命令前你的host必须正在运行. 参阅 [CLI 文档](../CLI.md). -使用以下内容替换 `
    ` 标签: +在 `angular` 文件夹下运行以下命令: -```html - +```bash +abp generate-proxy ``` -* 我们添加了 `(ngSubmit)="save()"` 到 `` 元素,当按下enter时保存图书. -* 我们在模态框的底部添加了 `abp-button` 来保存图书. +生成的文件如下: -模态框最终看起来像这样: +![Generated files](./images/generated-proxies-2.png) -![Save button to the modal](./images/bookstore-new-book-form-v2.png) +### BookComponent -### 更新图书 - -打开 `app\book\book-list` 文件夹下的 `book-list.component.ts` 文件并且添加名为 `selectedBook` 的变量. +打开 `/src/app/book/book.component.ts` 用以下内容替换它: ```js import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, OnInit } from '@angular/core'; -import { BookDto, BookType } from '../models'; -import { BookService } from '../services'; -import { FormGroup, FormBuilder, Validators } from '@angular/forms'; -import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; +import { BookDto } from './models'; +import { BookService } from './services'; @Component({ - selector: 'app-book-list', - templateUrl: './book-list.component.html', - styleUrls: ['./book-list.component.scss'], - providers: [ListService, { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], + selector: 'app-book', + templateUrl: './book.component.html', + styleUrls: ['./book.component.scss'], + providers: [ListService], }) -export class BookListComponent implements OnInit { +export class BookComponent implements OnInit { book = { items: [], totalCount: 0 } as PagedResultDto; - booksType = BookType; - - bookTypes = Object.keys(BookType).filter( - (bookType) => typeof this.booksType[bookType] === 'number' - ); - - isModalOpen = false; - - form: FormGroup; - - selectedBook = {} as BookDto; // <== declared selectedBook ==> - - constructor( - public readonly list: ListService, - private bookService: BookService, - private fb: FormBuilder - ) {} + constructor(public readonly list: ListService, private bookService: BookService) {} ngOnInit() { const bookStreamCreator = (query) => this.bookService.getListByInput(query); @@ -927,196 +500,55 @@ export class BookListComponent implements OnInit { this.book = response; }); } - - // <== this method is replaced ==> - createBook() { - this.selectedBook = {} as BookDto; // <== added ==> - this.buildForm(); - this.isModalOpen = true; - } - - // <== added editBook method ==> - editBook(id: string) { - this.bookService.getById(id).subscribe((book) => { - this.selectedBook = book; - this.buildForm(); - this.isModalOpen = true; - }); - } - - // <== this method is replaced ==> - buildForm() { - this.form = this.fb.group({ - name: [this.selectedBook.name || '', Validators.required], - type: [this.selectedBook.type || null, Validators.required], - publishDate: [ - this.selectedBook.publishDate ? new Date(this.selectedBook.publishDate) : null, - Validators.required, - ], - price: [this.selectedBook.price || null, Validators.required], - }); - } - - // <== this method is replaced ==> - save() { - if (this.form.invalid) { - return; - } - - // <== added request ==> - const request = this.selectedBook.id - ? this.bookService.updateByIdAndInput(this.form.value, this.selectedBook.id) - : this.bookService.createByInput(this.form.value); - - request.subscribe(() => { - this.isModalOpen = false; - this.form.reset(); - this.list.get(); - }); - } -} -``` - -* 我们声明了类型为 `BookDto` 的 `selectedBook` 变量. -* 我们添加了 `editBook` 方法, 根据给定图书 `Id` 设置 `selectedBook` 对象. -* 我们替换了 `buildForm` 方法使用 `selectedBook` 数据创建表单. -* 我们替换了 `createBook` 方法,设置 `selectedBook` 为空对象. -* 我们替换了 `save` 方法. - -#### 添加 "Actions" 下拉框到表格 - -打开 `app\book\book-list` 文件夹下的 `book-list.component.html` 文件,使用以下内容替换 `
    ` 标签: - -```html -
    - - - - -
    - -
    - -
    -
    -
    -
    - - - - {%{{{ booksType[row.type] }}}%} - - - - - {%{{{ row.publishDate | date }}}%} - - - - - {%{{{ row.price | currency }}}%} - - -
    -
    -``` - -- 我们添加了 "Actions" 栏的 `th`. -- 我们为 "Actions" 栏添加了 `ngx-datatable-column`. -- 我们添加了带有 `ngbDropdownToggle` 的 `button`,在点击按钮时打开操作. -- 我们习惯于将[NgbDropdown](https://ng-bootstrap.github.io/#/components/dropdown/examples)用于操作的下拉菜单. - -UI最终看起来像这样: - -![Action buttons](./images/bookstore-actions-buttons.png) - -打开 `app\book\book-list` 文件夹下的 `book-list.component.html` 文件,使用以下内容替换 `` 标签: - -```html - -

    {%{{{ (selectedBook.id ? 'AbpIdentity::Edit' : '::NewBook' ) | abpLocalization }}}%}

    -
    -``` - -* **Edit** 文本做为编辑记录操作的标题, **New Book** 做为添加记录操作的标题. - -### 删除图书 - -#### 删除确认弹层 - -打开 `app\book\book-list` 文件夹下的 `book-list.component.ts` 文件,注入 `ConfirmationService`. - -替换构造函数: - -```js -import { ConfirmationService } from '@abp/ng.theme.shared'; -//... - -constructor( - public readonly list: ListService, - private bookService: BookService, - private fb: FormBuilder, - private confirmation: ConfirmationService // <== added this line ==> -) {} -``` - -* We imported `ConfirmationService`. -* We injected `ConfirmationService` to the constructor. - -See the [Confirmation Popup documentation](https://docs.abp.io/en/abp/latest/UI/Angular/Confirmation-Service) - -In the `book-list.component.ts` add a delete method: - -```js -import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared'; //<== imported Confirmation namespace ==> - -//... - -delete(id: string) { - this.confirmation.warn('::AreYouSureToDelete', 'AbpAccount::AreYouSure').subscribe((status) => { - if (status === Confirmation.Status.confirm) { - this.bookService.deleteById(id).subscribe(() => this.list.get()); - } - }); } ``` -`delete` 方法会显示一个确认弹层并订阅用户响应. 只在用户点击 `Yes` 按钮时调用 `BookService` 的 `deleteById` 方法. 确认弹层看起来如下: - -![bookstore-confirmation-popup](./images/bookstore-confirmation-popup.png) +* 我们注入了生成的 `BookService`. +* 我们实现了 [ListService](https://docs.abp.io/en/abp/latest/UI/Angular/List-Service),它是一个公用服务,提供了简单的分页,排序和搜索. -#### 添加删除按钮 - -打开 `app\book\book-list` 文件夹下的 `app\book\book-list` 文件,修改 `ngbDropdownMenu` 添加删除按钮: +打开 `/src/app/book/book.component.html` 用以下内容替换它: ```html -
    - - +
    +
    +
    +
    +
    + {%{{{ '::Menu:Books' | abpLocalization }}}%} +
    +
    +
    +
    +
    +
    + + + + + {%{{{ '::Enum:BookType:' + row.type | abpLocalization }}}%} + + + + + {%{{{ row.publishDate | date }}}%} + + + + + {%{{{ row.price | currency }}}%} + + + +
    ``` -最终操作下拉框UI看起来如下: +现在你可以在浏览器看到最终结果: -![bookstore-final-actions-dropdown](./images/bookstore-final-actions-dropdown.png) +![Book list final result](./images/bookstore-book-list.png) {{end}} -### 下一章 +## 下一章 -查看本教程的 [下一章](Part-3.md) . +查看本教程的[下一章](Part-3.md). diff --git a/docs/zh-Hans/Tutorials/Part-3.md b/docs/zh-Hans/Tutorials/Part-3.md index cded954e6b..7ad96a0465 100644 --- a/docs/zh-Hans/Tutorials/Part-3.md +++ b/docs/zh-Hans/Tutorials/Part-3.md @@ -1,198 +1,1187 @@ -## ASP.NET Core {{UI_Value}} 教程 - 第三章 +# Web应用程序开发教程 - 第三章: 创建,更新和删除图书 ````json //[doc-params] { - "UI": ["MVC","NG"] + "UI": ["MVC","NG"], + "DB": ["EF","Mongo"] } ```` - {{ 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 ="?" +else UI_Text="?" end +if DB == "EF" + DB_Text="Entity Framework Core" +else if DB == "Mongo" + DB_Text="MongoDB" +else + DB_Text="?" +end }} -### 关于本教程 +## 关于本教程 -这是ASP.NET Core{{UI_Value}}系列教程的第二章. 共有三章: +在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的: -- [Part-1: 创建项目和书籍列表页面](Part-1.md) -- [Part 2: 创建,编辑,删除书籍](Part-2.md) -- **Part-3: 集成测试(本章)** +* **{{DB_Text}}** 做为ORM提供程序. +* **{{UI_Value}}** 做为UI框架. -> 你也可以观看由ABP社区成员为本教程录制的[视频课程](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application). +本教程分为以下部分: -### 解决方案中的测试项目 +- [Part 1: 创建服务端](Part-1.md) +- [Part 2: 图书列表页面](Part-2.md) +- **Part 3: 创建,更新和删除图书**(本章) +- [Part 4: 集成测试](Part-4.md) +- [Part 5: 授权](Part-5.md) +- [Part 6: 作者: 领域层](Part-6.md) +- [Part 7: 作者: 数据库集成](Part-7.md) +- [Part 8: 作者: 应用服务层](Part-8.md) +- [Part 9: 作者: 用户页面](Part-9.md) +- [Part 10: 图书到作者的关系](Part-10.md) -解决方案中有多个测试项目: +## 下载源码 -![bookstore-test-projects-v2](./images/bookstore-test-projects-{{UI_Text}}.png) +本教程根据你的**UI** 和 **Database**偏好有多个版,我们准备了两种可供下载的源码组合: -每个项目用于测试相关的应用程序项目.测试项目使用以下库进行测试: +* [MVC (Razor Pages) UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) +* [Angular UI 与 MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) -* [xunit](https://xunit.github.io/) 作为主测试框架. -* [Shoudly](http://shouldly.readthedocs.io/en/latest/) 作为断言库. -* [NSubstitute](http://nsubstitute.github.io/) 作为模拟库. +{{if UI == "MVC"}} -### 添加测试数据 +## 创建新书籍 -启动模板包含`Acme.BookStore.TestBase`项目中的`BookStoreTestDataSeedContributor`类,它创建一些数据来运行测试. -更改`BookStoreTestDataSeedContributor`类如下所示: +通过本节, 你将会了解如何创建一个 modal form 来实现新增书籍的功能. 最终成果如下图所示: -````csharp -using System; +![bookstore-create-dialog](./images/bookstore-create-dialog-2.png) + +### 创建 modal form + +在 `Acme.BookStore.Web` 项目的 `Pages/Books` 目录下新建一个 `CreateModal.cshtml` Razor页面: + +![bookstore-add-create-dialog](./images/bookstore-add-create-dialog-v2.png) + +#### CreateModal.cshtml.cs + +打开 `CreateModal.cshtml.cs` 代码文件,用如下代码替换 `CreateModalModel` 类的实现: + +````C# using System.Threading.Tasks; -using Volo.Abp.Data; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Repositories; -using Volo.Abp.Guids; +using Acme.BookStore.Books; +using Microsoft.AspNetCore.Mvc; -namespace Acme.BookStore +namespace Acme.BookStore.Web.Pages.Books { - public class BookStoreTestDataSeedContributor - : IDataSeedContributor, ITransientDependency + public class CreateModalModel : BookStorePageModel { - private readonly IRepository _bookRepository; - private readonly IGuidGenerator _guidGenerator; + [BindProperty] + public CreateUpdateBookDto Book { get; set; } + + private readonly IBookAppService _bookAppService; + + public CreateModalModel(IBookAppService bookAppService) + { + _bookAppService = bookAppService; + } - public BookStoreTestDataSeedContributor( - IRepository bookRepository, - IGuidGenerator guidGenerator) + public void OnGet() { - _bookRepository = bookRepository; - _guidGenerator = guidGenerator; + Book = new CreateUpdateBookDto(); } - public async Task SeedAsync(DataSeedContext context) + public async Task OnPostAsync() { - await _bookRepository.InsertAsync( - new Book(id: _guidGenerator.Create(), - name: "Test book 1", - type: BookType.Fantastic, - publishDate: new DateTime(2015, 05, 24), - price: 21 - ) - ); - - await _bookRepository.InsertAsync( - new Book(id: _guidGenerator.Create(), - name: "Test book 2", - type: BookType.Science, - publishDate: new DateTime(2014, 02, 11), - price: 15 - ) - ); + await _bookAppService.CreateAsync(Book); + return NoContent(); } } } ```` -* 注入`IRepository`并在`SeedAsync`中使用它来创建两个书实体作为测试数据. -* 使用`IGuidGenerator`服务创建GUID. 虽然`Guid.NewGuid()`非常适合测试,但`IGuidGenerator`在使用真实数据库时还有其他特别重要的功能(参见[Guid生成文档](../Guid-Generation.md)了解更多信息). +* 该类派生于 `BookStorePageModel` 而非默认的 `PageModel`. `BookStorePageModel` 继承了 `PageModel` 并且添加了一些可以被你的page model类使用的通用属性和方法. +* `Book` 属性上的 `[BindProperty]` 特性将post请求提交上来的数据绑定到该属性上. +* 该类通过构造函数注入了 `IBookAppService` 应用服务,并且在 `OnPostAsync` 处理程序中调用了服务的 `CreateAsync` 方法. +* 它在 `OnGet` 方法中创建一个新的 `CreateUpdateBookDto` 对象。 ASP.NET Core不需要像这样创建一个新实例就可以正常工作. 但是它不会为你创建实例,并且如果你的类在类构造函数中具有一些默认值分配或代码执行,它们将无法工作. 对于这种情况,我们为某些 `CreateUpdateBookDto` 属性设置了默认值. + +#### CreateModal.cshtml + +打开 `CreateModal.cshtml` 文件并粘贴如下代码: + +````html +@page +@using Acme.BookStore.Localization +@using Acme.BookStore.Web.Pages.Books +@using Microsoft.Extensions.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@model CreateModalModel +@inject IStringLocalizer L +@{ + Layout = null; +} + + + + + + + + + +```` + +* 这个 modal 使用 `abp-dynamic-form` [tag Helper](../UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md) 根据 `CreateBookViewModel` 类自动构建了表单. +* `abp-model` 指定了 `Book` 属性为模型对象. +* `data-ajaxForm` 设置了表单通过AJAX提交,而不是经典的页面回发. +* `abp-form-content` tag helper 作为表单控件渲染位置的占位符 (这是可选的,只有你在 `abp-dynamic-form` 中像本示例这样添加了其他内容才需要). + +> 提示: 就像在本示例中一样,`Layout` 应该为 `null`,因为当通过AJAX加载模态时,我们不希望包括所有布局. + +### 添加 "New book" 按钮 -### 测试 BookAppService +打开 `Pages/Books/Index.cshtml` 并按如下代码修改 `abp-card-header` : -在 `Acme.BookStore.Application.Tests` 项目中创建一个名叫 `BookAppService_Tests` 的测试类: +````html + + + + @L["Books"] + + + + + + +```` + +`Index.cshtml` 的内容最终如下所示: + +````html +@page +@using Acme.BookStore.Localization +@using Acme.BookStore.Web.Pages.Books +@using Microsoft.Extensions.Localization +@model IndexModel +@inject IStringLocalizer L +@section scripts +{ + +} + + + + + + @L["Books"] + + + + + + + + + + +```` + +如下图所示,只是在表格 **右上方** 添加了 **New book** 按钮: + +![bookstore-new-book-button](./images/bookstore-new-book-button-2.png) + +打开 `Pages/book/index.js` 在 `datatable` 配置代码后面添加如下代码: + +````js +var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); + +createModal.onResult(function () { + dataTable.ajax.reload(); +}); + +$('#NewBookButton').click(function (e) { + e.preventDefault(); + createModal.open(); +}); +```` + +* `abp.ModalManager` 是一个在客户端打开和管理modal的辅助类.它基于Twitter Bootstrap的标准modal组件通过简化的API抽象隐藏了许多细节. +* `createModal.onResult(...)` 用于在创建书籍后刷新数据表格. +* `createModal.open();` 用于打开模态创建新书籍. + +`Index.js` 的内容最终如下所示: + +````js +$(function () { + var l = abp.localization.getResource('BookStore'); + + var dataTable = $('#BooksTable').DataTable( + abp.libs.datatables.normalizeConfiguration({ + serverSide: true, + paging: true, + order: [[1, "asc"]], + searching: false, + scrollX: true, + ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList), + columnDefs: [ + { + title: l('Name'), + data: "name" + }, + { + title: l('Type'), + data: "type", + render: function (data) { + return l('Enum:BookType:' + data); + } + }, + { + title: l('PublishDate'), + data: "publishDate", + render: function (data) { + return luxon + .DateTime + .fromISO(data, { + locale: abp.localization.currentCulture.name + }).toLocaleString(); + } + }, + { + title: l('Price'), + data: "price" + }, + { + title: l('CreationTime'), data: "creationTime", + render: function (data) { + return luxon + .DateTime + .fromISO(data, { + locale: abp.localization.currentCulture.name + }).toLocaleString(luxon.DateTime.DATETIME_SHORT); + } + } + ] + }) + ); + + var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); + + createModal.onResult(function () { + dataTable.ajax.reload(); + }); + + $('#NewBookButton').click(function (e) { + e.preventDefault(); + createModal.open(); + }); +}); +```` + +现在,你可以 **运行程序** 通过新的 modal form 来创建书籍了. + +## 更新书籍 + +在 `Acme.BookStore.Web` 项目的 `Pages/Books` 目录下新建一个名叫 `EditModal.cshtml` 的Razor页面: + +![bookstore-add-edit-dialog](./images/bookstore-add-edit-dialog.png) + +### EditModal.cshtml.cs + +打开 `EditModal.cshtml.cs` 文件(`EditModalModel`类) 并替换成以下代码: ````csharp using System; -using System.Linq; using System.Threading.Tasks; -using Xunit; -using Shouldly; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Validation; -using Microsoft.EntityFrameworkCore.Internal; +using Acme.BookStore.Books; +using Microsoft.AspNetCore.Mvc; -namespace Acme.BookStore +namespace Acme.BookStore.Web.Pages.Books { - public class BookAppService_Tests : BookStoreApplicationTestBase + public class EditModalModel : BookStorePageModel { + [HiddenInput] + [BindProperty(SupportsGet = true)] + public Guid Id { get; set; } + + [BindProperty] + public CreateUpdateBookDto Book { get; set; } + private readonly IBookAppService _bookAppService; - public BookAppService_Tests() + public EditModalModel(IBookAppService bookAppService) + { + _bookAppService = bookAppService; + } + + public async Task OnGetAsync() { - _bookAppService = GetRequiredService(); + var bookDto = await _bookAppService.GetAsync(Id); + Book = ObjectMapper.Map(bookDto); } - [Fact] - public async Task Should_Get_List_Of_Books() + public async Task OnPostAsync() { - //Act - var result = await _bookAppService.GetListAsync( - new PagedAndSortedResultRequestDto() - ); - - //Assert - result.TotalCount.ShouldBeGreaterThan(0); - result.Items.ShouldContain(b => b.Name == "Test book 1"); + await _bookAppService.UpdateAsync(Id, Book); + return NoContent(); } } } ```` -* 测试方法 `Should_Get_List_Of_Books` 直接使用 `BookAppService.GetListAsync` 方法来获取用户列表,并执行检查. +* `[HiddenInput]` 和 `[BindProperty]` 是标准的 ASP.NET Core MVC 特性.这里启用 `SupportsGet` 从Http请求的查询字符串中获取Id的值. +* 在 `OnGetAsync` 方法中,将 `BookAppService.GetAsync` 方法返回的 `BookDto` 映射成 `CreateUpdateBookDto` 并赋值给Book属性. +* `OnPostAsync` 方法直接使用 `BookAppService.UpdateAsync` 来更新实体. -新增测试方法,用以测试创建一个合法book实体的场景: +### BookDto 到 CreateUpdateBookDto 对象映射 -````C# -[Fact] -public async Task Should_Create_A_Valid_Book() +为了执行 `BookDto` 到 `CreateUpdateBookDto` 对象映射,请打开 `Acme.BookStore.Web` 项目中的 `BookStoreWebAutoMapperProfile.cs` 并更改它,如下所示: + +````csharp +using AutoMapper; + +namespace Acme.BookStore.Web { - //Act - var result = await _bookAppService.CreateAsync( - new CreateUpdateBookDto + public class BookStoreWebAutoMapperProfile : Profile + { + public BookStoreWebAutoMapperProfile() { - Name = "New test book 42", - Price = 10, - PublishDate = DateTime.Now, - Type = BookType.ScienceFiction + CreateMap(); } - ); + } +} +```` + +* 我们添加了 `CreateMap();` 作为映射定义. + +> 请注意,我们在Web层中进行映射定义是一种最佳实践,因为仅在该层中需要它. - //Assert - result.Id.ShouldNotBe(Guid.Empty); - result.Name.ShouldBe("New test book 42"); +### EditModal.cshtml + +将 `EditModal.cshtml` 页面内容替换成如下代码: + +````html +@page +@using Acme.BookStore.Localization +@using Acme.BookStore.Web.Pages.Books +@using Microsoft.Extensions.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@model EditModalModel +@inject IStringLocalizer L +@{ + Layout = null; } + + + + + + + + + + ```` -新增测试方法,用以测试创建一个非法book实体失败的场景: +这个页面内容和 `CreateModal.cshtml` 非常相似,除了以下几点: -````csharp -[Fact] -public async Task Should_Not_Create_A_Book_Without_Name() +* 它包含`id`属性的`abp-input`, 用于存储编辑书的 `id` (它是隐藏的Input) +* 此页面指定的post地址是`Books/EditModal`. + +### 为表格添加 "操作(Actions)" 下拉菜单 + +我们将为表格每行添加下拉按钮 ("Actions"): + +打开 `Pages/Books/Index.cshtml` 页面,并按下方所示修改表格部分的代码: + +````js +$(function () { + var l = abp.localization.getResource('BookStore'); + var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); + var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal'); + + var dataTable = $('#BooksTable').DataTable( + abp.libs.datatables.normalizeConfiguration({ + serverSide: true, + paging: true, + order: [[1, "asc"]], + searching: false, + scrollX: true, + ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList), + columnDefs: [ + { + title: l('Actions'), + rowAction: { + items: + [ + { + text: l('Edit'), + action: function (data) { + editModal.open({ id: data.record.id }); + } + } + ] + } + }, + { + title: l('Name'), + data: "name" + }, + { + title: l('Type'), + data: "type", + render: function (data) { + return l('Enum:BookType:' + data); + } + }, + { + title: l('PublishDate'), + data: "publishDate", + render: function (data) { + return luxon + .DateTime + .fromISO(data, { + locale: abp.localization.currentCulture.name + }).toLocaleString(); + } + }, + { + title: l('Price'), + data: "price" + }, + { + title: l('CreationTime'), data: "creationTime", + render: function (data) { + return luxon + .DateTime + .fromISO(data, { + locale: abp.localization.currentCulture.name + }).toLocaleString(luxon.DateTime.DATETIME_SHORT); + } + } + ] + }) + ); + + createModal.onResult(function () { + dataTable.ajax.reload(); + }); + + editModal.onResult(function () { + dataTable.ajax.reload(); + }); + + $('#NewBookButton').click(function (e) { + e.preventDefault(); + createModal.open(); + }); +}); +```` + +* 增加了一个新的 `ModalManager` 名为 `editModal` 打开编辑模态框. +* 在 `columnDefs` 部分的开头添加了一个新列,用于"*Actions*"下拉按钮. +* "*Edit*" 动作简单地调用 `editModal.open()` 打开编辑模态框. +* `editModal.onResult(...)` 当你关闭编程模态框时进行回调刷新数据表格. + +你可以运行应用程序,并通过选择一本书的编辑操作编辑任何一本书. + +最终的UI看起来如下: + +![bookstore-books-table-actions](./images/bookstore-edit-button-2.png) + +## 删除书籍 + +打开 `Pages/book/index.js` 文件,在 `rowAction` `items` 下新增一项: + +````js { - var exception = await Assert.ThrowsAsync(async () => - { - await _bookAppService.CreateAsync( - new CreateUpdateBookDto - { - Name = "", - Price = 10, - PublishDate = DateTime.Now, - Type = BookType.ScienceFiction - } - ); - }); - - exception.ValidationErrors - .ShouldContain(err => err.MemberNames.Any(mem => mem == "Name")); + text: l('Delete'), + confirmMessage: function (data) { + return l('BookDeletionConfirmationMessage', data.record.name); + }, + action: function (data) { + acme.bookStore.books.book + .delete(data.record.id) + .then(function() { + abp.notify.info(l('SuccessfullyDeleted')); + dataTable.ajax.reload(); + }); + } } ```` -* 由于 `Name` 是空值, ABP 抛出一个 `AbpValidationException` 异常. +* `confirmMessage` 用来在实际执行 `action` 之前向用户进行确认. +* 通过javascript代理方法 `acme.bookStore.books.book.delete(...)` 执行一个AJAX请求来删除一个book实体. +* `abp.notify.info` 用来在执行删除操作后显示一个toastr通知信息. + +由于我们使用了两个新的本地化文本(`BookDeletionConfirmationMessage`和`SuccesslyDeleted`),因此你需要将它们添加到本地化文件(`Acme.BookStore.Domain.Shared`项目的`Localization/BookStore`文件夹下的`en.json`): + +````json +"BookDeletionConfirmationMessage": "Are you sure to delete the book '{0}'?", +"SuccessfullyDeleted": "Successfully deleted!" +```` + +`Index.js` 的内容最终如下所示: + +````js +$(function () { + var l = abp.localization.getResource('BookStore'); + var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); + var editModal = new abp.ModalManager(abp.appPath + 'Books/EditModal'); + + var dataTable = $('#BooksTable').DataTable( + abp.libs.datatables.normalizeConfiguration({ + serverSide: true, + paging: true, + order: [[1, "asc"]], + searching: false, + scrollX: true, + ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList), + columnDefs: [ + { + title: l('Actions'), + rowAction: { + items: + [ + { + text: l('Edit'), + action: function (data) { + editModal.open({ id: data.record.id }); + } + }, + { + text: l('Delete'), + confirmMessage: function (data) { + return l( + 'BookDeletionConfirmationMessage', + data.record.name + ); + }, + action: function (data) { + acme.bookStore.books.book + .delete(data.record.id) + .then(function() { + abp.notify.info( + l('SuccessfullyDeleted') + ); + dataTable.ajax.reload(); + }); + } + } + ] + } + }, + { + title: l('Name'), + data: "name" + }, + { + title: l('Type'), + data: "type", + render: function (data) { + return l('Enum:BookType:' + data); + } + }, + { + title: l('PublishDate'), + data: "publishDate", + render: function (data) { + return luxon + .DateTime + .fromISO(data, { + locale: abp.localization.currentCulture.name + }).toLocaleString(); + } + }, + { + title: l('Price'), + data: "price" + }, + { + title: l('CreationTime'), data: "creationTime", + render: function (data) { + return luxon + .DateTime + .fromISO(data, { + locale: abp.localization.currentCulture.name + }).toLocaleString(luxon.DateTime.DATETIME_SHORT); + } + } + ] + }) + ); + + createModal.onResult(function () { + dataTable.ajax.reload(); + }); + + editModal.onResult(function () { + dataTable.ajax.reload(); + }); + + $('#NewBookButton').click(function (e) { + e.preventDefault(); + createModal.open(); + }); +}); +```` + +你可以运行程序并尝试删除一本书. + +{{end}} + +{{if UI == "NG"}} + +## 创建新书籍 + +下面的章节中,你将学习到如何创建一个新的模态对话框来新增书籍. + +### BookComponent + +打开 `/src/app/book/book.component.ts` 使用以下内容替换: + +```js +import { ListService, PagedResultDto } from '@abp/ng.core'; +import { Component, OnInit } from '@angular/core'; +import { BookDto } from './models'; +import { BookService } from './services'; + +@Component({ + selector: 'app-book', + templateUrl: './book.component.html', + styleUrls: ['./book.component.scss'], + providers: [ListService], +}) +export class BookComponent implements OnInit { + book = { items: [], totalCount: 0 } as PagedResultDto; + + isModalOpen = false; // add this line + + constructor(public readonly list: ListService, private bookService: BookService) {} + + ngOnInit() { + const bookStreamCreator = (query) => this.bookService.getListByInput(query); + + this.list.hookToQuery(bookStreamCreator).subscribe((response) => { + this.book = response; + }); + } + + // add new method + createBook() { + this.isModalOpen = true; + } +} +``` + +* 我们定义了一个名为 `isModalOpen` 的变量和 `createBook` 方法. + +打开 `/src/app/book/book.component.html` 做以下更改: + +```html +
    +
    +
    +
    +
    {%{{{ '::Menu:Books' | abpLocalization }}}%}
    +
    +
    + + +
    + +
    + +
    +
    +
    +
    + +
    +
    + + + + +

    {%{{{ '::NewBook' | abpLocalization }}}%}

    +
    + + + + + + +
    +``` + +* 添加了 `New book` 按钮到卡片头部. +* 添加了 `abp-modal` 渲染模态框,允许用户创建新书. `abp-modal` 是显示模态框的预构建组件. 你也可以使用其它方法显示模态框,但 `abp-modal` 提供了一些附加的好处. + +你可以打开浏览器,点击**New book**按钮看到模态框. + +![Empty modal for new book](./images/bookstore-empty-new-book-modal.png) + +### 添加响应式表单 + +[响应式表单](https://angular.io/guide/reactive-forms) 提供一种模型驱动的方法来处理其值随时间变化的表单输入. + +打开 `/src/app/book/book.component.ts` 使用以下内容替换: + +```js +import { ListService, PagedResultDto } from '@abp/ng.core'; +import { Component, OnInit } from '@angular/core'; +import { BookDto, BookType } from './models'; // add BookType +import { BookService } from './services'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // add this + +@Component({ + selector: 'app-book', + templateUrl: './book.component.html', + styleUrls: ['./book.component.scss'], + providers: [ListService], +}) +export class BookComponent implements OnInit { + book = { items: [], totalCount: 0 } as PagedResultDto; + + form: FormGroup; // add this line + + bookType = BookType; // add this line + + // add bookTypes as a list of BookType enum members + bookTypes = Object.keys(this.bookType).filter( + (key) => typeof this.bookType[key] === 'number' + ); + + isModalOpen = false; + + constructor( + public readonly list: ListService, + private bookService: BookService, + private fb: FormBuilder // inject FormBuilder + ) {} + + ngOnInit() { + const bookStreamCreator = (query) => this.bookService.getListByInput(query); + + this.list.hookToQuery(bookStreamCreator).subscribe((response) => { + this.book = response; + }); + } + + createBook() { + this.buildForm(); // add this line + this.isModalOpen = true; + } + + // add buildForm method + buildForm() { + this.form = this.fb.group({ + name: ['', Validators.required], + type: [null, Validators.required], + publishDate: [null, Validators.required], + price: [null, Validators.required], + }); + } + + // add save method + save() { + if (this.form.invalid) { + return; + } + + this.bookService.createByInput(this.form.value).subscribe(() => { + this.isModalOpen = false; + this.form.reset(); + this.list.get(); + }); + } +} +``` + +* 导入了 `FormGroup, FormBuilder and Validators`. +* 添加了 `form: FormGroup` 变量. +* 添加了 `bookType` 属性,你可以从模板中获取 `BookType` 枚举成员. +* 添加了 `bookTypes` 属性作为 `BookType` 枚举成员列表. 将在表单选项中使用. +* 我们注入了 `fb: FormBuilder` 服务到构造函数. [FormBuilder](https://angular.io/api/forms/FormBuilder) 服务为生成控件提供了方便的方法. 它减少了构建复杂表单所需的样板文件的数量. +* 我们添加了 `buildForm` 方法到文件末尾, 在 `createBook` 方法调用 `buildForm()` 方法. 该方法创建一个响应式表单去创建新书. +* 添加了`save` 方法. + +打开 `/src/app/book/book.component.html`,使用以下内容替换 ` `: + +```html + + +
    + * + +
    + +
    + * + +
    + +
    + * + +
    + +
    + * + +
    + +
    +``` + +同时使用下面的代码部分替换 ` `: + +````html + + + + + + +```` + +### Datepicker + +我们在这个组件中使用了[NgBootstrap datepicker](https://ng-bootstrap.github.io/#/components/datepicker/overview). 因此需要添加与此组件相关的依赖项. + +打开 `/src/app/book/book.module.ts` 使用以下内容替换: + +```js +import { NgModule } from '@angular/core'; +import { SharedModule } from '../shared/shared.module'; +import { BookRoutingModule } from './book-routing.module'; +import { BookComponent } from './book.component'; +import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap'; // add this line + +@NgModule({ + declarations: [BookComponent], + imports: [ + BookRoutingModule, + SharedModule, + NgbDatepickerModule, // add this line + ] +}) +export class BookModule { } +``` + +* 我们导入了 `NgbDatepickerModule` 来使用日期选择器. + +打开 `/src/app/book/book.component.ts` 使用以内内容替换: + +```js +import { ListService, PagedResultDto } from '@abp/ng.core'; +import { Component, OnInit } from '@angular/core'; +import { BookDto, BookType } from './models'; +import { BookService } from './services'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; + +// added this line +import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + selector: 'app-book', + templateUrl: './book.component.html', + styleUrls: ['./book.component.scss'], + providers: [ + ListService, + { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter } // add this line + ], +}) +export class BookComponent implements OnInit { + book = { items: [], totalCount: 0 } as PagedResultDto; + + form: FormGroup; + + bookType = BookType; + + bookTypes = Object.keys(this.bookType).filter( + (key) => typeof this.bookType[key] === 'number' + ); + + isModalOpen = false; + + constructor( + public readonly list: ListService, + private bookService: BookService, + private fb: FormBuilder + ) {} + + ngOnInit() { + const bookStreamCreator = (query) => this.bookService.getListByInput(query); + + this.list.hookToQuery(bookStreamCreator).subscribe((response) => { + this.book = response; + }); + } + + createBook() { + this.buildForm(); + this.isModalOpen = true; + } + + buildForm() { + this.form = this.fb.group({ + name: ['', Validators.required], + type: [null, Validators.required], + publishDate: [null, Validators.required], + price: [null, Validators.required], + }); + } + + save() { + if (this.form.invalid) { + return; + } + + this.bookService.createByInput(this.form.value).subscribe(() => { + this.isModalOpen = false; + this.form.reset(); + this.list.get(); + }); + } +} +``` + +* 导入了 `NgbDateNativeAdapter` 和 `NgbDateAdapter`. +* 我们添加了一个新的 `NgbDateAdapter` 提供程序,它将Datepicker值转换为 `Date` 类型. 有关更多详细信息,请参见[datepicker adapters](https://ng-bootstrap.github.io/#/components/datepicker/overview). + +现在你可以打开浏览器看到以下变化: + +![Save button to the modal](./images/bookstore-new-book-form-v2.png) + +## 更新书籍 + +打开 `/src/app/book/book.component.ts` 使用以下内容替换: + +```js +import { ListService, PagedResultDto } from '@abp/ng.core'; +import { Component, OnInit } from '@angular/core'; +import { BookDto, BookType, CreateUpdateBookDto } from './models'; +import { BookService } from './services'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + selector: 'app-book', + templateUrl: './book.component.html', + styleUrls: ['./book.component.scss'], + providers: [ListService, { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], +}) +export class BookComponent implements OnInit { + book = { items: [], totalCount: 0 } as PagedResultDto; + + selectedBook = new BookDto(); // declare selectedBook + + form: FormGroup; + + bookType = BookType; + + bookTypes = Object.keys(this.bookType).filter( + (key) => typeof this.bookType[key] === 'number' + ); + + isModalOpen = false; + + constructor( + public readonly list: ListService, + private bookService: BookService, + private fb: FormBuilder + ) {} + + ngOnInit() { + const bookStreamCreator = (query) => this.bookService.getListByInput(query); + + this.list.hookToQuery(bookStreamCreator).subscribe((response) => { + this.book = response; + }); + } + + createBook() { + this.selectedBook = new BookDto(); // reset the selected book + this.buildForm(); + this.isModalOpen = true; + } + + // Add editBook method + editBook(id: string) { + this.bookService.getById(id).subscribe((book) => { + this.selectedBook = book; + this.buildForm(); + this.isModalOpen = true; + }); + } + + buildForm() { + this.form = this.fb.group({ + name: [this.selectedBook.name || '', Validators.required], + type: [this.selectedBook.type || null, Validators.required], + publishDate: [ + this.selectedBook.publishDate ? new Date(this.selectedBook.publishDate) : null, + Validators.required, + ], + price: [this.selectedBook.price || null, Validators.required], + }); + } + + // change the save method + save() { + if (this.form.invalid) { + return; + } + + const request = this.selectedBook.id + ? this.bookService.updateByIdAndInput(this.form.value, this.selectedBook.id) + : this.bookService.createByInput(this.form.value); + + request.subscribe(() => { + this.isModalOpen = false; + this.form.reset(); + this.list.get(); + }); + } +} +``` + +* 我们声明了类型为 `BookDto` 的 `selectedBook` 变量. +* 我们添加了 `editBook` 方法, 根据给定图书 `Id` 设置 `selectedBook` 对象. +* 我们替换了 `buildForm` 方法使用 `selectedBook` 数据创建表单. +* 我们替换了 `createBook` 方法,设置 `selectedBook` 为空对象. +* 我们替换了 `save` 方法. + +### 添加 "Actions" 下拉框到表格 + +打开 `/src/app/book/book.component.html` 在 `ngx-datatable` 第一列添加 `ngx-datatable-column` 定义: + +```html + + +
    + +
    + +
    +
    +
    +
    +``` + +在表格的第一列添加了一个 "Actions" 下拉菜单,如下图所示: + +![Action buttons](./images/bookstore-actions-buttons.png) + +同时如下所示更改 `ng-template #abpHeader` 部分: + +```html + +

    {%{{{ (selectedBook.id ? '::Edit' : '::NewBook' ) | abpLocalization }}}%}

    +
    +``` + +模板将在标题中显示 **Edit** 文本用于编辑记录操作, **New Book** 用于添加新记录操作. + +## 删除书籍 + +打开 `/src/app/book/book.component.ts` 注入 `ConfirmationService`. + +所示替换构造函数: + +```js +// ... + +// add new imports +import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared'; + +//change the constructor +constructor( + public readonly list: ListService, + private bookService: BookService, + private fb: FormBuilder, + private confirmation: ConfirmationService // inject the ConfirmationService +) {} + +// Add a delete method +delete(id: string) { + this.confirmation.warn('::AreYouSureToDelete', '::AreYouSure').subscribe((status) => { + if (status === Confirmation.Status.confirm) { + this.bookService.deleteById(id).subscribe(() => this.list.get()); + } + }); +} +``` + +* 我们注入了 `ConfirmationService`. +* 我们注入了 `ConfirmationService` 到构造函数. +* 添加了 `delete` 方法. + +> 参阅[确认弹层文档](../UI/Angular/Confirmation-Service)了解该服务的更多信息. + +### 添加删除按钮: + +打开 `/src/app/book/book.component.html` 修改 `ngbDropdownMenu` 添加删除按钮: + +```html +
    + + +
    +``` + +最终操作下拉框UI看起来如下: + +![bookstore-final-actions-dropdown](./images/bookstore-final-actions-dropdown.png) + +点击 `delete` 动作调用 `delete` 方法,然后无法显示一个确认弹层如下图所示. + +![bookstore-confirmation-popup](./images/bookstore-confirmation-popup.png) -打开**测试资源管理器**(测试 -> Windows -> 测试资源管理器)并**执行**所有测试: +{{end}} -![bookstore-appservice-tests](./images/bookstore-appservice-tests.png) +## 下一章 -恭喜, 绿色图标表示测试已成功通过! +查看本教程的[下一章](Part-4.md). diff --git a/docs/zh-Hans/Tutorials/Part-4.md b/docs/zh-Hans/Tutorials/Part-4.md new file mode 100644 index 0000000000..151c7aab90 --- /dev/null +++ b/docs/zh-Hans/Tutorials/Part-4.md @@ -0,0 +1,255 @@ +# Web应用程序开发教程 - 第三章: 集成测试 +````json +//[doc-params] +{ + "UI": ["MVC","NG"], + "DB": ["EF","Mongo"] +} +```` +{{ +if UI == "MVC" + UI_Text="mvc" +else if UI == "NG" + UI_Text="angular" +else + UI_Text="?" +end +if DB == "EF" + DB_Text="Entity Framework Core" +else if DB == "Mongo" + DB_Text="MongoDB" +else + DB_Text="?" +end +}} + +## 关于本教程 + +在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的: + +* **{{DB_Text}}** 做为ORM提供程序. +* **{{UI_Value}}** 做为UI框架. + +本教程分为以下部分: + +- [Part 1: 创建服务端](Part-1.md) +- [Part 2: 图书列表页面](Part-2.md) +- [Part 3: 创建,更新和删除图书](Part-3.md) +- **Part 4: 集成测试**(本章) +- [Part 5: 授权](Part-5.md) +- [Part 6: 作者: 领域层](Part-6.md) +- [Part 7: 作者: 数据库集成](Part-7.md) +- [Part 8: 作者: 应用服务层](Part-8.md) +- [Part 9: 作者: 用户页面](Part-9.md) +- [Part 10: 图书到作者的关系](Part-10.md) + +## 下载源码 + +本教程根据你的**UI** 和 **Database**偏好有多个版,我们准备了两种可供下载的源码组合: + +* [MVC (Razor Pages) UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) +* [Angular UI 与 MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) + +## 解决方案中的测试项目 + +这一部分涵盖了 **服务器端** 测试. 解决方案中有多个测试项目: + +![bookstore-test-projects-v2](./images/bookstore-test-projects-{{UI_Text}}.png) + +每个项目用于测试相关的应用程序项目.测试项目使用以下库进行测试: + +* [xunit](https://xunit.github.io/) 作为主测试框架. +* [Shoudly](http://shouldly.readthedocs.io/en/latest/) 作为断言库. +* [NSubstitute](http://nsubstitute.github.io/) 作为模拟库. + +{{if DB=="EF"}} + +> 测试项目配置为使用 **SQLite内存** 作为数据库. 创建一个单独的数据库实例并使用数据种子系统进行初始化种子数据,为每个测试准备一个新的数据库. + +{{else if DB=="Mongo"}} + +> **[Mongo2Go](https://github.com/Mongo2Go/Mongo2Go)**库用于模拟MongoDB数据库. 创建一个单独的数据库实例并使用数据种子系统进行初始化种子数据,为每个测试准备一个新的数据库. + +{{end}} + +## 添加测试数据 + +如果你已经按照[第一部分](Part-1.md)中的描述创建了数据种子贡献者,则相同的数据也在测试中可用. 因此你可以跳过此部分. 如果你尚未创建种子贡献者,可以使用 `BookStoreTestDataSeedContributor` 来为要在以下测试中使用的相同数据提供种子. + +## 测试 BookAppService + +在 `Acme.BookStore.Application.Tests` 项目中创建一个名叫 `BookAppService_Tests` 的测试类: + +````csharp +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Application.Dtos; +using Xunit; + +namespace Acme.BookStore.Books +{ {{if DB=="Mongo"}} + [Collection(BookStoreTestConsts.CollectionDefinitionName)]{{end}} + public class BookAppService_Tests : BookStoreApplicationTestBase + { + private readonly IBookAppService _bookAppService; + + public BookAppService_Tests() + { + _bookAppService = GetRequiredService(); + } + + [Fact] + public async Task Should_Get_List_Of_Books() + { + //Act + var result = await _bookAppService.GetListAsync( + new PagedAndSortedResultRequestDto() + ); + + //Assert + result.TotalCount.ShouldBeGreaterThan(0); + result.Items.ShouldContain(b => b.Name == "1984"); + } + } +} +```` + +* 测试方法 `Should_Get_List_Of_Books` 直接使用 `BookAppService.GetListAsync` 方法来获取用户列表,并执行检查. +* 我们可以安全地检查 "1984" 这本书的名称,因为我们知道这本书可以在数据库中找到,我们已将其添加到种子数据中. + +新增测试方法,用以测试创建一个**合法**book实体的场景: + +````csharp +[Fact] +public async Task Should_Create_A_Valid_Book() +{ + //Act + var result = await _bookAppService.CreateAsync( + new CreateUpdateBookDto + { + Name = "New test book 42", + Price = 10, + PublishDate = System.DateTime.Now, + Type = BookType.ScienceFiction + } + ); + + //Assert + result.Id.ShouldNotBe(Guid.Empty); + result.Name.ShouldBe("New test book 42"); +} +```` + +新增测试方法,用以测试创建一个非法book实体失败的场景: + +````csharp +[Fact] +public async Task Should_Not_Create_A_Book_Without_Name() +{ + var exception = await Assert.ThrowsAsync(async () => + { + await _bookAppService.CreateAsync( + new CreateUpdateBookDto + { + Name = "", + Price = 10, + PublishDate = DateTime.Now, + Type = BookType.ScienceFiction + } + ); + }); + + exception.ValidationErrors + .ShouldContain(err => err.MemberNames.Any(mem => mem == "Name")); +} +```` + +* 由于 `Name` 是空值, ABP 抛出一个 `AbpValidationException` 异常. + +最终的测试类如下所示: + +````csharp +using System; +using System.Linq; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Validation; +using Xunit; + +namespace Acme.BookStore.Books +{ {{if DB=="Mongo"}} + [Collection(BookStoreTestConsts.CollectionDefinitionName)]{{end}} + public class BookAppService_Tests : BookStoreApplicationTestBase + { + private readonly IBookAppService _bookAppService; + + public BookAppService_Tests() + { + _bookAppService = GetRequiredService(); + } + + [Fact] + public async Task Should_Get_List_Of_Books() + { + //Act + var result = await _bookAppService.GetListAsync( + new PagedAndSortedResultRequestDto() + ); + + //Assert + result.TotalCount.ShouldBeGreaterThan(0); + result.Items.ShouldContain(b => b.Name == "1984"); + } + + [Fact] + public async Task Should_Create_A_Valid_Book() + { + //Act + var result = await _bookAppService.CreateAsync( + new CreateUpdateBookDto + { + Name = "New test book 42", + Price = 10, + PublishDate = System.DateTime.Now, + Type = BookType.ScienceFiction + } + ); + + //Assert + result.Id.ShouldNotBe(Guid.Empty); + result.Name.ShouldBe("New test book 42"); + } + + [Fact] + public async Task Should_Not_Create_A_Book_Without_Name() + { + var exception = await Assert.ThrowsAsync(async () => + { + await _bookAppService.CreateAsync( + new CreateUpdateBookDto + { + Name = "", + Price = 10, + PublishDate = DateTime.Now, + Type = BookType.ScienceFiction + } + ); + }); + + exception.ValidationErrors + .ShouldContain(err => err.MemberNames.Any(mem => mem == "Name")); + } + } +} +```` + +打开**测试资源管理器**(测试 -> Windows -> 测试资源管理器)并**执行**所有测试: + +![bookstore-appservice-tests](./images/bookstore-appservice-tests.png) + +恭喜你, **绿色图标**表示测试已成功通过! + +## 下一章 + +查看本教程的[下一章](Part-5.md). \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/Part-5.md b/docs/zh-Hans/Tutorials/Part-5.md new file mode 100644 index 0000000000..602306c484 --- /dev/null +++ b/docs/zh-Hans/Tutorials/Part-5.md @@ -0,0 +1 @@ +TODO.. \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/Part-6.md b/docs/zh-Hans/Tutorials/Part-6.md new file mode 100644 index 0000000000..602306c484 --- /dev/null +++ b/docs/zh-Hans/Tutorials/Part-6.md @@ -0,0 +1 @@ +TODO.. \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/Part-7.md b/docs/zh-Hans/Tutorials/Part-7.md new file mode 100644 index 0000000000..602306c484 --- /dev/null +++ b/docs/zh-Hans/Tutorials/Part-7.md @@ -0,0 +1 @@ +TODO.. \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/Part-8.md b/docs/zh-Hans/Tutorials/Part-8.md new file mode 100644 index 0000000000..602306c484 --- /dev/null +++ b/docs/zh-Hans/Tutorials/Part-8.md @@ -0,0 +1 @@ +TODO.. \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/Part-9.md b/docs/zh-Hans/Tutorials/Part-9.md new file mode 100644 index 0000000000..602306c484 --- /dev/null +++ b/docs/zh-Hans/Tutorials/Part-9.md @@ -0,0 +1 @@ +TODO.. \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/images/bookstore-add-migration-authors.png b/docs/zh-Hans/Tutorials/images/bookstore-add-migration-authors.png new file mode 100644 index 0000000000..c24e33c628 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-add-migration-authors.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-added-author-to-book-list-angular.png b/docs/zh-Hans/Tutorials/images/bookstore-added-author-to-book-list-angular.png new file mode 100644 index 0000000000..9fba94d5bf Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-added-author-to-book-list-angular.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-added-author-to-book-list.png b/docs/zh-Hans/Tutorials/images/bookstore-added-author-to-book-list.png new file mode 100644 index 0000000000..f318ddfd31 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-added-author-to-book-list.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-added-authors-to-modals.png b/docs/zh-Hans/Tutorials/images/bookstore-added-authors-to-modals.png new file mode 100644 index 0000000000..5fe4046794 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-added-authors-to-modals.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-angular-authors-page.png b/docs/zh-Hans/Tutorials/images/bookstore-angular-authors-page.png new file mode 100644 index 0000000000..bbd865e44b Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-angular-authors-page.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-angular-service-proxy-author.png b/docs/zh-Hans/Tutorials/images/bookstore-angular-service-proxy-author.png new file mode 100644 index 0000000000..ac231ef7a8 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-angular-service-proxy-author.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-author-domain-layer.png b/docs/zh-Hans/Tutorials/images/bookstore-author-domain-layer.png new file mode 100644 index 0000000000..fde53834bb Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-author-domain-layer.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-author-permissions.png b/docs/zh-Hans/Tutorials/images/bookstore-author-permissions.png new file mode 100644 index 0000000000..a20093d2c4 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-author-permissions.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-authors-page.png b/docs/zh-Hans/Tutorials/images/bookstore-authors-page.png new file mode 100644 index 0000000000..6b8959df54 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-authors-page.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-book-and-booktype.png b/docs/zh-Hans/Tutorials/images/bookstore-book-and-booktype.png new file mode 100644 index 0000000000..1620ebf150 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-book-and-booktype.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-book-list-3.png b/docs/zh-Hans/Tutorials/images/bookstore-book-list-3.png new file mode 100644 index 0000000000..cbd7ec4a6a Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-book-list-3.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-book-list.png b/docs/zh-Hans/Tutorials/images/bookstore-book-list.png index ecdb87c737..4982032e64 100644 Binary files a/docs/zh-Hans/Tutorials/images/bookstore-book-list.png and b/docs/zh-Hans/Tutorials/images/bookstore-book-list.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-dbmigrator-on-solution.png b/docs/zh-Hans/Tutorials/images/bookstore-dbmigrator-on-solution.png new file mode 100644 index 0000000000..0b81900307 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-dbmigrator-on-solution.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-edit-button-2.png b/docs/zh-Hans/Tutorials/images/bookstore-edit-button-2.png new file mode 100644 index 0000000000..507bdd9406 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-edit-button-2.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-edit-delete-actions.png b/docs/zh-Hans/Tutorials/images/bookstore-edit-delete-actions.png new file mode 100644 index 0000000000..9c275c6d15 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-edit-delete-actions.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-getlist-result-network.png b/docs/zh-Hans/Tutorials/images/bookstore-getlist-result-network.png new file mode 100644 index 0000000000..141f9d14ee Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-getlist-result-network.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-index-js-file-v3.png b/docs/zh-Hans/Tutorials/images/bookstore-index-js-file-v3.png new file mode 100644 index 0000000000..add95788a8 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-index-js-file-v3.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-javascript-proxy-console.png b/docs/zh-Hans/Tutorials/images/bookstore-javascript-proxy-console.png new file mode 100644 index 0000000000..f393c47875 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-javascript-proxy-console.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-new-author-modal.png b/docs/zh-Hans/Tutorials/images/bookstore-new-author-modal.png new file mode 100644 index 0000000000..dda0dab4c1 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-new-author-modal.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-new-book-button-2.png b/docs/zh-Hans/Tutorials/images/bookstore-new-book-button-2.png new file mode 100644 index 0000000000..12f1819688 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-new-book-button-2.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-new-book-button-small.png b/docs/zh-Hans/Tutorials/images/bookstore-new-book-button-small.png new file mode 100644 index 0000000000..13f7da9285 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-new-book-button-small.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-permissions-ui.png b/docs/zh-Hans/Tutorials/images/bookstore-permissions-ui.png new file mode 100644 index 0000000000..749f7a013f Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-permissions-ui.png differ diff --git a/docs/zh-Hans/Tutorials/images/generated-proxies-2.png b/docs/zh-Hans/Tutorials/images/generated-proxies-2.png new file mode 100644 index 0000000000..adad417c3d Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/generated-proxies-2.png differ diff --git a/docs/zh-Hans/UI/Angular/Custom-Setting-Page.md b/docs/zh-Hans/UI/Angular/Custom-Setting-Page.md index f7854ac6bf..2b888e8d75 100644 --- a/docs/zh-Hans/UI/Angular/Custom-Setting-Page.md +++ b/docs/zh-Hans/UI/Angular/Custom-Setting-Page.md @@ -2,38 +2,33 @@ 不同的模块提供它们的设置选项卡. 你可以通过3个步骤在项目中自定义设置页面. -1. 创建一个组件 +1. 使用以下命令创建一个组件 -```js -import { Select } from '@ngxs/store'; -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-your-custom-settings', - template: ` - custom-settings works! - `, -}) -export class YourCustomSettingsComponent { - // Your component logic -} +```bash +yarn ng generate component my-settings ``` -2. 添加 `YourCustomSettingsComponent` 到 `AppModule` 中的 `declarations`和 `entryComponents` 数组中. - -3. 打开 `app.component.ts` 在 `ngOnInit` 添加以下内容: +2. 打开 `app.component.ts` 做以下修改: ```js -import { addSettingTab } from '@abp/ng.theme.shared'; -// ... - -ngOnInit() { - addSettingTab({ - component: YourCustomSettingsComponent, - name: 'Type here the setting tab title (you can type a localization key, e.g: AbpAccount::Login', - order: 4, - requiredPolicy: 'type here a policy key' - }); +import { Component } from '@angular/core'; +import { SettingTabsService } from '@abp/ng.core'; // imported SettingTabsService +import { MySettingsComponent } from './my-settings/my-settings.component'; // imported MySettingsComponent + +@Component(/* component metadata */) +export class AppComponent { + constructor(private settingTabs: SettingTabsService) // injected MySettingsComponent + { + // added below + settingTabs.add([ + { + name: 'MySettings', + order: 1, + requiredPolicy: 'policy key here', + component: MySettingsComponent, + }, + ]); + } } ``` diff --git a/docs/zh-Hans/UI/Angular/Localization.md b/docs/zh-Hans/UI/Angular/Localization.md index 60af06a976..9ddb414234 100644 --- a/docs/zh-Hans/UI/Angular/Localization.md +++ b/docs/zh-Hans/UI/Angular/Localization.md @@ -146,16 +146,6 @@ this.store.selectSnapshot( "build": { "options": { "styles": [ - { - "input": "node_modules/@abp/ng.theme.shared/styles/bootstrap-rtl.min.css", - "inject": false, - "bundleName": "bootstrap-rtl.min" - }, - { - "input": "node_modules/bootstrap/dist/css/bootstrap.min.css", - "inject": true, - "bundleName": "bootstrap-ltr.min" - }, { "input": "node_modules/@fortawesome/fontawesome-free/css/all.min.css", "inject": true, @@ -166,6 +156,16 @@ this.store.selectSnapshot( "inject": true, "bundleName": "fontawesome-v4-shims.min" }, + { + "input": "node_modules/@abp/ng.theme.shared/styles/bootstrap-rtl.min.css", + "inject": false, + "bundleName": "bootstrap-rtl.min" + }, + { + "input": "node_modules/bootstrap/dist/css/bootstrap.min.css", + "inject": true, + "bundleName": "bootstrap-ltr.min" + }, "apps/dev-app/src/styles.scss" ], } @@ -174,6 +174,7 @@ this.store.selectSnapshot( } } } +``` #### 步骤 2. 清除AppComponent中延迟加载的Fontawesome @@ -192,6 +193,32 @@ import { Component } from '@angular/core'; export class AppComponent {} ``` +## 文化名称到语言环境文件名的映射 + +.NET中定义的某些文化名称与Angular语言环境不匹配. 在这种情况下Angular应用程序在运行时会引发如下错误: + +![locale-error](./images/locale-error.png) + +如果你看到这样的错误,你应该像下面这样传递 `cultureNameLocaleFileMap` 属性到CoreModule的forRoot静态方法. + +```js +// app.module.ts + +@NgModule({ + imports: [ + // other imports + CoreModule.forRoot({ + // other options + cultureNameLocaleFileMap: { + "DotnetCultureName": "AngularLocaleFileName", + "pt-BR": "pt" // example + } + }) + //... +``` + +查看 [Angular中所有的语言环境文件](https://github.com/angular/angular/tree/master/packages/common/locales). + ## 另请参阅 * [ASP.NET Core中的本地化](../../Localization.md) diff --git a/docs/zh-Hans/UI/Angular/Migration-Guide-v3.md b/docs/zh-Hans/UI/Angular/Migration-Guide-v3.md index 36afffa54f..eb16c21e03 100644 --- a/docs/zh-Hans/UI/Angular/Migration-Guide-v3.md +++ b/docs/zh-Hans/UI/Angular/Migration-Guide-v3.md @@ -18,7 +18,7 @@ yarn ng update @angular/cli @angular/core --force - 更新你的package.json并安装新的软件包 - 修改tsconfig.json文件创建一个"Solution Style"配置 -- 重命名 `browserlist` 为 `.browserlistrc` +- 重命名 `browserslist` 为 `.browserslistrc` 另一方面,如果你单独使用 `yarn ng update` 命令检查首先要更新哪些包会更好. Angular会给你一个要更新的包列表. @@ -144,42 +144,33 @@ export class AppModule {} AppRoutingModule: ```js -import { DynamicLayoutComponent } from '@abp/ng.core'; import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; const routes: Routes = [ { path: '', - component: DynamicLayoutComponent, - children: [ - { - path: '', - pathMatch: 'full', - loadChildren: () => import('./home/home.module') - .then(m => m.HomeModule), - }, - { - path: 'account', - loadChildren: () => import('@abp/ng.account') - .then(m => m.AccountModule.forLazy({ redirectUrl: '/' })), - }, - { - path: 'identity', - loadChildren: () => import('@abp/ng.identity') - .then(m => m.IdentityModule.forLazy()), - }, - { - path: 'tenant-management', - loadChildren: () => import('@abp/ng.tenant-management') - .then(m => m.TenantManagementModule.forLazy()), - }, - { - path: 'setting-management', - loadChildren: () => import('@abp/ng.setting-management') - .then(m => m.SettingManagementModule.forLazy()), - }, - ], + pathMatch: 'full', + loadChildren: () => import('./home/home.module').then(m => m.HomeModule), + }, + { + path: 'account', + loadChildren: () => + import('@abp/ng.account').then(m => m.AccountModule.forLazy({ redirectUrl: '/' })), + }, + { + path: 'identity', + loadChildren: () => import('@abp/ng.identity').then(m => m.IdentityModule.forLazy()), + }, + { + path: 'tenant-management', + loadChildren: () => + import('@abp/ng.tenant-management').then(m => m.TenantManagementModule.forLazy()), + }, + { + path: 'setting-management', + loadChildren: () => + import('@abp/ng.setting-management').then(m => m.SettingManagementModule.forLazy()), }, ]; @@ -190,7 +181,7 @@ const routes: Routes = [ export class AppRoutingModule {} ``` -> 你可能已经注意到我们在top级别路由组件上使用了 `DynamicLayoutComponent`. 我们这样做是为了避免不必要的渲染和闪烁. 这不是强制的,但是我们建议在你的应用程序路由中做同样的事情. +> 你可能已经注意到我们在AppComponent模板中使用了 `` 而不是 ``. 我们这样做是为了避免不必要的渲染和闪烁. 这不是强制的,但是我们建议在你的应用程序路由中做同样的事情. #### 如何迁移? @@ -200,7 +191,7 @@ export class AppRoutingModule {} - 调用 `ThemeBasicModule` 的静态 `forRoot` 方法(或商业上的 `ThemeLeptonModule`),并从导入中删除 `SharedModule`(除非已在其中添加了根模块所需的任何内容). - 在app路由模块中直接导入延迟ABP模块 (如 `() => import('@abp/ng.identity').then(...)`). - 在所有延迟模块 `then` 中调用的静态 `forLazy` 方法,即使配置没有被传递. -- [可选]使用 `DynamicLayoutComponent` 添加空的父路由,获得更好的性能和UX. +- [可选]添加 `` 到AppComponent模板并且删除 ``,获得更好的性能和UX. ### RoutesService @@ -233,9 +224,10 @@ export class AppRoutingModule {} 从ABP v3开始,我们已切换到经过严格测试,执行良好的数据表格:[ngx-datatable](https://github.com/swimlane/ngx-datatable). 所有的ABP模块都已经实现了ngx-datatable. `ThemeSharedModule` 已经导出了 `NgxDatatableModule`. 因此如果你在终端运行 `yarn add @swimlane/ngx-datatable` 来安装这个包,它将在你的应用的所有模块中可用. -为了正确设置样式,你需要在angular.json文件的样式部分中添加以下内容: +为了正确设置样式,你需要在angular.json文件的样式部分中添加以下内容(在其他所有元素之上): ```json +"styles": [ { "input": "node_modules/@swimlane/ngx-datatable/index.css", "inject": true, @@ -250,7 +242,9 @@ export class AppRoutingModule {} "input": "node_modules/@swimlane/ngx-datatable/themes/material.css", "inject": true, "bundleName": "ngx-datatable-material" - } + }, + // other styles +] ``` 由于尚未删除 `abp-table`, 因此以前由ABP v2.x构建的模块不会突然丢失所有. 但是它们的外观与内置ABP v3模块有所不同, 因此你可能希望将这些模块中的表转换为ngx-datatable. 为了减少将abp-table转换为ngx-datatable所需的工作量,我们修改了 `ListService` 以使其与 `ngx-datatable` 一起很好地工作,并引入了两个新指令: `NgxDatatableListDirective` 和 `NgxDatatableDefaultDirective`. @@ -348,7 +342,103 @@ export class SomeComponent { - 如果可以的话,根据上面的例子更新你的模. - 如果你稍后需要这样做,并且打算保留abp-table一段时间,请确保根据此处描述的[破坏性更改](List-Service.md)更新分页. -**重要说明:**abp-table没有被删除,但已被弃用并在以后的版本中删除. 请考虑切换到ngx-datatable。 +**重要说明:**abp-table没有被删除,但已被弃用并在以后的版本中删除. 请考虑切换到ngx-datatable. + +### 扩展系统[商业版] + +扩展程序系统现在是开源的, 可以从 `@abp/ng.theme.shared/extensions` 而不是从 `@volo/abp.commercial.ng.ui` 中获取. 同样,根据config软件包的新结构,如上所述通过 `forLazy` 静态方法进行配置. + +#### 如何迁移? + +如果你以前从未使用过扩展系统,则无需执行任何操作. 否则请再次检查文档以查看更改. 扩展系统本身的工作原理与以前相同,唯一的变化是你从中导入的包,静态方法以及您将贡献者传递给的模块. + + +### Lepton 主题Logo [商业版] + +在ABP v2.x中,Lepton每个颜色主题都有一个亮徽标和一个暗徽标. 我们意识到我们可以使它仅使用一个浅色和一个深色徽标. 因此我们更改了Lepton查找徽标图像的方式,现在你只需要在项目中包含 `logo-light.png` 和 `logo-dark.png`. + +#### 如何迁移? + +如果你之前已切换模板徽标PNG,则更改很简单: + +- 转到 `/assets/images/logo` 目录. +- 重命名 `theme1.png` 为 `logo-light.png` 并且重命名 `theme1-reverse.png` 为 `logo-dark.png`. +- 删除所有其他 `theme*.png` 文件. + +如果你更换了徽标组件,则更改有些不同,但仍然很简单. `LayoutStateService` 有两个新成员: `primaryLogoColor` 和 `secondaryLogoColor`. 它们有 `'light'` 和 `'dark'` 设置值做为可观察流. 你可以使用 `async` 管道在自定义徽标组件模板中使用它们的值. 这是一个完整的示例,其中涵盖了主要和辅助(帐户)布局徽标. + +```js +import { AddReplaceableComponent } from '@abp/ng.core'; +import { CommonModule } from '@angular/common'; +import { APP_INITIALIZER, Component, Injector, NgModule } from '@angular/core'; +import { Store } from '@ngxs/store'; +import { eAccountComponents } from '@volo/abp.ng.account'; +import { + AccountLayoutComponent, + eThemeLeptonComponents, + LayoutStateService, +} from '@volo/abp.ng.theme.lepton'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +@Component({ + template: ` + + + + + + `, +}) +export class LogoComponent { + isAccount: boolean; + + logoColor: Observable<'dark' | 'light'>; + + get logoUrl() { + return this.logoColor.pipe(map(color => `url(/assets/images/logo/logo-${color}.png)`)); + } + + constructor(injector: Injector) { + const layout = injector.get(LayoutStateService); + this.isAccount = Boolean(injector.get(AccountLayoutComponent, false)); + this.logoColor = this.isAccount ? layout.secondaryLogoColor : layout.primaryLogoColor; + } +} + +@NgModule({ + imports: [CommonModule], + declarations: [LogoComponent], + exports: [LogoComponent], +}) +export class LogoModule {} + +export const APP_LOGO_PROVIDER = [ + { provide: APP_INITIALIZER, useFactory: switchLogos, multi: true, deps: [Store] }, +]; + +export function switchLogos(store: Store) { + return () => { + store.dispatch( + new AddReplaceableComponent({ + component: LogoComponent, + key: eThemeLeptonComponents.Logo, + }), + ); + + store.dispatch( + new AddReplaceableComponent({ + component: LogoComponent, + key: eAccountComponents.Logo, + }), + ); + }; +} +``` + +只要将 `APP_LOGO_PROVIDER` 添加到根模块的提供程序(通常是 `AppModule` ),你就会有一个调整主题颜色的自定义徽标组件. ### 过时的接口 diff --git a/docs/zh-Hans/UI/Angular/Modifying-the-Menu.md b/docs/zh-Hans/UI/Angular/Modifying-the-Menu.md index 1f4e626bd3..82c1fe41ad 100644 --- a/docs/zh-Hans/UI/Angular/Modifying-the-Menu.md +++ b/docs/zh-Hans/UI/Angular/Modifying-the-Menu.md @@ -2,8 +2,6 @@ 菜单在 @abp/ng.theme.basic包 `ApplicationLayoutComponent` 内部. 有几种修改菜单的方法,本文档介绍了这些方法. 如果你想完全替换菜单,请参考[组件替换文档]了解如何替换布局. - - ## 如何添加Logo 环境变量中的 `logoUrl` 是logo的url. @@ -212,51 +210,65 @@ this.routes.remove(['Your navigation']); ## 如何在菜单的右侧添加元素 -右侧的元素存储在 @abp/ng.theme.basic 包的 `LayoutState` 中. - -`LayoutStateService` 的 `dispatchAddNavigationElement` 方法添加元素到右侧的菜单. - -你可以通过将模板添加到 `app.component` 调用 `dispatchAddNavigationElement` 方法来插入元素: +你可以通过调用 `NavItemsService` 的 `addItems` 方法将元素添加到菜单的右侧. 这是一个单例服务,即以根身份提供. 因此你可以立即注入并使用它. ```js -import { Layout, LayoutStateService } from '@abp/ng.theme.basic'; // added this line +import { NavItemsService } from '@abp/ng.theme.shared'; +import { Component } from '@angular/core'; @Component({ - selector: 'app-root', template: ` - - - + `, }) -export class AppComponent { - // Added ViewChild - @ViewChild('search', { static: false, read: TemplateRef }) searchElementRef: TemplateRef; - - constructor(private layout: LayoutStateService) {} // injected LayoutStateService +export class MySearchInputComponent {} - // Added ngAfterViewInit - ngAfterViewInit() { - const newElement = { - name: 'Search', - element: this.searchElementRef, - order: 1, - } as Layout.NavigationElement; - this.layout.dispatchAddNavigationElement(newElement); +@Component(/* component metadata */) +export class AppComponent { + constructor(private navItems: NavItemsService) { + navItems.addItems([ + { + id: 'MySearchInput', + order: 1, + component: MySearchInputComponent, + }, + { + id: 'SignOutIcon', + html: '', + action: () => console.log('Clicked the sign out icon'), + order: 101, // puts as last element + }, + ]); } } ``` -上面我们在菜单添加了一个搜索输入,最终UI如下: +上面我们在菜单添加了一个搜索输入和退出登录图标,最终UI如下: ![navigation-menu-search-input](./images/navigation-menu-search-input.png) -## 如何删除右侧菜单元素 +> 默认元素的排序为 `100`. 如果要将自定义元素放在默认值之前,请指定一个排序,最高为 `99`. 如果要将自定义元素放在默认值之后,请指定排序从 `101` 开始. 最后如果必须在默认值之间放置一个项目,请按如下所述修补默认元素顺序. 但有一个警告:我们将来可能会添加另一个默认元素,排序也为 `100`. + +## 如何修补或删除右侧部分元素 + +`NavItemsService` 的 `patchItem` 方法通过 `id` 查找元素,并将配置替换为第二个参数传递的新配置. `removeItem` 方法会找到一个元素并删除. + +```js +export class AppComponent { + constructor(private navItems: NavItemsService) { + navItems.patchItem(eThemeBasicComponents.Languages, { + requiredPolicy: 'new policy here', + order: 1, + }); + + navItems.removeItem(eThemeBasicComponents.CurrentUser); + } +} +``` -TODO +* 使用新的 `requiredPolicy` 和新的 `order` 修补了语言下拉菜单元素. +* 删除了当前用户的下拉菜单元素. ## 下一步是什么? diff --git a/docs/zh-Hans/UI/Angular/Permission-Management.md b/docs/zh-Hans/UI/Angular/Permission-Management.md index 5c1ba5b4fb..7f28035105 100644 --- a/docs/zh-Hans/UI/Angular/Permission-Management.md +++ b/docs/zh-Hans/UI/Angular/Permission-Management.md @@ -55,7 +55,9 @@ export class YourComponent { 如果你想要在导航过程中控制经过身份验证的用户对路由的访问权限,可以使用 `PermissionGuard`. -将 `requiredPolicy` 添加到路由模块中的 `routes`属性. +* 从@abp/ng.core导入PermissionGuard. +* 添加 `canActivate: [PermissionGuard]` 到你的路由对象. +* 添加 `requiredPolicy` 到路由模块路由的 `data` 属性. ```js import { PermissionGuard } from '@abp/ng.core'; @@ -66,9 +68,7 @@ const routes: Routes = [ component: YourComponent, canActivate: [PermissionGuard], data: { - routes: { - requiredPolicy: 'AbpIdentity.Roles.Create', - }, + requiredPolicy: 'YourProjectName.YourComponent', // policy key for your component }, }, ]; diff --git a/docs/zh-Hans/UI/Angular/Service-Proxies.md b/docs/zh-Hans/UI/Angular/Service-Proxies.md index 29aafebdae..2824bc44a7 100644 --- a/docs/zh-Hans/UI/Angular/Service-Proxies.md +++ b/docs/zh-Hans/UI/Angular/Service-Proxies.md @@ -33,7 +33,7 @@ abp generate-proxy 服务的 `providerIn` 属性定义为 `'root'`. 因此无需将服务作为提供程序添加到模块. 你可以通过将服务注入到构造函数中来使用它,如下所示: ```js -import { AbpApplicationConfigurationService } from '../app/shared/services'; +import { AbpApplicationConfigurationService } from '../abp/applicationconfiguration/services'; //... export class HomeComponent{ @@ -49,14 +49,14 @@ Angular编译器会从最终输出中删除那些没有被注入的服务. 参 ### Models -生成的模型与后端中的dto匹配. 每个模型在 `src/app/*/shared/models` 文件夹生成一个类. +生成的模型与后端中的dto匹配. 每个模型在 `src/app/*/models` 文件夹生成一个类. `@abp/ng.core` 包有一些[基类](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/dtos.ts). 一些模型扩展了这些类. 可以如下所示创建一个类的实例: ```js -import { IdentityRoleCreateDto } from '../identity/shared/models'; +import { IdentityRoleCreateDto } from '../identity/role/models' //... const instance = new IdentityRoleCreateDto({name: 'Role 1', isDefault: false, isPublic: true}) ``` diff --git a/docs/zh-Hans/UI/Angular/Subscription-Service.md b/docs/zh-Hans/UI/Angular/Subscription-Service.md new file mode 100644 index 0000000000..fb248d4d95 --- /dev/null +++ b/docs/zh-Hans/UI/Angular/Subscription-Service.md @@ -0,0 +1,205 @@ +# 轻松取消将你的观察取消订阅 + +`SubscriptionService` 是一个实用工具服务,它提供了一个简单的取消订阅Angular组件和指令中的RxJS可观察对象的功能. 请参见[为什么在实例销毁时要取消订阅可观察对象](https://angular.io/guide/lifecycle-hooks#cleaning-up-on-instance-destruction). + +## 入门 + +你必须在组件或指令级别提供 `SubscriptionService`,因为它没有在**根中提供**,而且它与组件/指令的生命周期同步. 只有在此之后,您才能注入并开始使用它。 + +```js +import { SubscriptionService } from '@abp/ng.core'; + +@Component({ + /* class metadata here */ + providers: [SubscriptionService], +}) +class DemoComponent { + count$ = interval(1000); + + constructor(private subscription: SubscriptionService) { + this.subscription.addOne(this.count$, console.log); + } +} +``` + +由 `count$` 发出的值将被记录下来,直到组件被销毁. 你不必手动退订. + +> 请不要尝试使用单例 `SubscriptionService`. 这是行不通的. + +## 用法 + +## 如何订阅可观察对象 + +你可以传递 `next` 函数和 `error` 函数. + + + +```js +@Component({ + /* class metadata here */ + providers: [SubscriptionService], +}) +class DemoComponent implements OnInit { + constructor(private subscription: SubscriptionService) {} + + ngOnInit() { + const source$ = interval(1000); + const nextFn = value => console.log(value * 2); + const errorFn = error => { + console.error(error); + return of(null); + }; + + this.subscription.addOne(source$, nextFn, errorFn); + } +} +``` + +或者,你可以传递一个观察者. + +```js +@Component({ + /* class metadata here */ + providers: [SubscriptionService], +}) +class DemoComponent implements OnInit { + constructor(private subscription: SubscriptionService) {} + + ngOnInit() { + const source$ = interval(1000); + const observer = { + next: value => console.log(value * 2), + complete: () => console.log('DONE'), + }; + + this.subscription.addOne(source$, observer); + } +} +``` + +`addOne` 方法返回单个订阅以便你以后使用它. 有关详细信息,请参见下面的主题. + +### 实例销毁之前如何退订 + +有两种方法可以做到这一点. 如果你不想再次订阅.可以使用 `closeAll` 方法. + +```js +@Component({ + /* class metadata here */ + providers: [SubscriptionService], +}) +class DemoComponent implements OnInit { + constructor(private subscription: SubscriptionService) {} + + ngOnInit() { + this.subscription.addOne(interval(1000), console.log); + } + + onSomeEvent() { + this.subscription.closeAll(); + } +} +``` + +这将清除所有订阅,你将无法再次订阅. 如果你打算添加另一个订阅,可以使用`reset`方法. + +```js +@Component({ + /* class metadata here */ + providers: [SubscriptionService], +}) +class DemoComponent implements OnInit { + constructor(private subscription: SubscriptionService) {} + + ngOnInit() { + this.subscription.addOne(interval(1000), console.log); + } + + onSomeEvent() { + this.subscription.reset(); + this.subscription.addOne(interval(1000), console.warn); + } +} +``` + +### 如何取消单个订阅 + +有时你可能需要取消订阅特定的订阅,但保留其他订阅. 在这种情况下,你可以使用 `closeOne` 方法. + +```js +@Component({ + /* class metadata here */ + providers: [SubscriptionService], +}) +class DemoComponent implements OnInit { + countSubscription: Subscription; + + constructor(private subscription: SubscriptionService) {} + + ngOnInit() { + this.countSubscription = this.subscription.addOne( + interval(1000), + console.log + ); + } + + onSomeEvent() { + this.subscription.closeOne(this.countSubscription); + console.log(this.countSubscription.closed); // true + } +} +``` + +### 如何从跟踪的订阅中删除单个订阅 + +你可能需要控制特定的订阅. 在这种情况下你可以使用 `removeOne` 方法将其从跟踪的订阅中删除. + +```js +@Component({ + /* class metadata here */ + providers: [SubscriptionService], +}) +class DemoComponent implements OnInit { + countSubscription: Subscription; + + constructor(private subscription: SubscriptionService) {} + + ngOnInit() { + this.countSubscription = this.subscription.addOne( + interval(1000), + console.log + ); + } + + onSomeEvent() { + this.subscription.removeOne(this.countSubscription); + console.log(this.countSubscription.closed); // false + } +} +``` + +### 如何检查是否全部取消订阅 + +使用 `isClosed` 检查 `closeAll` 是否被调用. + +```js +@Component({ + /* class metadata here */ + providers: [SubscriptionService], +}) +class DemoComponent implements OnInit { + constructor(private subscription: SubscriptionService) {} + + ngOnInit() { + this.subscription.addOne(interval(1000), console.log); + } + + onSomeEvent() { + console.log(this.subscription.isClosed); // false + } +} +``` + +## 下一步是什么? + +- [ListService](./List-Service.md) diff --git a/docs/zh-Hans/UI/Angular/Track-By-Service.md b/docs/zh-Hans/UI/Angular/Track-By-Service.md index 53c9dcbf1c..f941b1f0ca 100644 --- a/docs/zh-Hans/UI/Angular/Track-By-Service.md +++ b/docs/zh-Hans/UI/Angular/Track-By-Service.md @@ -99,4 +99,4 @@ class DemoComponent { ## 下一步是什么? -- [ListService](./List-Service.md) \ No newline at end of file +- [SubscriptionService](./Subscription-Service.md) \ No newline at end of file diff --git a/docs/zh-Hans/UI/Angular/images/custom-settings.png b/docs/zh-Hans/UI/Angular/images/custom-settings.png index 05dd3f695a..dc40ad3fa9 100644 Binary files a/docs/zh-Hans/UI/Angular/images/custom-settings.png and b/docs/zh-Hans/UI/Angular/images/custom-settings.png differ diff --git a/docs/zh-Hans/UI/Angular/images/generated-files-via-generate-proxy.png b/docs/zh-Hans/UI/Angular/images/generated-files-via-generate-proxy.png index 77a120a35d..0d062cc2d3 100644 Binary files a/docs/zh-Hans/UI/Angular/images/generated-files-via-generate-proxy.png and b/docs/zh-Hans/UI/Angular/images/generated-files-via-generate-proxy.png differ diff --git a/docs/zh-Hans/UI/Angular/images/locale-error.png b/docs/zh-Hans/UI/Angular/images/locale-error.png new file mode 100644 index 0000000000..de385a59c0 Binary files /dev/null and b/docs/zh-Hans/UI/Angular/images/locale-error.png differ diff --git a/docs/zh-Hans/UI/Angular/images/navigation-menu-search-input.png b/docs/zh-Hans/UI/Angular/images/navigation-menu-search-input.png index ebdc05e3e0..e93b7927d1 100644 Binary files a/docs/zh-Hans/UI/Angular/images/navigation-menu-search-input.png and b/docs/zh-Hans/UI/Angular/images/navigation-menu-search-input.png differ diff --git a/docs/zh-Hans/UI/AspNetCore/Dynamic-JavaScript-Proxies.md b/docs/zh-Hans/UI/AspNetCore/Dynamic-JavaScript-Proxies.md new file mode 100644 index 0000000000..391a63a910 --- /dev/null +++ b/docs/zh-Hans/UI/AspNetCore/Dynamic-JavaScript-Proxies.md @@ -0,0 +1,3 @@ +# Dynamic JavaScript HTTP API Proxies + +TODO \ No newline at end of file diff --git a/docs/zh-Hans/Unit-Of-Work.md b/docs/zh-Hans/Unit-Of-Work.md index 0fbf5762f0..b937c86ac0 100644 --- a/docs/zh-Hans/Unit-Of-Work.md +++ b/docs/zh-Hans/Unit-Of-Work.md @@ -21,7 +21,7 @@ ABP的UOW系统是; UOW自动针对这些方法开始,除非**周围已经有一个(环境)**UOW在运行.示例; -* 如果你调用一个[仓储]方法(Repositories.md),但还没有启动UOW,它将自动**启动一个新的事务UOW**,其中包括在仓储方法中完成的所有操作,如果仓储方法没有抛出任何异常,则**提交事务**. 仓储方法根本不知道UOW或事务. 它只在一个常规的数据库对象上工作(例如用于[EF Core](Entity-Framework-Core.md)的`DbContext`),而UOW由ABP框架处理. +* 如果你调用一个[仓储](Repositories.md)方法,但还没有启动UOW,它将自动**启动一个新的事务UOW**,其中包括在仓储方法中完成的所有操作,如果仓储方法没有抛出任何异常,则**提交事务**. 仓储方法根本不知道UOW或事务. 它只在一个常规的数据库对象上工作(例如用于[EF Core](Entity-Framework-Core.md)的`DbContext`),而UOW由ABP框架处理. * 如果调用[应用服务](Application-Services.md)方法,则相同的UOW系统将按上述说明工作. 如果应用服务方法使用某些仓储,这些仓储**不会开始新的UOW**,而是**参与由ABP框架为应用程序服务方法启动的当前工作单元中**. * ASP.NET Core控制器操作也是如此. 如果操作以控制器action开始,**UOW范围是控制器action的方法主体**. @@ -352,4 +352,4 @@ public async Task CreateAsync(string name) ````csharp app.UseUnitOfWork(); app.UseConfiguredEndpoints(); -```` \ No newline at end of file +```` diff --git a/docs/zh-Hans/docs-nav.json b/docs/zh-Hans/docs-nav.json index 732acc8806..26a526dd10 100644 --- a/docs/zh-Hans/docs-nav.json +++ b/docs/zh-Hans/docs-nav.json @@ -80,6 +80,15 @@ "text": "CLI", "path": "CLI.md" }, + { + "text": "认证", + "items": [ + { + "text": "社交/外部登录", + "path": "Authentication/Social-External-Logins.md" + } + ] + }, { "text": "基础知识", "items": [ @@ -210,6 +219,18 @@ "text": "Azure提供程序", "path": "Blob-Storing-Azure.md" }, + { + "text": "Aliyun提供程序", + "path": "Blob-Storing-Aliyun.md" + }, + { + "text": "Minio提供程序", + "path": "Blob-Storing-Minio.md" + }, + { + "text": "AWS提供程序", + "path": "Blob-Storing-Aws.md" + }, { "text": "创建自定义提供程序", "path": "Blob-Storing-Custom-Provider.md" @@ -428,12 +449,25 @@ "text": "TrackByService", "path": "UI/Angular/Track-By-Service.md" }, + { + "text": "SubscriptionService", + "path": "UI/Angular/Subscription-Service.md" + }, { "text": "ListService", "path": "UI/Angular/List-Service.md" } ] }, + { + "text": "React Native", + "items": [ + { + "text": "入门", + "path": "Getting-Started-React-Native.md" + } + ] + }, { "text": "通用", "items": [ @@ -612,6 +646,10 @@ { "text": "API文档", "path": "{ApiDocumentationUrl}" + }, + { + "text": "官方包", + "path": "https://abp.io/packages" } ] } diff --git a/docs/zh-Hans/images/basic-console-application-solution.png b/docs/zh-Hans/images/basic-console-application-solution.png new file mode 100644 index 0000000000..788bad0381 Binary files /dev/null and b/docs/zh-Hans/images/basic-console-application-solution.png differ diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index a3ee894572..5778b054ed 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -289,27 +289,39 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.SignalR EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.SignalR.Tests", "test\Volo.Abp.AspNetCore.SignalR.Tests\Volo.Abp.AspNetCore.SignalR.Tests.csproj", "{8B758716-DCC9-4223-8421-5588D1597487}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Tests", "test\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Tests\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Tests.csproj", "{79323211-E658-493E-9863-035AA4C3F913}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Tests", "test\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Tests\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Tests.csproj", "{79323211-E658-493E-9863-035AA4C3F913}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring", "src\Volo.Abp.BlobStoring\Volo.Abp.BlobStoring.csproj", "{A0CFBDD6-A3CB-438C-83F1-5025F12E2D42}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring", "src\Volo.Abp.BlobStoring\Volo.Abp.BlobStoring.csproj", "{A0CFBDD6-A3CB-438C-83F1-5025F12E2D42}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Tests", "test\Volo.Abp.BlobStoring.Tests\Volo.Abp.BlobStoring.Tests.csproj", "{D53A17BB-4E23-451D-AD9B-E1F6AC3F7958}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Tests", "test\Volo.Abp.BlobStoring.Tests\Volo.Abp.BlobStoring.Tests.csproj", "{D53A17BB-4E23-451D-AD9B-E1F6AC3F7958}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.FileSystem", "src\Volo.Abp.BlobStoring.FileSystem\Volo.Abp.BlobStoring.FileSystem.csproj", "{02B1FBE2-850E-4612-ABC6-DD62BCF2DD6B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.FileSystem", "src\Volo.Abp.BlobStoring.FileSystem\Volo.Abp.BlobStoring.FileSystem.csproj", "{02B1FBE2-850E-4612-ABC6-DD62BCF2DD6B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.FileSystem.Tests", "test\Volo.Abp.BlobStoring.FileSystem.Tests\Volo.Abp.BlobStoring.FileSystem.Tests.csproj", "{68443D4A-1608-4039-B995-7AF4CF82E9F8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.FileSystem.Tests", "test\Volo.Abp.BlobStoring.FileSystem.Tests\Volo.Abp.BlobStoring.FileSystem.Tests.csproj", "{68443D4A-1608-4039-B995-7AF4CF82E9F8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.EntityFrameworkCore.Oracle.Devart", "src\Volo.Abp.EntityFrameworkCore.Oracle.Devart\Volo.Abp.EntityFrameworkCore.Oracle.Devart.csproj", "{75E5C841-5F36-4C44-A532-57CB8E7FFE15}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.EntityFrameworkCore.Oracle.Devart", "src\Volo.Abp.EntityFrameworkCore.Oracle.Devart\Volo.Abp.EntityFrameworkCore.Oracle.Devart.csproj", "{75E5C841-5F36-4C44-A532-57CB8E7FFE15}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Azure", "src\Volo.Abp.BlobStoring.Azure\Volo.Abp.BlobStoring.Azure.csproj", "{C44242F7-D55D-4867-AAF4-A786E404312E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Azure", "src\Volo.Abp.BlobStoring.Azure\Volo.Abp.BlobStoring.Azure.csproj", "{C44242F7-D55D-4867-AAF4-A786E404312E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Azure.Tests", "test\Volo.Abp.BlobStoring.Azure.Tests\Volo.Abp.BlobStoring.Azure.Tests.csproj", "{A80E9A0B-8932-4B5D-83FB-6751708FD484}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Azure.Tests", "test\Volo.Abp.BlobStoring.Azure.Tests\Volo.Abp.BlobStoring.Azure.Tests.csproj", "{A80E9A0B-8932-4B5D-83FB-6751708FD484}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.EntityFrameworkCore.Oracle", "src\Volo.Abp.EntityFrameworkCore.Oracle\Volo.Abp.EntityFrameworkCore.Oracle.csproj", "{1738845A-5348-4EB8-B736-CD1D22A808B4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Minio", "src\Volo.Abp.BlobStoring.Minio\Volo.Abp.BlobStoring.Minio.csproj", "{658D7EDE-A057-4256-96B6-083D3C2B9704}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Caching.StackExchangeRedis", "src\Volo.Abp.Caching.StackExchangeRedis\Volo.Abp.Caching.StackExchangeRedis.csproj", "{2B83DF1F-0FD2-4DEA-ABC5-E324B51401D4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Minio.Tests", "test\Volo.Abp.BlobStoring.Minio.Tests\Volo.Abp.BlobStoring.Minio.Tests.csproj", "{36D4B268-FD3A-4655-A41B-D56D68476C83}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Caching.StackExchangeRedis.Tests", "test\Volo.Abp.Caching.StackExchangeRedis.Tests\Volo.Abp.Caching.StackExchangeRedis.Tests.csproj", "{60D0E384-965E-4F81-9D71-B28F419254FC}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.EntityFrameworkCore.Oracle", "src\Volo.Abp.EntityFrameworkCore.Oracle\Volo.Abp.EntityFrameworkCore.Oracle.csproj", "{1738845A-5348-4EB8-B736-CD1D22A808B4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Caching.StackExchangeRedis", "src\Volo.Abp.Caching.StackExchangeRedis\Volo.Abp.Caching.StackExchangeRedis.csproj", "{2B83DF1F-0FD2-4DEA-ABC5-E324B51401D4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Caching.StackExchangeRedis.Tests", "test\Volo.Abp.Caching.StackExchangeRedis.Tests\Volo.Abp.Caching.StackExchangeRedis.Tests.csproj", "{60D0E384-965E-4F81-9D71-B28F419254FC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Aliyun", "src\Volo.Abp.BlobStoring.Aliyun\Volo.Abp.BlobStoring.Aliyun.csproj", "{845E6A13-D1B5-4DDC-A16C-68D807E3B4C7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlobStoring.Aliyun.Tests", "test\Volo.Abp.BlobStoring.Aliyun.Tests\Volo.Abp.BlobStoring.Aliyun.Tests.csproj", "{8E49687A-E69F-49F2-8DB0-428D0883A937}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Aws", "src\Volo.Abp.BlobStoring.Aws\Volo.Abp.BlobStoring.Aws.csproj", "{50968CDE-1029-4051-B2E5-B69D0ECF2A18}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Aws.Tests", "test\Volo.Abp.BlobStoring.Aws.Tests\Volo.Abp.BlobStoring.Aws.Tests.csproj", "{2CD3B26A-CA81-4279-8D5D-6A594517BB3F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -913,6 +925,14 @@ Global {A80E9A0B-8932-4B5D-83FB-6751708FD484}.Debug|Any CPU.Build.0 = Debug|Any CPU {A80E9A0B-8932-4B5D-83FB-6751708FD484}.Release|Any CPU.ActiveCfg = Release|Any CPU {A80E9A0B-8932-4B5D-83FB-6751708FD484}.Release|Any CPU.Build.0 = Release|Any CPU + {658D7EDE-A057-4256-96B6-083D3C2B9704}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {658D7EDE-A057-4256-96B6-083D3C2B9704}.Debug|Any CPU.Build.0 = Debug|Any CPU + {658D7EDE-A057-4256-96B6-083D3C2B9704}.Release|Any CPU.ActiveCfg = Release|Any CPU + {658D7EDE-A057-4256-96B6-083D3C2B9704}.Release|Any CPU.Build.0 = Release|Any CPU + {36D4B268-FD3A-4655-A41B-D56D68476C83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {36D4B268-FD3A-4655-A41B-D56D68476C83}.Debug|Any CPU.Build.0 = Debug|Any CPU + {36D4B268-FD3A-4655-A41B-D56D68476C83}.Release|Any CPU.ActiveCfg = Release|Any CPU + {36D4B268-FD3A-4655-A41B-D56D68476C83}.Release|Any CPU.Build.0 = Release|Any CPU {1738845A-5348-4EB8-B736-CD1D22A808B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1738845A-5348-4EB8-B736-CD1D22A808B4}.Debug|Any CPU.Build.0 = Debug|Any CPU {1738845A-5348-4EB8-B736-CD1D22A808B4}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -925,6 +945,22 @@ Global {60D0E384-965E-4F81-9D71-B28F419254FC}.Debug|Any CPU.Build.0 = Debug|Any CPU {60D0E384-965E-4F81-9D71-B28F419254FC}.Release|Any CPU.ActiveCfg = Release|Any CPU {60D0E384-965E-4F81-9D71-B28F419254FC}.Release|Any CPU.Build.0 = Release|Any CPU + {845E6A13-D1B5-4DDC-A16C-68D807E3B4C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {845E6A13-D1B5-4DDC-A16C-68D807E3B4C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {845E6A13-D1B5-4DDC-A16C-68D807E3B4C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {845E6A13-D1B5-4DDC-A16C-68D807E3B4C7}.Release|Any CPU.Build.0 = Release|Any CPU + {8E49687A-E69F-49F2-8DB0-428D0883A937}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8E49687A-E69F-49F2-8DB0-428D0883A937}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8E49687A-E69F-49F2-8DB0-428D0883A937}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8E49687A-E69F-49F2-8DB0-428D0883A937}.Release|Any CPU.Build.0 = Release|Any CPU + {50968CDE-1029-4051-B2E5-B69D0ECF2A18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50968CDE-1029-4051-B2E5-B69D0ECF2A18}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50968CDE-1029-4051-B2E5-B69D0ECF2A18}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50968CDE-1029-4051-B2E5-B69D0ECF2A18}.Release|Any CPU.Build.0 = Release|Any CPU + {2CD3B26A-CA81-4279-8D5D-6A594517BB3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2CD3B26A-CA81-4279-8D5D-6A594517BB3F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2CD3B26A-CA81-4279-8D5D-6A594517BB3F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2CD3B26A-CA81-4279-8D5D-6A594517BB3F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1079,9 +1115,15 @@ Global {75E5C841-5F36-4C44-A532-57CB8E7FFE15} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {C44242F7-D55D-4867-AAF4-A786E404312E} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {A80E9A0B-8932-4B5D-83FB-6751708FD484} = {447C8A77-E5F0-4538-8687-7383196D04EA} + {658D7EDE-A057-4256-96B6-083D3C2B9704} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {36D4B268-FD3A-4655-A41B-D56D68476C83} = {447C8A77-E5F0-4538-8687-7383196D04EA} {1738845A-5348-4EB8-B736-CD1D22A808B4} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {2B83DF1F-0FD2-4DEA-ABC5-E324B51401D4} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {60D0E384-965E-4F81-9D71-B28F419254FC} = {447C8A77-E5F0-4538-8687-7383196D04EA} + {845E6A13-D1B5-4DDC-A16C-68D807E3B4C7} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {8E49687A-E69F-49F2-8DB0-428D0883A937} = {447C8A77-E5F0-4538-8687-7383196D04EA} + {50968CDE-1029-4051-B2E5-B69D0ECF2A18} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {2CD3B26A-CA81-4279-8D5D-6A594517BB3F} = {447C8A77-E5F0-4538-8687-7383196D04EA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj index af317c263c..1d30366e67 100644 --- a/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj @@ -19,7 +19,7 @@ - + diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Properties/launchSettings.json b/framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Properties/launchSettings.json new file mode 100644 index 0000000000..f52562c96c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:53601/", + "sslPort": 44330 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Volo.Abp.AspNetCore.Authentication.OAuth": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Properties/launchSettings.json b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Properties/launchSettings.json new file mode 100644 index 0000000000..6495c71095 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:53597/", + "sslPort": 44355 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Volo.Abp.AspNetCore.MultiTenancy": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Properties/launchSettings.json b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Properties/launchSettings.json new file mode 100644 index 0000000000..b9542fa082 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:53603/", + "sslPort": 44305 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Volo.Abp.AspNetCore.Mvc.Client": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Blockquote/AbpBlockquoteFooterTagHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Blockquote/AbpBlockquoteFooterTagHelper.cs index 3b76fa2627..d4619390ef 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Blockquote/AbpBlockquoteFooterTagHelper.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Blockquote/AbpBlockquoteFooterTagHelper.cs @@ -2,7 +2,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Blockquote { - [HtmlTargetElement("footer", ParentTag = "abp-blockquote")] + [HtmlTargetElement("footer", ParentTag = "blockquote")] public class AbpBlockquoteFooterTagHelper : AbpTagHelper { public AbpBlockquoteFooterTagHelper(AbpBlockquoteFooterTagHelperService tagHelperService) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Blockquote/AbpBlockquoteParagraphTagHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Blockquote/AbpBlockquoteParagraphTagHelper.cs index 99bcceb4fe..0e915f2a36 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Blockquote/AbpBlockquoteParagraphTagHelper.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Blockquote/AbpBlockquoteParagraphTagHelper.cs @@ -2,7 +2,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Blockquote { - [HtmlTargetElement("p", ParentTag = "abp-blockquote")] + [HtmlTargetElement("p", ParentTag = "blockquote")] public class AbpBlockquoteParagraphTagHelper : AbpTagHelper { public AbpBlockquoteParagraphTagHelper(AbpBlockquoteParagraphTagHelperService tagHelperService) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Blockquote/AbpBlockquoteTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Blockquote/AbpBlockquoteTagHelperService.cs index a60f3a4145..6c26747d11 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Blockquote/AbpBlockquoteTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Blockquote/AbpBlockquoteTagHelperService.cs @@ -8,6 +8,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Blockquote public override void Process(TagHelperContext context, TagHelperOutput output) { output.Attributes.AddClass("blockquote"); + output.TagName = "blockquote"; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Breadcrumb/AbpBreadcrumbTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Breadcrumb/AbpBreadcrumbTagHelperService.cs index 8eeeb1e929..1d0476c5ad 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Breadcrumb/AbpBreadcrumbTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Breadcrumb/AbpBreadcrumbTagHelperService.cs @@ -13,9 +13,12 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Breadcrumb var list = InitilizeFormGroupContentsContext(context, output); await output.GetChildContentAsync(); - + SetInnerOlTag(context, output); SetInnerList(context, output, list); + + output.TagName = "nav"; + output.Attributes.Add("aria-label", "breadcrumb"); } protected virtual void SetInnerOlTag(TagHelperContext context, TagHelperOutput output) @@ -63,4 +66,4 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Breadcrumb } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Button/AbpButtonGroupTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Button/AbpButtonGroupTagHelperService.cs index 602e956e1e..3250fe6877 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Button/AbpButtonGroupTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Button/AbpButtonGroupTagHelperService.cs @@ -11,6 +11,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button AddButtonGroupClass(context, output); AddSizeClass(context, output); AddAttributes(context, output); + + output.TagName = "div"; } protected virtual void AddSizeClass(TagHelperContext context, TagHelperOutput output) @@ -52,4 +54,4 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button output.Attributes.Add("role", "group"); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Button/AbpButtonToolbarTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Button/AbpButtonToolbarTagHelperService.cs index 87b378e559..1581cc8bd3 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Button/AbpButtonToolbarTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Button/AbpButtonToolbarTagHelperService.cs @@ -7,8 +7,9 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button { public override void Process(TagHelperContext context, TagHelperOutput output) { + output.TagName = "div"; output.Attributes.AddClass("btn-toolbar"); output.Attributes.Add("role","toolbar"); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Collapse/AbpAccordionItemTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Collapse/AbpAccordionItemTagHelperService.cs index 9dc279334b..74365b0c8a 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Collapse/AbpAccordionItemTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Collapse/AbpAccordionItemTagHelperService.cs @@ -23,8 +23,6 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Collapse output.SuppressOutput(); } - - protected virtual string GetAccordionHeaderItem(TagHelperContext context, TagHelperOutput output) { return "
    " + Environment.NewLine + @@ -64,4 +62,4 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Collapse } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelper.cs index efd3af2d2c..711e8dfb6c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelper.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelper.cs @@ -36,6 +36,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form public string Value { get; set; } + public bool SuppressLabel { get; set; } + public AbpInputTagHelper(AbpInputTagHelperService tagHelperService) : base(tagHelperService) { 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 4ddf44d2d6..58a88c676f 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 @@ -247,7 +247,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form protected virtual async Task GetLabelAsHtmlAsync(TagHelperContext context, TagHelperOutput output, TagHelperOutput inputTag, bool isCheckbox) { - if (IsOutputHidden(inputTag)) + if (IsOutputHidden(inputTag) || TagHelper.SuppressLabel) { return ""; } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/nl.json b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/nl.json index 2e3971055e..ea15166b9e 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/nl.json +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/nl.json @@ -1,4 +1,4 @@ -{ +{ "culture": "nl", "texts": { "GivenTenantIsNotAvailable": "Gegeven klant is niet beschikbaar: {0}", diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs index a5ee47a753..93fa193bc7 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs @@ -1,5 +1,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Packages.BootstrapDatepicker; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQueryValidation; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Timeago; using Volo.Abp.Localization; using Volo.Abp.Modularity; @@ -12,6 +14,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages { Configure(options => { + //BootstrapDatepicker options.AddLanguagesMapOrUpdate(BootstrapDatepickerScriptContributor.PackageName, new NameValue("zh-Hans", "zh-CN"), new NameValue("zh-Hant", "zh-TW")); @@ -19,6 +22,16 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages options.AddLanguageFilesMapOrUpdate(BootstrapDatepickerScriptContributor.PackageName, new NameValue("zh-Hans", "zh-CN"), new NameValue("zh-Hant", "zh-TW")); + + //Timeago + options.AddLanguageFilesMapOrUpdate(TimeagoScriptContributor.PackageName, + new NameValue("zh-Hans", "zh-CN"), + new NameValue("zh-Hant", "zh-TW")); + + //JQueryValidation + options.AddLanguageFilesMapOrUpdate(JQueryValidationScriptContributor.PackageName, + new NameValue("zh-Hans", "zh"), + new NameValue("zh-Hant", "zh_TW")); }); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDatepicker/BootstrapDatepickerScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDatepicker/BootstrapDatepickerScriptContributor.cs index ca852972d8..751fb8c53e 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDatepicker/BootstrapDatepickerScriptContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDatepicker/BootstrapDatepickerScriptContributor.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Globalization; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery; using Volo.Abp.Localization; @@ -19,26 +18,12 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.BootstrapDatepicker public override void ConfigureDynamicResources(BundleConfigurationContext context) { - var cultureName = CultureInfo.CurrentUICulture.DateTimeFormat.Calendar.AlgorithmType == - CalendarAlgorithmType.LunarCalendar - ? "en" - : CultureInfo.CurrentUICulture.Name; - - TryAddCultureFile(context, cultureName); - } - - protected virtual bool TryAddCultureFile(BundleConfigurationContext context, string cultureName) - { - var fileName = context.LocalizationOptions.GetLanguageFilesMap(PackageName, cultureName); + var fileName = context.LocalizationOptions.GetCurrentUICultureLanguageFilesMap(PackageName); var filePath = $"/libs/bootstrap-datepicker/locales/bootstrap-datepicker.{fileName}.min.js"; - - if (!context.FileProvider.GetFileInfo(filePath).Exists) + if (context.FileProvider.GetFileInfo(filePath).Exists) { - return false; + context.Files.AddIfNotContains(filePath); } - - context.Files.AddIfNotContains(filePath); - return true; } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JQueryValidation/JQueryValidationScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JQueryValidation/JQueryValidationScriptContributor.cs index 586d8864cd..c5387b233c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JQueryValidation/JQueryValidationScriptContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/JQueryValidation/JQueryValidationScriptContributor.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Globalization; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery; using Volo.Abp.Localization; @@ -10,8 +9,6 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.JQueryValidation [DependsOn(typeof(JQueryScriptContributor))] public class JQueryValidationScriptContributor : BundleContributor { - public const string DefaultLocalizationFolder = "/libs/jquery-validation/localization/"; - public const string PackageName = "jquery-validation"; public override void ConfigureBundle(BundleConfigurationContext context) @@ -21,41 +18,12 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.JQueryValidation public override void ConfigureDynamicResources(BundleConfigurationContext context) { - //TODO: Can we optimize these points: - // - Can we get rid of context.FileProvider.GetFileInfo call? - // - What if the same Contributor is used twice for a page. - // Duplication is prevented by the bundle manager, however the logic below will execute twice - - var cultureName = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName.Replace('-', '_'); - - var fileName = context.LocalizationOptions.GetLanguageFilesMap(PackageName, cultureName); - if (TryAddCultureFile(context, fileName)) - { - return; - } - - if (!cultureName.Contains("_")) - { - return; - } - - fileName = context.LocalizationOptions.GetLanguageFilesMap(PackageName, - cultureName.Substring(0, cultureName.IndexOf('_'))); - TryAddCultureFile(context, fileName); - } - - protected virtual bool TryAddCultureFile(BundleConfigurationContext context, string cultureName) - { - var filePath = DefaultLocalizationFolder + "messages_" + cultureName + ".js"; - var fileInfo = context.FileProvider.GetFileInfo(filePath); - - if (!fileInfo.Exists) + var fileName = context.LocalizationOptions.GetCurrentUICultureLanguageFilesMap(PackageName); + var filePath = $"/libs/jquery-validation/localization/messages_{fileName}.js"; + if (context.FileProvider.GetFileInfo(filePath).Exists) { - return false; + context.Files.AddIfNotContains(filePath); } - - context.Files.AddIfNotContains(filePath); - return true; } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Timeago/TimeagoScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Timeago/TimeagoScriptContributor.cs index 9edbf065cb..56adbe5deb 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Timeago/TimeagoScriptContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Timeago/TimeagoScriptContributor.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; -using System.Globalization; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery; +using Volo.Abp.Localization; using Volo.Abp.Modularity; namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Timeago @@ -9,6 +9,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Timeago [DependsOn(typeof(JQueryScriptContributor))] public class TimeagoScriptContributor : BundleContributor { + public const string PackageName = "jquery.timeago"; + public override void ConfigureBundle(BundleConfigurationContext context) { context.Files.AddIfNotContains("/libs/timeago/jquery.timeago.js"); @@ -16,17 +18,11 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Timeago public override void ConfigureDynamicResources(BundleConfigurationContext context) { - var cultureName = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName; - if (cultureName.StartsWith("en")) - { - return; - } - - var cultureFileName = $"/libs/timeago/locales/jquery.timeago.{cultureName}.js"; - - if (context.FileProvider.GetFileInfo(cultureFileName).Exists) + var fileName = context.LocalizationOptions.GetCurrentUICultureLanguageFilesMap(PackageName); + var filePath = $"/libs/timeago/locales/jquery.timeago.{fileName}.js"; + if (context.FileProvider.GetFileInfo(filePath).Exists) { - context.Files.Add(cultureFileName); + context.Files.Add(filePath); } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/LanguageSwitch/LanguageSwitchViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/LanguageSwitch/LanguageSwitchViewComponent.cs index c6921d3210..3dd1ba2b9d 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/LanguageSwitch/LanguageSwitchViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/LanguageSwitch/LanguageSwitchViewComponent.cs @@ -1,7 +1,9 @@ -using System.Globalization; +using System.Globalization; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.RequestLocalization; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Localization; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Themes.Basic.Components.Toolbar.LanguageSwitch @@ -23,12 +25,32 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Themes.Basic.Components.Toolbar CultureInfo.CurrentUICulture.Name ); + if (currentLanguage == null) + { + var abpRequestLocalizationOptionsProvider = HttpContext.RequestServices.GetRequiredService(); + var localizationOptions = await abpRequestLocalizationOptionsProvider.GetLocalizationOptionsAsync(); + if (localizationOptions.DefaultRequestCulture != null) + { + currentLanguage = new LanguageInfo( + localizationOptions.DefaultRequestCulture.Culture.Name, + localizationOptions.DefaultRequestCulture.UICulture.Name, + localizationOptions.DefaultRequestCulture.UICulture.DisplayName); + } + else + { + currentLanguage = new LanguageInfo( + CultureInfo.CurrentCulture.Name, + CultureInfo.CurrentUICulture.Name, + CultureInfo.CurrentUICulture.DisplayName); + } + } + var model = new LanguageSwitchViewComponentModel { CurrentLanguage = currentLanguage, OtherLanguages = languages.Where(l => l != currentLanguage).ToList() }; - + return View("~/Themes/Basic/Components/Toolbar/LanguageSwitch/Default.cshtml", model); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml index 2c8401aacd..5fcd5951cd 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml @@ -39,8 +39,10 @@ @(ViewBag.Title == null ? BrandingProvider.AppName : ViewBag.Title) - - + @if (ViewBag.Description != null) + { + + } @await RenderSectionAsync("styles", false) @@ -86,7 +88,9 @@
    } @(await Component.InvokeAsync()) + @await Component.InvokeLayoutHookAsync(LayoutHooks.PageContent.First, StandardLayouts.Account) @RenderBody() + @await Component.InvokeLayoutHookAsync(LayoutHooks.PageContent.Last, StandardLayouts.Account)
    @@ -102,4 +106,4 @@ @await Component.InvokeLayoutHookAsync(LayoutHooks.Body.Last, StandardLayouts.Account) - \ No newline at end of file + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Application.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Application.cshtml index 5e271eabab..7b06538510 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Application.cshtml +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Application.cshtml @@ -64,7 +64,9 @@ @RenderSection("content_toolbar", false)
    + @await Component.InvokeLayoutHookAsync(LayoutHooks.PageContent.First, StandardLayouts.Application) @RenderBody() + @await Component.InvokeLayoutHookAsync(LayoutHooks.PageContent.Last, StandardLayouts.Application) @@ -78,4 +80,4 @@ @await Component.InvokeLayoutHookAsync(LayoutHooks.Body.Last, StandardLayouts.Application) - \ No newline at end of file + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Empty.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Empty.cshtml index 8d4eab5167..9871bcbb25 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Empty.cshtml +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Empty.cshtml @@ -59,7 +59,9 @@
    @(await Component.InvokeAsync()) + @await Component.InvokeLayoutHookAsync(LayoutHooks.PageContent.First, StandardLayouts.Empty) @RenderBody() + @await Component.InvokeLayoutHookAsync(LayoutHooks.PageContent.Last, StandardLayouts.Empty)
    @@ -73,4 +75,4 @@ @await Component.InvokeLayoutHookAsync(LayoutHooks.Body.Last, StandardLayouts.Empty) - \ No newline at end of file + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.css b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.css index c252bf55ef..20c75cfb69 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.css +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/wwwroot/themes/basic/layout.css @@ -57,7 +57,34 @@ font-size: 0.8em; } - .dataTables_scrollBody { min-height: 248px; -} \ No newline at end of file +} + +div.dataTables_wrapper div.dataTables_info { + padding-top: 11px; + white-space: nowrap; +} + +div.dataTables_wrapper div.dataTables_length label { + padding-top: 10px; + margin-bottom: 0; +} + +.rtl .dropdown-menu-right { + right: auto; + left: 0; +} + + .rtl .dropdown-menu-right a { + text-align: right; + } + +.rtl .navbar .dropdown-menu a { + text-align: right; +} +.rtl .navbar .dropdown-submenu .dropdown-menu { + top: 0; + left: auto; + right: 100%; +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/AbpAspNetCoreMvcUiThemeSharedDemoModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/AbpAspNetCoreMvcUiThemeSharedDemoModule.cs index cd312db532..30265019c4 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/AbpAspNetCoreMvcUiThemeSharedDemoModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/AbpAspNetCoreMvcUiThemeSharedDemoModule.cs @@ -12,7 +12,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo { Configure(options => { - options.FileSets.AddEmbedded("Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo"); + options.FileSets.AddEmbedded(); }); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.csproj index 8c098c3300..111a6a898a 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.csproj @@ -12,6 +12,7 @@ false false false + true true Library
    @@ -24,4 +25,13 @@ + + + + + + + + +
    diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js index 824e376934..9a2052b267 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js @@ -299,6 +299,13 @@ column.targets = i; } + if (!column.render && column.dataFormat) { + var render = datatables.defaultRenderers[column.dataFormat]; + if (render) { + column.render = render; + } + } + if (column.rowAction) { customizeRowActionColumn(column); } @@ -330,18 +337,23 @@ } }; + var ISOStringToDateTimeLocaleString = function (format) { + return function(data) { + var date = luxon + .DateTime + .fromISO(data, { + locale: abp.localization.currentCulture.name + }); + return format ? date.toLocaleString(format) : date.toLocaleString(); + }; + }; + datatables.defaultRenderers['date'] = function (value) { - return luxon - .DateTime - .fromISO(value, { locale: abp.localization.currentCulture.name }) - .toLocaleString(); + return (ISOStringToDateTimeLocaleString())(value); }; datatables.defaultRenderers['datetime'] = function (value) { - return luxon - .DateTime - .fromISO(value, { locale: abp.localization.currentCulture.name }) - .toLocaleString(luxon.DateTime.DATETIME_SHORT); + return (ISOStringToDateTimeLocaleString(luxon.DateTime.DATETIME_SHORT))(value); }; /************************************************************************ diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/widget-manager.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/widget-manager.js index 0b746b5e37..1dd7717070 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/widget-manager.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/widget-manager.js @@ -25,11 +25,16 @@ opts.filterForm = $(opts.filterForm); } - var getFilters = function ($widgetWrapperDiv) { + var publicApi = { + init: init, + refresh: refresh + }; + + function getFilters($widget) { var filters = {}; - + if (opts.filterForm) { - opts.filterForm.each(function() { + opts.filterForm.each(function () { filters = $.extend(filters, opts.filterForm.serializeFormToObject()); }); } @@ -38,55 +43,66 @@ filters = $.extend(filters, opts.filterCallback()); } - var widgetApi = $widgetWrapperDiv.data('abp-widget-api'); + var widgetApi = $widget.data('abp-widget-api'); if (widgetApi && widgetApi.getFilters) { filters = $.extend(filters, widgetApi.getFilters()); } return filters; - }; + } - var init = function () { - opts.wrapper.find('.abp-widget-wrapper').each(function () { - var $widgetWrapperDiv = $(this); - var widgetName = $widgetWrapperDiv.attr('data-widget-name'); + function init($widgets) { + if(!$widgets){ + $widgets = opts.wrapper.findWithSelf('.abp-widget-wrapper'); + } + + $widgets.each(function () { + var $widget = $(this); + $widget.data('abp-widget-manager', publicApi); + var widgetName = $widget.attr('data-widget-name'); var widgetApiClass = abp.widgets[widgetName]; if (widgetApiClass) { - var widgetApi = new widgetApiClass($widgetWrapperDiv); - $widgetWrapperDiv.data('abp-widget-api', widgetApi); + var widgetApi = new widgetApiClass($widget); + $widget.data('abp-widget-api', widgetApi); if (widgetApi.init) { - widgetApi.init(getFilters($widgetWrapperDiv)); + widgetApi.init(getFilters($widget)); } } }); - }; + } + + function refresh($widgets) { + if(!$widgets){ + $widgets = opts.wrapper.findWithSelf('.abp-widget-wrapper'); + } - var refresh = function () { - opts.wrapper.find('.abp-widget-wrapper').each(function () { - var $widgetWrapperDiv = $(this); + $widgets.each(function () { + var $widget = $(this); - var refreshUrl = $widgetWrapperDiv.attr('data-refresh-url'); + var refreshUrl = $widget.attr('data-refresh-url'); if (refreshUrl) { abp.ajax({ url: refreshUrl, type: 'GET', dataType: 'html', contentType: 'application/x-www-form-urlencoded; charset=UTF-8', - data: getFilters($widgetWrapperDiv) + data: getFilters($widget) }).then(function (result) { - $widgetWrapperDiv.replaceWith($(result)); + var $result = $(result); + $widget.replaceWith($result); + init($result); }); } else { - var widgetApi = $widgetWrapperDiv.data('abp-widget-api'); + var widgetApi = $widget.data('abp-widget-api'); if (widgetApi && widgetApi.refresh) { - widgetApi.refresh(getFilters($widgetWrapperDiv)); + widgetApi.refresh(getFilters($widget)); } } }); - }; + } if (opts.filterForm) { - opts.filterForm.each(function() { + opts.filterForm.each(function () { $(this).submit(function (e) { e.preventDefault(); refresh(); @@ -94,14 +110,9 @@ }); } - var publicApi = { - init: init, - refresh: refresh - }; - opts.wrapper.data('abp-widget-manager', publicApi); return publicApi; }; -})(jQuery); \ No newline at end of file +})(jQuery); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHooks.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHooks.cs index ab3ef8e903..ebdcb95e77 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHooks.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Components/LayoutHook/LayoutHooks.cs @@ -13,5 +13,11 @@ public const string First = "Body.First"; public const string Last = "Body.Last"; } + + public static class PageContent + { + public const string First = "PageContent.First"; + public const string Last = "PageContent.Last"; + } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj index 47899625d4..e190116b2f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj @@ -26,9 +26,9 @@ - + - + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs index 1042ee4d08..617650d0e2 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; -using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; @@ -13,7 +12,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Volo.Abp.Application.Services; -using Volo.Abp.AspNetCore.Mvc.ApiExploring; using Volo.Abp.AspNetCore.Mvc.Conventions; using Volo.Abp.AspNetCore.Mvc.Utils; using Volo.Abp.DependencyInjection; @@ -67,34 +65,51 @@ namespace Volo.Abp.AspNetCore.Mvc private void AddApiDescriptionToModel( ApiDescription apiDescription, - ApplicationApiDescriptionModel applicationModel, + ApplicationApiDescriptionModel applicationModel, ApplicationApiDescriptionModelRequestDto input) { - var controllerType = apiDescription.ActionDescriptor.AsControllerActionDescriptor().ControllerTypeInfo.AsType(); + var controllerType = apiDescription + .ActionDescriptor + .AsControllerActionDescriptor() + .ControllerTypeInfo; + var setting = FindSetting(controllerType); - var moduleModel = applicationModel.GetOrAddModule(GetRootPath(controllerType, setting), GetRemoteServiceName(controllerType, setting)); + var moduleModel = applicationModel.GetOrAddModule( + GetRootPath(controllerType, setting), + GetRemoteServiceName(controllerType, setting) + ); - var controllerModel = moduleModel.GetOrAddController(controllerType.FullName, CalculateControllerName(controllerType, setting), controllerType, _modelOptions.IgnoredInterfaces); + var controllerModel = moduleModel.GetOrAddController( + controllerType.FullName, + CalculateControllerName(controllerType, setting), + controllerType, + _modelOptions.IgnoredInterfaces + ); var method = apiDescription.ActionDescriptor.GetMethodInfo(); var uniqueMethodName = GetUniqueActionName(method); if (controllerModel.Actions.ContainsKey(uniqueMethodName)) { - Logger.LogWarning($"Controller '{controllerModel.ControllerName}' contains more than one action with name '{uniqueMethodName}' for module '{moduleModel.RootPath}'. Ignored: " + method); + Logger.LogWarning( + $"Controller '{controllerModel.ControllerName}' contains more than one action with name '{uniqueMethodName}' for module '{moduleModel.RootPath}'. Ignored: " + + method); return; } Logger.LogDebug($"ActionApiDescriptionModel.Create: {controllerModel.ControllerName}.{uniqueMethodName}"); - var actionModel = controllerModel.AddAction(uniqueMethodName, ActionApiDescriptionModel.Create( + var actionModel = controllerModel.AddAction( uniqueMethodName, - method, - apiDescription.RelativePath, - apiDescription.HttpMethod, - GetSupportedVersions(controllerType, method, setting) - )); + ActionApiDescriptionModel.Create( + uniqueMethodName, + method, + apiDescription.RelativePath, + apiDescription.HttpMethod, + GetSupportedVersions(controllerType, method, setting) + ) + ); if (input.IncludeTypes) { @@ -106,11 +121,14 @@ namespace Volo.Abp.AspNetCore.Mvc private static string CalculateControllerName(Type controllerType, ConventionalControllerSetting setting) { - var controllerName = controllerType.Name.RemovePostFix("Controller").RemovePostFix(ApplicationService.CommonPostfixes); + var controllerName = controllerType.Name.RemovePostFix("Controller") + .RemovePostFix(ApplicationService.CommonPostfixes); if (setting?.UrlControllerNameNormalizer != null) { - controllerName = setting.UrlControllerNameNormalizer(new UrlControllerNameNormalizerContext(setting.RootPath, controllerName)); + controllerName = + setting.UrlControllerNameNormalizer( + new UrlControllerNameNormalizerContext(setting.RootPath, controllerName)); } return controllerName; @@ -139,7 +157,8 @@ namespace Volo.Abp.AspNetCore.Mvc return methodNameBuilder.ToString(); } - private static List GetSupportedVersions(Type controllerType, MethodInfo method, ConventionalControllerSetting setting) + private static List GetSupportedVersions(Type controllerType, MethodInfo method, + ConventionalControllerSetting setting) { var supportedVersions = new List(); @@ -172,17 +191,23 @@ namespace Volo.Abp.AspNetCore.Mvc AddCustomTypesToModel(applicationModel, method.ReturnType); } - private static void AddCustomTypesToModel(ApplicationApiDescriptionModel applicationModel, [CanBeNull] Type type) + private static void AddCustomTypesToModel(ApplicationApiDescriptionModel applicationModel, + [CanBeNull] Type type) { if (type == null) { return; } + if (type.IsGenericParameter) + { + return; + } + type = AsyncHelper.UnwrapTask(type); if (type == typeof(object) || - type == typeof(void) || + type == typeof(void) || type == typeof(Enum) || type == typeof(ValueType) || TypeHelper.IsPrimitiveExtended(type)) @@ -190,41 +215,67 @@ namespace Volo.Abp.AspNetCore.Mvc return; } - if (TypeHelper.IsEnumerable(type, out var itemType)) + if (TypeHelper.IsDictionary(type, out var keyType, out var valueType)) { - AddCustomTypesToModel(applicationModel, itemType); + AddCustomTypesToModel(applicationModel, keyType); + AddCustomTypesToModel(applicationModel, valueType); return; } - if (TypeHelper.IsDictionary(type, out var keyType, out var valueType)) + if (TypeHelper.IsEnumerable(type, out var itemType)) { - AddCustomTypesToModel(applicationModel, keyType); - AddCustomTypesToModel(applicationModel, valueType); + AddCustomTypesToModel(applicationModel, itemType); return; } - /* TODO: Add interfaces - */ + if (type.IsGenericType && !type.IsGenericTypeDefinition) + { + var genericTypeDefinition = type.GetGenericTypeDefinition(); + + AddCustomTypesToModel(applicationModel, genericTypeDefinition); + + foreach (var genericArgument in type.GetGenericArguments()) + { + AddCustomTypesToModel(applicationModel, genericArgument); + } - var typeName = TypeHelper.GetFullNameHandlingNullableAndGenerics(type); + return; + } + var typeName = CalculateTypeName(type); if (applicationModel.Types.ContainsKey(typeName)) { return; } - - var typeModel = TypeApiDescriptionModel.Create(type); - applicationModel.Types[typeName] = typeModel; + + applicationModel.Types[typeName] = TypeApiDescriptionModel.Create(type); AddCustomTypesToModel(applicationModel, type.BaseType); - foreach (var propertyInfo in type.GetProperties()) + foreach (var propertyInfo in type.GetProperties().Where(p => p.DeclaringType == type)) { AddCustomTypesToModel(applicationModel, propertyInfo.PropertyType); } } - private void AddParameterDescriptionsToModel(ActionApiDescriptionModel actionModel, MethodInfo method, ApiDescription apiDescription) + private static string CalculateTypeName(Type type) + { + if (!type.IsGenericTypeDefinition) + { + return TypeHelper.GetFullNameHandlingNullableAndGenerics(type); + } + + var i = 0; + var argumentList = type + .GetGenericArguments() + .Select(_ => "T" + i++) + .JoinAsString(","); + + return $"{type.FullName.Left(type.FullName.IndexOf('`'))}<{argumentList}>"; + } + + private void AddParameterDescriptionsToModel(ActionApiDescriptionModel actionModel, MethodInfo method, + ApiDescription apiDescription) { if (!apiDescription.ParameterDescriptions.Any()) { @@ -240,8 +291,8 @@ namespace Volo.Abp.AspNetCore.Mvc { var parameterDescription = apiDescription.ParameterDescriptions[i]; var matchedMethodParamName = matchedMethodParamNames.Length > i - ? matchedMethodParamNames[i] - : parameterDescription.Name; + ? matchedMethodParamNames[i] + : parameterDescription.Name; actionModel.AddParameter(ParameterApiDescriptionModel.Create( parameterDescription.Name, @@ -270,10 +321,11 @@ namespace Volo.Abp.AspNetCore.Mvc return parameterInfo.Name; } - return modelNameProvider.Name; + return modelNameProvider.Name ?? parameterInfo.Name; } - private static string GetRootPath([NotNull] Type controllerType, [CanBeNull] ConventionalControllerSetting setting) + private static string GetRootPath([NotNull] Type controllerType, + [CanBeNull] ConventionalControllerSetting setting) { if (setting != null) { @@ -296,7 +348,8 @@ namespace Volo.Abp.AspNetCore.Mvc return setting.RemoteServiceName; } - var remoteServiceAttr = controllerType.GetCustomAttributes().OfType().FirstOrDefault(); + var remoteServiceAttr = + controllerType.GetCustomAttributes().OfType().FirstOrDefault(); if (remoteServiceAttr?.Name != null) { return remoteServiceAttr.Name; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs index 1ce177ba44..009d52b42f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs @@ -1,4 +1,5 @@ using System; +using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -25,7 +26,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling public AbpExceptionFilter( IExceptionToErrorInfoConverter errorInfoConverter, - IHttpExceptionStatusCodeFinder statusCodeFinder, + IHttpExceptionStatusCodeFinder statusCodeFinder, IJsonSerializer jsonSerializer) { _errorInfoConverter = errorInfoConverter; @@ -54,7 +55,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling { return true; } - + if (context.HttpContext.Request.CanAccept(MimeTypes.Application.Json)) { return true; @@ -81,8 +82,11 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling var logLevel = context.Exception.GetLogLevel(); - Logger.LogWithLevel(logLevel, $"---------- {nameof(RemoteServiceErrorInfo)} ----------"); - Logger.LogWithLevel(logLevel, _jsonSerializer.Serialize(remoteServiceErrorInfo, indented: true)); + var remoteServiceErrorInfoBuilder = new StringBuilder(); + remoteServiceErrorInfoBuilder.AppendLine($"---------- {nameof(RemoteServiceErrorInfo)} ----------"); + remoteServiceErrorInfoBuilder.AppendLine( _jsonSerializer.Serialize(remoteServiceErrorInfo, indented: true)); + Logger.LogWithLevel(logLevel, remoteServiceErrorInfoBuilder.ToString()); + Logger.LogException(context.Exception, logLevel); await context.HttpContext diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs index 9e449f0474..8e2356ff20 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs @@ -1,4 +1,5 @@ using System; +using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -93,8 +94,11 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling var logLevel = context.Exception.GetLogLevel(); - Logger.LogWithLevel(logLevel, $"---------- {nameof(RemoteServiceErrorInfo)} ----------"); - Logger.LogWithLevel(logLevel, _jsonSerializer.Serialize(remoteServiceErrorInfo, indented: true)); + var remoteServiceErrorInfoBuilder = new StringBuilder(); + remoteServiceErrorInfoBuilder.AppendLine($"---------- {nameof(RemoteServiceErrorInfo)} ----------"); + remoteServiceErrorInfoBuilder.AppendLine( _jsonSerializer.Serialize(remoteServiceErrorInfo, indented: true)); + Logger.LogWithLevel(logLevel, remoteServiceErrorInfoBuilder.ToString()); + Logger.LogException(context.Exception, logLevel); await context.HttpContext diff --git a/framework/src/Volo.Abp.AspNetCore.Serilog/Properties/launchSettings.json b/framework/src/Volo.Abp.AspNetCore.Serilog/Properties/launchSettings.json new file mode 100644 index 0000000000..afff9bb30b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Serilog/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:53606/", + "sslPort": 44319 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Volo.Abp.AspNetCore.Serilog": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.SignalR/Properties/launchSettings.json b/framework/src/Volo.Abp.AspNetCore.SignalR/Properties/launchSettings.json new file mode 100644 index 0000000000..f5e00240d4 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.SignalR/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:53611/", + "sslPort": 44331 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Volo.Abp.AspNetCore.SignalR": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.TestBase/Properties/launchSettings.json b/framework/src/Volo.Abp.AspNetCore.TestBase/Properties/launchSettings.json new file mode 100644 index 0000000000..fca23e6182 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.TestBase/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:53599/", + "sslPort": 44358 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Volo.Abp.AspNetCore.TestBase": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.TestBase/Volo.Abp.AspNetCore.TestBase.csproj b/framework/src/Volo.Abp.AspNetCore.TestBase/Volo.Abp.AspNetCore.TestBase.csproj index a4dcd1c8c5..ce02e1f0a0 100644 --- a/framework/src/Volo.Abp.AspNetCore.TestBase/Volo.Abp.AspNetCore.TestBase.csproj +++ b/framework/src/Volo.Abp.AspNetCore.TestBase/Volo.Abp.AspNetCore.TestBase.csproj @@ -24,7 +24,7 @@ - + diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/VirtualFileSystemApplicationBuilderExtensions.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/VirtualFileSystemApplicationBuilderExtensions.cs index 0557c37380..da9f65059f 100644 --- a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/VirtualFileSystemApplicationBuilderExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/VirtualFileSystemApplicationBuilderExtensions.cs @@ -1,18 +1,22 @@ -using Microsoft.Extensions.DependencyInjection; +using System; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.VirtualFileSystem; namespace Microsoft.AspNetCore.Builder { public static class VirtualFileSystemApplicationBuilderExtensions { - public static IApplicationBuilder UseVirtualFiles(this IApplicationBuilder app) + public static IApplicationBuilder UseVirtualFiles(this IApplicationBuilder app, Action configure = null) { - return app.UseStaticFiles( - new StaticFileOptions - { - FileProvider = app.ApplicationServices.GetRequiredService() - } - ); + var staticFileOptions = new StaticFileOptions + { + FileProvider = app.ApplicationServices.GetRequiredService(), + ContentTypeProvider = app.ApplicationServices.GetRequiredService() + }; + + configure?.Invoke(staticFileOptions); + + return app.UseStaticFiles(staticFileOptions); } } } diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/DefaultAbpRequestLocalizationOptionsProvider.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/DefaultAbpRequestLocalizationOptionsProvider.cs index 64f339ffce..263d0532d0 100644 --- a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/DefaultAbpRequestLocalizationOptionsProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/DefaultAbpRequestLocalizationOptionsProvider.cs @@ -50,35 +50,38 @@ namespace Microsoft.AspNetCore.RequestLocalization { using (await _syncSemaphore.LockAsync()) { - using (var serviceScope = _serviceProviderFactory.CreateScope()) + if (_requestLocalizationOptions == null) { - var languageProvider = serviceScope.ServiceProvider.GetRequiredService(); - var settingProvider = serviceScope.ServiceProvider.GetRequiredService(); + using (var serviceScope = _serviceProviderFactory.CreateScope()) + { + var languageProvider = serviceScope.ServiceProvider.GetRequiredService(); + var settingProvider = serviceScope.ServiceProvider.GetRequiredService(); - var languages = await languageProvider.GetLanguagesAsync(); - var defaultLanguage = await settingProvider.GetOrNullAsync(LocalizationSettingNames.DefaultLanguage); + var languages = await languageProvider.GetLanguagesAsync(); + var defaultLanguage = await settingProvider.GetOrNullAsync(LocalizationSettingNames.DefaultLanguage); - var options = !languages.Any() - ? new RequestLocalizationOptions() - : new RequestLocalizationOptions - { - DefaultRequestCulture = DefaultGetRequestCulture(defaultLanguage, languages), + var options = !languages.Any() + ? new RequestLocalizationOptions() + : new RequestLocalizationOptions + { + DefaultRequestCulture = DefaultGetRequestCulture(defaultLanguage, languages), - SupportedCultures = languages - .Select(l => l.CultureName) - .Distinct() - .Select(c => new CultureInfo(c)) - .ToArray(), + SupportedCultures = languages + .Select(l => l.CultureName) + .Distinct() + .Select(c => new CultureInfo(c)) + .ToArray(), - SupportedUICultures = languages - .Select(l => l.UiCultureName) - .Distinct() - .Select(c => new CultureInfo(c)) - .ToArray() - }; + SupportedUICultures = languages + .Select(l => l.UiCultureName) + .Distinct() + .Select(c => new CultureInfo(c)) + .ToArray() + }; - _optionsAction?.Invoke(options); - _requestLocalizationOptions = options; + _optionsAction?.Invoke(options); + _requestLocalizationOptions = options; + } } } } @@ -98,4 +101,4 @@ namespace Microsoft.AspNetCore.RequestLocalization return new RequestCulture(cultureName, uiCultureName); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore/Properties/launchSettings.json b/framework/src/Volo.Abp.AspNetCore/Properties/launchSettings.json new file mode 100644 index 0000000000..78196102ba --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:53596/", + "sslPort": 44300 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Volo.Abp.AspNetCore": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AspNetCoreAuditLogContributor.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AspNetCoreAuditLogContributor.cs index 378b92a14a..04238a9a45 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AspNetCoreAuditLogContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AspNetCoreAuditLogContributor.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.AspNetCore.WebClientInfo; using Volo.Abp.Auditing; using Volo.Abp.DependencyInjection; @@ -35,14 +36,15 @@ namespace Volo.Abp.AspNetCore.Auditing context.AuditInfo.Url = BuildUrl(httpContext); } + var clientInfoProvider = context.ServiceProvider.GetRequiredService(); if (context.AuditInfo.ClientIpAddress == null) { - context.AuditInfo.ClientIpAddress = GetClientIpAddress(httpContext); + context.AuditInfo.ClientIpAddress = clientInfoProvider.ClientIpAddress; } if (context.AuditInfo.BrowserInfo == null) { - context.AuditInfo.BrowserInfo = GetBrowserInfo(httpContext); + context.AuditInfo.BrowserInfo = clientInfoProvider.BrowserInfo; } //TODO: context.AuditInfo.ClientName @@ -62,24 +64,6 @@ namespace Volo.Abp.AspNetCore.Auditing } } - protected virtual string GetBrowserInfo(HttpContext httpContext) - { - return httpContext.Request?.Headers?["User-Agent"]; - } - - protected virtual string GetClientIpAddress(HttpContext httpContext) - { - try - { - return httpContext.Connection?.RemoteIpAddress?.ToString(); - } - catch (Exception ex) - { - Logger.LogException(ex, LogLevel.Warning); - return null; - } - } - protected virtual string BuildUrl(HttpContext httpContext) { //TODO: Add options to include/exclude query, schema and host diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingMiddleware.cs index 9a7a8f1676..edd00e1fb7 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingMiddleware.cs @@ -15,11 +15,11 @@ namespace Volo.Abp.AspNetCore.ExceptionHandling { public class AbpExceptionHandlingMiddleware : IMiddleware, ITransientDependency { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly Func _clearCacheHeadersDelegate; - public AbpExceptionHandlingMiddleware(ILogger logger) + public AbpExceptionHandlingMiddleware(ILogger logger) { _logger = logger; diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/SecurityLog/AspNetCoreSecurityLogManager.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/SecurityLog/AspNetCoreSecurityLogManager.cs new file mode 100644 index 0000000000..f0fc0aa347 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/SecurityLog/AspNetCoreSecurityLogManager.cs @@ -0,0 +1,73 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Volo.Abp.AspNetCore.WebClientInfo; +using Volo.Abp.Clients; +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; +using Volo.Abp.SecurityLog; +using Volo.Abp.Timing; +using Volo.Abp.Tracing; +using Volo.Abp.Users; + +namespace Volo.Abp.AspNetCore.SecurityLog +{ + [Dependency(ReplaceServices = true)] + public class AspNetCoreSecurityLogManager : DefaultSecurityLogManager + { + protected ILogger Logger { get; } + protected IClock Clock { get; } + protected ICurrentUser CurrentUser { get; } + protected ICurrentTenant CurrentTenant { get; } + protected ICurrentClient CurrentClient { get; } + protected IHttpContextAccessor HttpContextAccessor { get; } + protected ICorrelationIdProvider CorrelationIdProvider { get; } + protected IWebClientInfoProvider WebClientInfoProvider { get; } + + public AspNetCoreSecurityLogManager( + IOptions securityLogOptions, + ISecurityLogStore securityLogStore, + ILogger logger, + IClock clock, + ICurrentUser currentUser, + ICurrentTenant currentTenant, + ICurrentClient currentClient, + IHttpContextAccessor httpContextAccessor, + ICorrelationIdProvider correlationIdProvider, + IWebClientInfoProvider webClientInfoProvider) + : base(securityLogOptions, securityLogStore) + { + Logger = logger; + Clock = clock; + CurrentUser = currentUser; + CurrentTenant = currentTenant; + CurrentClient = currentClient; + HttpContextAccessor = httpContextAccessor; + CorrelationIdProvider = correlationIdProvider; + WebClientInfoProvider = webClientInfoProvider; + } + + protected override async Task CreateAsync() + { + var securityLogInfo = await base.CreateAsync(); + + securityLogInfo.CreationTime = Clock.Now; + + securityLogInfo.TenantId = CurrentTenant.Id; + securityLogInfo.TenantName = CurrentTenant.Name; + + securityLogInfo.UserId = CurrentUser.Id; + securityLogInfo.UserName = CurrentUser.UserName; + + securityLogInfo.ClientId = CurrentClient.Id; + + securityLogInfo.CorrelationId = CorrelationIdProvider.Get(); + + securityLogInfo.ClientIpAddress = WebClientInfoProvider.ClientIpAddress; + securityLogInfo.BrowserInfo = WebClientInfoProvider.BrowserInfo; + + return securityLogInfo; + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/AbpAspNetCoreContentOptions.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/AbpAspNetCoreContentOptions.cs index ce6e454f54..f4b7d631cc 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/AbpAspNetCoreContentOptions.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/AbpAspNetCoreContentOptions.cs @@ -1,14 +1,401 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; namespace Volo.Abp.AspNetCore.VirtualFileSystem { public class AbpAspNetCoreContentOptions { + public Dictionary ContentTypeMaps { get; } + public List AllowedExtraWebContentFolders { get; } + public List AllowedExtraWebContentFileExtensions { get; } public AbpAspNetCoreContentOptions() { + ContentTypeMaps = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + {".323", "text/h323"}, + {".3g2", "video/3gpp2"}, + {".3gp2", "video/3gpp2"}, + {".3gp", "video/3gpp"}, + {".3gpp", "video/3gpp"}, + {".aac", "audio/aac"}, + {".aaf", "application/octet-stream"}, + {".aca", "application/octet-stream"}, + {".accdb", "application/msaccess"}, + {".accde", "application/msaccess"}, + {".accdt", "application/msaccess"}, + {".acx", "application/internet-property-stream"}, + {".adt", "audio/vnd.dlna.adts"}, + {".adts", "audio/vnd.dlna.adts"}, + {".afm", "application/octet-stream"}, + {".ai", "application/postscript"}, + {".aif", "audio/x-aiff"}, + {".aifc", "audio/aiff"}, + {".aiff", "audio/aiff"}, + {".appcache", "text/cache-manifest"}, + {".application", "application/x-ms-application"}, + {".art", "image/x-jg"}, + {".asd", "application/octet-stream"}, + {".asf", "video/x-ms-asf"}, + {".asi", "application/octet-stream"}, + {".asm", "text/plain"}, + {".asr", "video/x-ms-asf"}, + {".asx", "video/x-ms-asf"}, + {".atom", "application/atom+xml"}, + {".au", "audio/basic"}, + {".avi", "video/x-msvideo"}, + {".axs", "application/olescript"}, + {".bas", "text/plain"}, + {".bcpio", "application/x-bcpio"}, + {".bin", "application/octet-stream"}, + {".bmp", "image/bmp"}, + {".c", "text/plain"}, + {".cab", "application/vnd.ms-cab-compressed"}, + {".calx", "application/vnd.ms-office.calx"}, + {".cat", "application/vnd.ms-pki.seccat"}, + {".cdf", "application/x-cdf"}, + {".chm", "application/octet-stream"}, + {".class", "application/x-java-applet"}, + {".clp", "application/x-msclip"}, + {".cmx", "image/x-cmx"}, + {".cnf", "text/plain"}, + {".cod", "image/cis-cod"}, + {".cpio", "application/x-cpio"}, + {".cpp", "text/plain"}, + {".crd", "application/x-mscardfile"}, + {".crl", "application/pkix-crl"}, + {".crt", "application/x-x509-ca-cert"}, + {".csh", "application/x-csh"}, + {".css", "text/css"}, + {".csv", "application/octet-stream"}, + {".cur", "application/octet-stream"}, + {".dcr", "application/x-director"}, + {".deploy", "application/octet-stream"}, + {".der", "application/x-x509-ca-cert"}, + {".dib", "image/bmp"}, + {".dir", "application/x-director"}, + {".disco", "text/xml"}, + {".dlm", "text/dlm"}, + {".doc", "application/msword"}, + {".docm", "application/vnd.ms-word.document.macroEnabled.12"}, + {".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, + {".dot", "application/msword"}, + {".dotm", "application/vnd.ms-word.template.macroEnabled.12"}, + {".dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"}, + {".dsp", "application/octet-stream"}, + {".dtd", "text/xml"}, + {".dvi", "application/x-dvi"}, + {".dvr-ms", "video/x-ms-dvr"}, + {".dwf", "drawing/x-dwf"}, + {".dwp", "application/octet-stream"}, + {".dxr", "application/x-director"}, + {".eml", "message/rfc822"}, + {".emz", "application/octet-stream"}, + {".eot", "application/vnd.ms-fontobject"}, + {".eps", "application/postscript"}, + {".etx", "text/x-setext"}, + {".evy", "application/envoy"}, + {".fdf", "application/vnd.fdf"}, + {".fif", "application/fractals"}, + {".fla", "application/octet-stream"}, + {".flr", "x-world/x-vrml"}, + {".flv", "video/x-flv"}, + {".gif", "image/gif"}, + {".gtar", "application/x-gtar"}, + {".gz", "application/x-gzip"}, + {".h", "text/plain"}, + {".hdf", "application/x-hdf"}, + {".hdml", "text/x-hdml"}, + {".hhc", "application/x-oleobject"}, + {".hhk", "application/octet-stream"}, + {".hhp", "application/octet-stream"}, + {".hlp", "application/winhlp"}, + {".hqx", "application/mac-binhex40"}, + {".hta", "application/hta"}, + {".htc", "text/x-component"}, + {".htm", "text/html"}, + {".html", "text/html"}, + {".htt", "text/webviewhtml"}, + {".hxt", "text/html"}, + {".ical", "text/calendar"}, + {".icalendar", "text/calendar"}, + {".ico", "image/x-icon"}, + {".ics", "text/calendar"}, + {".ief", "image/ief"}, + {".ifb", "text/calendar"}, + {".iii", "application/x-iphone"}, + {".inf", "application/octet-stream"}, + {".ins", "application/x-internet-signup"}, + {".isp", "application/x-internet-signup"}, + {".IVF", "video/x-ivf"}, + {".jar", "application/java-archive"}, + {".java", "application/octet-stream"}, + {".jck", "application/liquidmotion"}, + {".jcz", "application/liquidmotion"}, + {".jfif", "image/pjpeg"}, + {".jpb", "application/octet-stream"}, + {".jpe", "image/jpeg"}, + {".jpeg", "image/jpeg"}, + {".jpg", "image/jpeg"}, + {".js", "application/javascript"}, + {".json", "application/json"}, + {".jsx", "text/jscript"}, + {".latex", "application/x-latex"}, + {".lit", "application/x-ms-reader"}, + {".lpk", "application/octet-stream"}, + {".lsf", "video/x-la-asf"}, + {".lsx", "video/x-la-asf"}, + {".lzh", "application/octet-stream"}, + {".m13", "application/x-msmediaview"}, + {".m14", "application/x-msmediaview"}, + {".m1v", "video/mpeg"}, + {".m2ts", "video/vnd.dlna.mpeg-tts"}, + {".m3u", "audio/x-mpegurl"}, + {".m4a", "audio/mp4"}, + {".m4v", "video/mp4"}, + {".man", "application/x-troff-man"}, + {".manifest", "application/x-ms-manifest"}, + {".map", "text/plain"}, + {".markdown", "text/markdown"}, + {".md", "text/markdown"}, + {".mdb", "application/x-msaccess"}, + {".mdp", "application/octet-stream"}, + {".me", "application/x-troff-me"}, + {".mht", "message/rfc822"}, + {".mhtml", "message/rfc822"}, + {".mid", "audio/mid"}, + {".midi", "audio/mid"}, + {".mix", "application/octet-stream"}, + {".mmf", "application/x-smaf"}, + {".mno", "text/xml"}, + {".mny", "application/x-msmoney"}, + {".mov", "video/quicktime"}, + {".movie", "video/x-sgi-movie"}, + {".mp2", "video/mpeg"}, + {".mp3", "audio/mpeg"}, + {".mp4", "video/mp4"}, + {".mp4v", "video/mp4"}, + {".mpa", "video/mpeg"}, + {".mpe", "video/mpeg"}, + {".mpeg", "video/mpeg"}, + {".mpg", "video/mpeg"}, + {".mpp", "application/vnd.ms-project"}, + {".mpv2", "video/mpeg"}, + {".ms", "application/x-troff-ms"}, + {".msi", "application/octet-stream"}, + {".mso", "application/octet-stream"}, + {".mvb", "application/x-msmediaview"}, + {".mvc", "application/x-miva-compiled"}, + {".nc", "application/x-netcdf"}, + {".nsc", "video/x-ms-asf"}, + {".nws", "message/rfc822"}, + {".ocx", "application/octet-stream"}, + {".oda", "application/oda"}, + {".odc", "text/x-ms-odc"}, + {".ods", "application/oleobject"}, + {".oga", "audio/ogg"}, + {".ogg", "video/ogg"}, + {".ogv", "video/ogg"}, + {".ogx", "application/ogg"}, + {".one", "application/onenote"}, + {".onea", "application/onenote"}, + {".onetoc", "application/onenote"}, + {".onetoc2", "application/onenote"}, + {".onetmp", "application/onenote"}, + {".onepkg", "application/onenote"}, + {".osdx", "application/opensearchdescription+xml"}, + {".otf", "font/otf"}, + {".p10", "application/pkcs10"}, + {".p12", "application/x-pkcs12"}, + {".p7b", "application/x-pkcs7-certificates"}, + {".p7c", "application/pkcs7-mime"}, + {".p7m", "application/pkcs7-mime"}, + {".p7r", "application/x-pkcs7-certreqresp"}, + {".p7s", "application/pkcs7-signature"}, + {".pbm", "image/x-portable-bitmap"}, + {".pcx", "application/octet-stream"}, + {".pcz", "application/octet-stream"}, + {".pdf", "application/pdf"}, + {".pfb", "application/octet-stream"}, + {".pfm", "application/octet-stream"}, + {".pfx", "application/x-pkcs12"}, + {".pgm", "image/x-portable-graymap"}, + {".pko", "application/vnd.ms-pki.pko"}, + {".pma", "application/x-perfmon"}, + {".pmc", "application/x-perfmon"}, + {".pml", "application/x-perfmon"}, + {".pmr", "application/x-perfmon"}, + {".pmw", "application/x-perfmon"}, + {".png", "image/png"}, + {".pnm", "image/x-portable-anymap"}, + {".pnz", "image/png"}, + {".pot", "application/vnd.ms-powerpoint"}, + {".potm", "application/vnd.ms-powerpoint.template.macroEnabled.12"}, + {".potx", "application/vnd.openxmlformats-officedocument.presentationml.template"}, + {".ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12"}, + {".ppm", "image/x-portable-pixmap"}, + {".pps", "application/vnd.ms-powerpoint"}, + {".ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12"}, + {".ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow"}, + {".ppt", "application/vnd.ms-powerpoint"}, + {".pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12"}, + {".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"}, + {".prf", "application/pics-rules"}, + {".prm", "application/octet-stream"}, + {".prx", "application/octet-stream"}, + {".ps", "application/postscript"}, + {".psd", "application/octet-stream"}, + {".psm", "application/octet-stream"}, + {".psp", "application/octet-stream"}, + {".pub", "application/x-mspublisher"}, + {".qt", "video/quicktime"}, + {".qtl", "application/x-quicktimeplayer"}, + {".qxd", "application/octet-stream"}, + {".ra", "audio/x-pn-realaudio"}, + {".ram", "audio/x-pn-realaudio"}, + {".rar", "application/octet-stream"}, + {".ras", "image/x-cmu-raster"}, + {".rf", "image/vnd.rn-realflash"}, + {".rgb", "image/x-rgb"}, + {".rm", "application/vnd.rn-realmedia"}, + {".rmi", "audio/mid"}, + {".roff", "application/x-troff"}, + {".rpm", "audio/x-pn-realaudio-plugin"}, + {".rtf", "application/rtf"}, + {".rtx", "text/richtext"}, + {".scd", "application/x-msschedule"}, + {".sct", "text/scriptlet"}, + {".sea", "application/octet-stream"}, + {".setpay", "application/set-payment-initiation"}, + {".setreg", "application/set-registration-initiation"}, + {".sgml", "text/sgml"}, + {".sh", "application/x-sh"}, + {".shar", "application/x-shar"}, + {".sit", "application/x-stuffit"}, + {".sldm", "application/vnd.ms-powerpoint.slide.macroEnabled.12"}, + {".sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide"}, + {".smd", "audio/x-smd"}, + {".smi", "application/octet-stream"}, + {".smx", "audio/x-smd"}, + {".smz", "audio/x-smd"}, + {".snd", "audio/basic"}, + {".snp", "application/octet-stream"}, + {".spc", "application/x-pkcs7-certificates"}, + {".spl", "application/futuresplash"}, + {".spx", "audio/ogg"}, + {".src", "application/x-wais-source"}, + {".ssm", "application/streamingmedia"}, + {".sst", "application/vnd.ms-pki.certstore"}, + {".stl", "application/vnd.ms-pki.stl"}, + {".sv4cpio", "application/x-sv4cpio"}, + {".sv4crc", "application/x-sv4crc"}, + {".svg", "image/svg+xml"}, + {".svgz", "image/svg+xml"}, + {".swf", "application/x-shockwave-flash"}, + {".t", "application/x-troff"}, + {".tar", "application/x-tar"}, + {".tcl", "application/x-tcl"}, + {".tex", "application/x-tex"}, + {".texi", "application/x-texinfo"}, + {".texinfo", "application/x-texinfo"}, + {".tgz", "application/x-compressed"}, + {".thmx", "application/vnd.ms-officetheme"}, + {".thn", "application/octet-stream"}, + {".tif", "image/tiff"}, + {".tiff", "image/tiff"}, + {".toc", "application/octet-stream"}, + {".tr", "application/x-troff"}, + {".trm", "application/x-msterminal"}, + {".ts", "video/vnd.dlna.mpeg-tts"}, + {".tsv", "text/tab-separated-values"}, + {".ttc", "application/x-font-ttf"}, + {".ttf", "application/x-font-ttf"}, + {".tts", "video/vnd.dlna.mpeg-tts"}, + {".txt", "text/plain"}, + {".u32", "application/octet-stream"}, + {".uls", "text/iuls"}, + {".ustar", "application/x-ustar"}, + {".vbs", "text/vbscript"}, + {".vcf", "text/x-vcard"}, + {".vcs", "text/plain"}, + {".vdx", "application/vnd.ms-visio.viewer"}, + {".vml", "text/xml"}, + {".vsd", "application/vnd.visio"}, + {".vss", "application/vnd.visio"}, + {".vst", "application/vnd.visio"}, + {".vsto", "application/x-ms-vsto"}, + {".vsw", "application/vnd.visio"}, + {".vsx", "application/vnd.visio"}, + {".vtx", "application/vnd.visio"}, + {".wasm", "application/wasm"}, + {".wav", "audio/wav"}, + {".wax", "audio/x-ms-wax"}, + {".wbmp", "image/vnd.wap.wbmp"}, + {".wcm", "application/vnd.ms-works"}, + {".wdb", "application/vnd.ms-works"}, + {".webm", "video/webm"}, + {".webp", "image/webp"}, + {".wks", "application/vnd.ms-works"}, + {".wm", "video/x-ms-wm"}, + {".wma", "audio/x-ms-wma"}, + {".wmd", "application/x-ms-wmd"}, + {".wmf", "application/x-msmetafile"}, + {".wml", "text/vnd.wap.wml"}, + {".wmlc", "application/vnd.wap.wmlc"}, + {".wmls", "text/vnd.wap.wmlscript"}, + {".wmlsc", "application/vnd.wap.wmlscriptc"}, + {".wmp", "video/x-ms-wmp"}, + {".wmv", "video/x-ms-wmv"}, + {".wmx", "video/x-ms-wmx"}, + {".wmz", "application/x-ms-wmz"}, + {".woff", "application/font-woff"}, // https://www.w3.org/TR/WOFF/#appendix-b + {".woff2", "font/woff2"}, // https://www.w3.org/TR/WOFF2/#IMT + {".wps", "application/vnd.ms-works"}, + {".wri", "application/x-mswrite"}, + {".wrl", "x-world/x-vrml"}, + {".wrz", "x-world/x-vrml"}, + {".wsdl", "text/xml"}, + {".wtv", "video/x-ms-wtv"}, + {".wvx", "video/x-ms-wvx"}, + {".x", "application/directx"}, + {".xaf", "x-world/x-vrml"}, + {".xaml", "application/xaml+xml"}, + {".xap", "application/x-silverlight-app"}, + {".xbap", "application/x-ms-xbap"}, + {".xbm", "image/x-xbitmap"}, + {".xdr", "text/plain"}, + {".xht", "application/xhtml+xml"}, + {".xhtml", "application/xhtml+xml"}, + {".xla", "application/vnd.ms-excel"}, + {".xlam", "application/vnd.ms-excel.addin.macroEnabled.12"}, + {".xlc", "application/vnd.ms-excel"}, + {".xlm", "application/vnd.ms-excel"}, + {".xls", "application/vnd.ms-excel"}, + {".xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12"}, + {".xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12"}, + {".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, + {".xlt", "application/vnd.ms-excel"}, + {".xltm", "application/vnd.ms-excel.template.macroEnabled.12"}, + {".xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template"}, + {".xlw", "application/vnd.ms-excel"}, + {".xml", "text/xml"}, + {".xof", "x-world/x-vrml"}, + {".xpm", "image/x-xpixmap"}, + {".xps", "application/vnd.ms-xpsdocument"}, + {".xsd", "text/xml"}, + {".xsf", "text/xml"}, + {".xsl", "text/xml"}, + {".xslt", "text/xml"}, + {".xsn", "application/octet-stream"}, + {".xtp", "application/octet-stream"}, + {".xwd", "image/x-xwindowdump"}, + {".z", "application/x-compress"}, + {".zip", "application/x-zip-compressed"}, + }; + AllowedExtraWebContentFolders = new List { "/Pages", @@ -16,18 +403,7 @@ namespace Volo.Abp.AspNetCore.VirtualFileSystem "/Themes" }; - AllowedExtraWebContentFileExtensions = new List - { - ".js", - ".css", - ".png", - ".jpg", - ".jpeg", - ".woff", - ".woff2", - ".tff", - ".otf" - }; + AllowedExtraWebContentFileExtensions = ContentTypeMaps.Select(x => x.Key).ToList(); } } } diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/AbpFileExtensionContentTypeProvider.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/AbpFileExtensionContentTypeProvider.cs new file mode 100644 index 0000000000..82ca1c5539 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/VirtualFileSystem/AbpFileExtensionContentTypeProvider.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.VirtualFileSystem +{ + public class AbpFileExtensionContentTypeProvider : IContentTypeProvider, ITransientDependency + { + protected AbpAspNetCoreContentOptions Options { get; } + + public AbpFileExtensionContentTypeProvider(IOptions abpAspNetCoreContentOptions) + { + Options = abpAspNetCoreContentOptions.Value; + } + + public bool TryGetContentType(string subpath, out string contentType) + { + var extension = GetExtension(subpath); + if (extension == null) + { + contentType = null; + return false; + } + + return Options.ContentTypeMaps.TryGetValue(extension, out contentType); + } + + protected virtual string GetExtension(string path) + { + if (string.IsNullOrWhiteSpace(path)) + { + return null; + } + + var index = path.LastIndexOf('.'); + return index < 0 ? null : path.Substring(index); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/WebClientInfo/HttpContextWebClientInfoProvider.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/WebClientInfo/HttpContextWebClientInfoProvider.cs new file mode 100644 index 0000000000..503771c938 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/WebClientInfo/HttpContextWebClientInfoProvider.cs @@ -0,0 +1,43 @@ +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.WebClientInfo +{ + public class HttpContextWebClientInfoProvider : IWebClientInfoProvider, ITransientDependency + { + protected ILogger Logger { get; } + protected IHttpContextAccessor HttpContextAccessor { get; } + + public HttpContextWebClientInfoProvider( + ILogger logger, + IHttpContextAccessor httpContextAccessor) + { + Logger = logger; + HttpContextAccessor = httpContextAccessor; + } + + public string BrowserInfo => GetBrowserInfo(); + + public string ClientIpAddress => GetClientIpAddress(); + + protected virtual string GetBrowserInfo() + { + return HttpContextAccessor.HttpContext?.Request?.Headers?["User-Agent"]; + } + + protected virtual string GetClientIpAddress() + { + try + { + return HttpContextAccessor.HttpContext?.Connection?.RemoteIpAddress?.ToString(); + } + catch (Exception ex) + { + Logger.LogException(ex, LogLevel.Warning); + return null; + } + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/WebClientInfo/IWebClientInfoProvider.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/WebClientInfo/IWebClientInfoProvider.cs new file mode 100644 index 0000000000..3a15ac2f93 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/WebClientInfo/IWebClientInfoProvider.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.AspNetCore.WebClientInfo +{ + public interface IWebClientInfoProvider + { + string BrowserInfo { get; } + + string ClientIpAddress { get; } + } +} diff --git a/framework/src/Volo.Abp.Authorization/Volo.Abp.Authorization.csproj b/framework/src/Volo.Abp.Authorization/Volo.Abp.Authorization.csproj index b8ff62572b..a7e76714c2 100644 --- a/framework/src/Volo.Abp.Authorization/Volo.Abp.Authorization.csproj +++ b/framework/src/Volo.Abp.Authorization/Volo.Abp.Authorization.csproj @@ -15,7 +15,7 @@ - + diff --git a/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj b/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj index 2fb39be54f..bd5cffe848 100644 --- a/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj +++ b/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj @@ -21,7 +21,7 @@ - + diff --git a/framework/src/Volo.Abp.Autofac/Volo.Abp.Autofac.csproj b/framework/src/Volo.Abp.Autofac/Volo.Abp.Autofac.csproj index 3c5c275117..85a8541e26 100644 --- a/framework/src/Volo.Abp.Autofac/Volo.Abp.Autofac.csproj +++ b/framework/src/Volo.Abp.Autofac/Volo.Abp.Autofac.csproj @@ -17,7 +17,7 @@ - + diff --git a/framework/src/Volo.Abp.BlobStoring.Aliyun/FodyWeavers.xml b/framework/src/Volo.Abp.BlobStoring.Aliyun/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aliyun/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlobStoring.Aliyun/FodyWeavers.xsd b/framework/src/Volo.Abp.BlobStoring.Aliyun/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aliyun/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.BlobStoring.Aliyun/Volo.Abp.BlobStoring.Aliyun.csproj b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo.Abp.BlobStoring.Aliyun.csproj new file mode 100644 index 0000000000..c9b29dc83f --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo.Abp.BlobStoring.Aliyun.csproj @@ -0,0 +1,27 @@ + + + + + + + netstandard2.0 + Volo.Abp.BlobStoring.Aliyun + Volo.Abp.BlobStoring.Aliyun + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunModule.cs b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunModule.cs new file mode 100644 index 0000000000..c14dd193fc --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunModule.cs @@ -0,0 +1,14 @@ +using Volo.Abp.Caching; +using Volo.Abp.Modularity; + +namespace Volo.Abp.BlobStoring.Aliyun +{ + [DependsOn( + typeof(AbpBlobStoringModule), + typeof(AbpCachingModule) + )] + public class AbpBlobStoringAliyunModule: AbpModule + { + + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobContainerConfigurationExtensions.cs b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobContainerConfigurationExtensions.cs new file mode 100644 index 0000000000..7802173b23 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobContainerConfigurationExtensions.cs @@ -0,0 +1,25 @@ +using System; + +namespace Volo.Abp.BlobStoring.Aliyun +{ + public static class AliyunBlobContainerConfigurationExtensions + { + public static AliyunBlobProviderConfiguration GetAliyunConfiguration( + this BlobContainerConfiguration containerConfiguration) + { + return new AliyunBlobProviderConfiguration(containerConfiguration); + } + + public static BlobContainerConfiguration UseAliyun( + this BlobContainerConfiguration containerConfiguration, + Action aliyunConfigureAction) + { + containerConfiguration.ProviderType = typeof(AliyunBlobProvider); + containerConfiguration.NamingNormalizers.TryAdd(); + + aliyunConfigureAction(new AliyunBlobProviderConfiguration(containerConfiguration)); + + return containerConfiguration; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobNamingNormalizer.cs b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobNamingNormalizer.cs new file mode 100644 index 0000000000..b456d7abb6 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobNamingNormalizer.cs @@ -0,0 +1,51 @@ +using System.Text.RegularExpressions; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.BlobStoring.Aliyun +{ + public class AliyunBlobNamingNormalizer : IBlobNamingNormalizer, ITransientDependency + { + /// + /// Container names can contain only letters, numbers, and the dash (-) character + /// they can't start or end with the dash (-) character + /// Container names must be from 3 through 63 characters long + /// + public virtual string NormalizeContainerName(string containerName) + { + // All letters in a container name must be lowercase. + containerName = containerName.ToLower(); + + // Container names can contain only letters, numbers, and the dash (-) character. + containerName = Regex.Replace(containerName, "[^a-z0-9-]", string.Empty); + + // Every dash (-) character must be immediately preceded and followed by a letter or number; + // consecutive dashes are not permitted in container names. + // Container names must start or end with a letter or number + containerName = Regex.Replace(containerName, "-{2,}", "-"); + containerName = Regex.Replace(containerName, "^-", string.Empty); + containerName = Regex.Replace(containerName, "-$", string.Empty); + + // Container names must be from 3 through 63 characters long. + if (containerName.Length < 3) + { + var length = containerName.Length; + for (var i = 0; i < 3 - length; i++) + { + containerName += "0"; + } + } + + if (containerName.Length > 63) + { + containerName = containerName.Substring(0, 63); + } + + return containerName; + } + + public virtual string NormalizeBlobName(string blobName) + { + return blobName; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs new file mode 100644 index 0000000000..237465ef91 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProvider.cs @@ -0,0 +1,107 @@ +using Aliyun.OSS; +using System; +using System.IO; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.BlobStoring.Aliyun +{ + public class AliyunBlobProvider : BlobProviderBase, ITransientDependency + { + protected IOssClientFactory OssClientFactory { get; } + protected IAliyunBlobNameCalculator AliyunBlobNameCalculator { get; } + + public AliyunBlobProvider( + IOssClientFactory ossClientFactory, + IAliyunBlobNameCalculator aliyunBlobNameCalculator) + { + OssClientFactory = ossClientFactory; + AliyunBlobNameCalculator = aliyunBlobNameCalculator; + } + + protected virtual IOss GetOssClient(BlobContainerConfiguration blobContainerConfiguration) + { + var aliyunConfig = blobContainerConfiguration.GetAliyunConfiguration(); + return OssClientFactory.Create(aliyunConfig); + } + + protected virtual IOss GetOssClient(AliyunBlobProviderConfiguration aliyunConfig) + { + return OssClientFactory.Create(aliyunConfig); + } + + + public override Task SaveAsync(BlobProviderSaveArgs args) + { + var containerName = GetContainerName(args); + var blobName = AliyunBlobNameCalculator.Calculate(args); + var aliyunConfig = args.Configuration.GetAliyunConfiguration(); + var ossClient = GetOssClient(aliyunConfig); + if (!args.OverrideExisting && BlobExistsAsync(ossClient, containerName, blobName)) + { + throw new BlobAlreadyExistsException($"Saving BLOB '{args.BlobName}' does already exists in the container '{containerName}'! Set {nameof(args.OverrideExisting)} if it should be overwritten."); + } + if (aliyunConfig.CreateContainerIfNotExists) + { + if (!ossClient.DoesBucketExist(containerName)) + { + ossClient.CreateBucket(containerName); + } + } + ossClient.PutObject(containerName, blobName, args.BlobStream); + return Task.CompletedTask; + } + + public override Task DeleteAsync(BlobProviderDeleteArgs args) + { + var containerName = GetContainerName(args); + var blobName = AliyunBlobNameCalculator.Calculate(args); + var ossClient = GetOssClient(args.Configuration); + if(!BlobExistsAsync(ossClient, containerName, blobName)) + { + return Task.FromResult(false); + } + ossClient.DeleteObject(containerName, blobName); + return Task.FromResult(true); + } + + public override Task ExistsAsync(BlobProviderExistsArgs args) + { + var containerName = GetContainerName(args); + var blobName = AliyunBlobNameCalculator.Calculate(args); + var ossClient = GetOssClient(args.Configuration); + return Task.FromResult(BlobExistsAsync(ossClient, containerName, blobName)); + } + + public override async Task GetOrNullAsync(BlobProviderGetArgs args) + { + var containerName = GetContainerName(args); + var blobName = AliyunBlobNameCalculator.Calculate(args); + var ossClient = GetOssClient(args.Configuration); + if (!BlobExistsAsync(ossClient, containerName, blobName)) + { + return null; + } + var result = ossClient.GetObject(containerName, blobName); + var memoryStream = new MemoryStream(); + await result.Content.CopyToAsync(memoryStream); + return memoryStream; + } + + private static string GetContainerName(BlobProviderArgs args) + { + var configuration = args.Configuration.GetAliyunConfiguration(); + return configuration.ContainerName.IsNullOrWhiteSpace() + ? args.ContainerName + : configuration.ContainerName; + } + + private bool BlobExistsAsync(IOss ossClient,string containerName, string blobName) + { + // Make sure Blob Container exists. + return ossClient.DoesBucketExist(containerName) && + ossClient.DoesObjectExist(containerName, blobName); + } + + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfiguration.cs b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfiguration.cs new file mode 100644 index 0000000000..757ad4b54c --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfiguration.cs @@ -0,0 +1,112 @@ +using System; + +namespace Volo.Abp.BlobStoring.Aliyun +{ + /// + /// Sub-account access to OSS or STS temporary authorization to access OSS + /// + public class AliyunBlobProviderConfiguration + { + public string AccessKeyId + { + get => _containerConfiguration.GetConfiguration(AliyunBlobProviderConfigurationNames.AccessKeyId); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.AccessKeyId, Check.NotNullOrWhiteSpace(value, nameof(value))); + } + + public string AccessKeySecret + { + get => _containerConfiguration.GetConfiguration(AliyunBlobProviderConfigurationNames.AccessKeySecret); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.AccessKeySecret, Check.NotNullOrWhiteSpace(value, nameof(value))); + } + + public string Endpoint + { + get => _containerConfiguration.GetConfiguration(AliyunBlobProviderConfigurationNames.Endpoint); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.Endpoint, Check.NotNullOrWhiteSpace(value, nameof(value))); + } + + public bool UseSecurityTokenService + { + get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.UseSecurityTokenService, false); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.UseSecurityTokenService, value); + } + + public string RegionId + { + get => _containerConfiguration.GetConfiguration(AliyunBlobProviderConfigurationNames.RegionId); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.RegionId, value); + } + + /// + /// acs:ram::$accountID:role/$roleName + /// + public string RoleArn + { + get => _containerConfiguration.GetConfiguration(AliyunBlobProviderConfigurationNames.RoleArn); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.RoleArn, value); + } + + /// + /// The name used to identify the temporary access credentials, it is recommended to use different application users to distinguish. + /// + public string RoleSessionName + { + get => _containerConfiguration.GetConfiguration(AliyunBlobProviderConfigurationNames.RoleSessionName); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.RoleSessionName, value); + } + + /// + /// Set the validity period of the temporary access credential, the unit is s, the minimum is 900, and the maximum is 3600. + /// + public int DurationSeconds + { + get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.DurationSeconds, 0); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.DurationSeconds, value); + } + + /// + /// If policy is empty, the user will get all permissions under this role + /// + public string Policy + { + get => _containerConfiguration.GetConfiguration(AliyunBlobProviderConfigurationNames.Policy); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.Policy, value); + } + + /// + /// This name may only contain lowercase letters, numbers, and hyphens, and must begin with a letter or a number. + /// Each hyphen must be preceded and followed by a non-hyphen character. + /// The name must also be between 3 and 63 characters long. + /// If this parameter is not specified, the ContainerName of the will be used. + /// + public string ContainerName + { + get => _containerConfiguration.GetConfiguration(AliyunBlobProviderConfigurationNames.ContainerName); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.ContainerName, Check.NotNullOrWhiteSpace(value, nameof(value))); + } + + /// + /// Default value: false. + /// + public bool CreateContainerIfNotExists + { + get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.CreateContainerIfNotExists, false); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.CreateContainerIfNotExists, value); + } + + private readonly string _temporaryCredentialsCacheKey; + public string TemporaryCredentialsCacheKey + { + get => _containerConfiguration.GetConfigurationOrDefault(AliyunBlobProviderConfigurationNames.TemporaryCredentialsCacheKey, _temporaryCredentialsCacheKey); + set => _containerConfiguration.SetConfiguration(AliyunBlobProviderConfigurationNames.TemporaryCredentialsCacheKey, value); + } + + private readonly BlobContainerConfiguration _containerConfiguration; + + public AliyunBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration) + { + _containerConfiguration = containerConfiguration; + _temporaryCredentialsCacheKey = Guid.NewGuid().ToString("N"); + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfigurationNames.cs b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfigurationNames.cs new file mode 100644 index 0000000000..0f8585f12b --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobProviderConfigurationNames.cs @@ -0,0 +1,18 @@ +namespace Volo.Abp.BlobStoring.Aliyun +{ + public class AliyunBlobProviderConfigurationNames + { + public const string AccessKeyId = "Aliyun.AccessKeyId"; + public const string AccessKeySecret = "Aliyun.AccessKeySecret"; + public const string Endpoint = "Aliyun.Endpoint"; + public const string UseSecurityTokenService = "Aliyun.UseSecurityTokenService"; + public const string RegionId = "Aliyun.RegionId"; + public const string RoleArn = "Aliyun.RoleArn"; + public const string RoleSessionName = "Aliyun.RoleSessionName"; + public const string DurationSeconds = "Aliyun.DurationSeconds"; + public const string Policy = "Aliyun.Policy"; + public const string ContainerName = "Aliyun.ContainerName"; + public const string CreateContainerIfNotExists = "Aliyun.CreateContainerIfNotExists"; + public const string TemporaryCredentialsCacheKey = "Aliyun.TemporaryCredentialsCacheKey"; + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunTemporaryCredentialsCacheItem.cs b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunTemporaryCredentialsCacheItem.cs new file mode 100644 index 0000000000..508d94b743 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunTemporaryCredentialsCacheItem.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Volo.Abp.Caching; + +namespace Volo.Abp.BlobStoring.Aliyun +{ + [Serializable] + public class AliyunTemporaryCredentialsCacheItem + { + public string AccessKeyId { get; set; } + + public string AccessKeySecret { get; set; } + + public string SecurityToken { get; set; } + + public AliyunTemporaryCredentialsCacheItem() + { + + } + + public AliyunTemporaryCredentialsCacheItem(string accessKeyId,string accessKeySecret,string securityToken) + { + AccessKeyId = accessKeyId; + AccessKeySecret = accessKeySecret; + SecurityToken = securityToken; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/DefaultAliyunBlobNameCalculator.cs b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/DefaultAliyunBlobNameCalculator.cs new file mode 100644 index 0000000000..e6b3e85ff6 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/DefaultAliyunBlobNameCalculator.cs @@ -0,0 +1,22 @@ +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.BlobStoring.Aliyun +{ + public class DefaultAliyunBlobNameCalculator: IAliyunBlobNameCalculator, ITransientDependency + { + protected ICurrentTenant CurrentTenant { get; } + + public DefaultAliyunBlobNameCalculator(ICurrentTenant currentTenant) + { + CurrentTenant = currentTenant; + } + + public virtual string Calculate(BlobProviderArgs args) + { + return CurrentTenant.Id == null + ? $"host/{args.BlobName}" + : $"tenants/{CurrentTenant.Id.Value:D}/{args.BlobName}"; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/DefaultOssClientFactory.cs b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/DefaultOssClientFactory.cs new file mode 100644 index 0000000000..ea0c4cbf28 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/DefaultOssClientFactory.cs @@ -0,0 +1,100 @@ +using Aliyun.Acs.Core; +using Aliyun.Acs.Core.Auth.Sts; +using Aliyun.Acs.Core.Http; +using Aliyun.Acs.Core.Profile; +using Aliyun.OSS; +using Microsoft.Extensions.Caching.Distributed; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Security.Encryption; +using static Aliyun.Acs.Core.Auth.Sts.AssumeRoleResponse; + +namespace Volo.Abp.BlobStoring.Aliyun +{ + /// + /// Sub-account access to OSS or STS temporary authorization to access OSS + /// + public class DefaultOssClientFactory : IOssClientFactory, ITransientDependency + { + protected IDistributedCache Cache { get; } + + protected IStringEncryptionService StringEncryptionService { get; } + + public DefaultOssClientFactory( + IDistributedCache cache, + IStringEncryptionService stringEncryptionService) + { + Cache = cache; + StringEncryptionService = stringEncryptionService; + } + + public virtual IOss Create(AliyunBlobProviderConfiguration configuration) + { + Check.NotNullOrWhiteSpace(configuration.AccessKeyId, nameof(configuration.AccessKeyId)); + Check.NotNullOrWhiteSpace(configuration.AccessKeySecret, nameof(configuration.AccessKeySecret)); + Check.NotNullOrWhiteSpace(configuration.Endpoint, nameof(configuration.Endpoint)); + if (configuration.UseSecurityTokenService) + { + //STS temporary authorization to access OSS + return GetSecurityTokenClient(configuration); + } + //Sub-account + return new OssClient(configuration.Endpoint, configuration.AccessKeyId, configuration.AccessKeySecret); + } + + protected virtual IOss GetSecurityTokenClient(AliyunBlobProviderConfiguration configuration) + { + Check.NotNullOrWhiteSpace(configuration.RoleArn, nameof(configuration.RoleArn)); + Check.NotNullOrWhiteSpace(configuration.RoleSessionName, nameof(configuration.RoleSessionName)); + var cacheItem = Cache.Get(configuration.TemporaryCredentialsCacheKey); + if (cacheItem == null) + { + IClientProfile profile = DefaultProfile.GetProfile( + configuration.RegionId, + configuration.AccessKeyId, + configuration.AccessKeySecret); + DefaultAcsClient client = new DefaultAcsClient(profile); + AssumeRoleRequest request = new AssumeRoleRequest + { + AcceptFormat = FormatType.JSON, + //eg:acs:ram::$accountID:role/$roleName + RoleArn = configuration.RoleArn, + RoleSessionName = configuration.RoleSessionName, + //Set the validity period of the temporary access credential, the unit is s, the minimum is 900, and the maximum is 3600. default 3600 + DurationSeconds = configuration.DurationSeconds, + //Set additional permission policy of Token; when acquiring Token, further reduce the permission of Token by setting an additional permission policy + Policy = configuration.Policy.IsNullOrEmpty() ? null : configuration.Policy, + }; + var response = client.GetAcsResponse(request); + cacheItem = SetTemporaryCredentialsCache(configuration, response.Credentials); + } + return new OssClient( + configuration.Endpoint, + StringEncryptionService.Decrypt(cacheItem.AccessKeyId), + StringEncryptionService.Decrypt(cacheItem.AccessKeySecret), + StringEncryptionService.Decrypt(cacheItem.SecurityToken)); + } + + private AliyunTemporaryCredentialsCacheItem SetTemporaryCredentialsCache( + AliyunBlobProviderConfiguration configuration, + AssumeRole_Credentials credentials) + { + var temporaryCredentialsCache = new AliyunTemporaryCredentialsCacheItem( + StringEncryptionService.Encrypt(credentials.AccessKeyId), + StringEncryptionService.Encrypt(credentials.AccessKeySecret), + StringEncryptionService.Encrypt(credentials.SecurityToken)); + + Cache.Set(configuration.TemporaryCredentialsCacheKey, temporaryCredentialsCache, + new DistributedCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(configuration.DurationSeconds - 10) + }); + + return temporaryCredentialsCache; + } + + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/IAliyunBlobNameCalculator.cs b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/IAliyunBlobNameCalculator.cs new file mode 100644 index 0000000000..755e6beaca --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/IAliyunBlobNameCalculator.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.BlobStoring.Aliyun +{ + public interface IAliyunBlobNameCalculator + { + string Calculate(BlobProviderArgs args); + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/IOssClientFactory.cs b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/IOssClientFactory.cs new file mode 100644 index 0000000000..8410383012 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/IOssClientFactory.cs @@ -0,0 +1,9 @@ +using Aliyun.OSS; + +namespace Volo.Abp.BlobStoring.Aliyun +{ + public interface IOssClientFactory + { + IOss Create(AliyunBlobProviderConfiguration args); + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/FodyWeavers.xml b/framework/src/Volo.Abp.BlobStoring.Aws/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/FodyWeavers.xsd b/framework/src/Volo.Abp.BlobStoring.Aws/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/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.BlobStoring.Aws/Volo.Abp.BlobStoring.Aws.csproj b/framework/src/Volo.Abp.BlobStoring.Aws/Volo.Abp.BlobStoring.Aws.csproj new file mode 100644 index 0000000000..d30601d1e8 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo.Abp.BlobStoring.Aws.csproj @@ -0,0 +1,24 @@ + + + + + + + netstandard2.0 + false + false + false + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAwsModule.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAwsModule.cs new file mode 100644 index 0000000000..28426321c8 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAwsModule.cs @@ -0,0 +1,14 @@ +using Volo.Abp.Caching; +using Volo.Abp.Modularity; + +namespace Volo.Abp.BlobStoring.Aws +{ + [DependsOn(typeof(AbpBlobStoringModule), + typeof(AbpCachingModule))] + public class AbpBlobStoringAwsModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobContainerConfigurationExtensions.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobContainerConfigurationExtensions.cs new file mode 100644 index 0000000000..d63e408aab --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobContainerConfigurationExtensions.cs @@ -0,0 +1,25 @@ +using System; + +namespace Volo.Abp.BlobStoring.Aws +{ + public static class AwsBlobContainerConfigurationExtensions + { + public static AwsBlobProviderConfiguration GetAwsConfiguration( + this BlobContainerConfiguration containerConfiguration) + { + return new AwsBlobProviderConfiguration(containerConfiguration); + } + + public static BlobContainerConfiguration UseAws( + this BlobContainerConfiguration containerConfiguration, + Action awsConfigureAction) + { + containerConfiguration.ProviderType = typeof(AwsBlobProvider); + containerConfiguration.NamingNormalizers.TryAdd(); + + awsConfigureAction(new AwsBlobProviderConfiguration(containerConfiguration)); + + return containerConfiguration; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobNamingNormalizer.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobNamingNormalizer.cs new file mode 100644 index 0000000000..cd8eca021e --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobNamingNormalizer.cs @@ -0,0 +1,52 @@ +using System.Text.RegularExpressions; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.BlobStoring.Aws +{ + public class AwsBlobNamingNormalizer : IBlobNamingNormalizer, ITransientDependency + { + /// + ///https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html + /// + public virtual string NormalizeContainerName(string containerName) + { + // All letters in a container name must be lowercase. + containerName = containerName.ToLower(); + + // Container names can contain only letters, numbers, and the dash (-) character. + containerName = Regex.Replace(containerName, "[^a-z0-9-]", string.Empty); + + // Every dash (-) character must be immediately preceded and followed by a letter or number; + // consecutive dashes are not permitted in container names. + // Container names must start or end with a letter or number + containerName = Regex.Replace(containerName, "-{2,}", "-"); + containerName = Regex.Replace(containerName, "^-", string.Empty); + containerName = Regex.Replace(containerName, "-$", string.Empty); + + // Container names must be from 3 through 63 characters long. + if (containerName.Length < 3) + { + var length = containerName.Length; + for (var i = 0; i < 3 - length; i++) + { + containerName += "0"; + } + } + + if (containerName.Length > 63) + { + containerName = containerName.Substring(0, 63); + } + + return containerName; + } + + /// + /// https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html + /// + public virtual string NormalizeBlobName(string blobName) + { + return blobName; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProvider.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProvider.cs new file mode 100644 index 0000000000..6acb91aea9 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProvider.cs @@ -0,0 +1,158 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Amazon.S3; +using Amazon.S3.Model; +using Amazon.S3.Util; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.BlobStoring.Aws +{ + public class AwsBlobProvider : BlobProviderBase, ITransientDependency + { + protected IAwsBlobNameCalculator AwsBlobNameCalculator { get; } + protected IAmazonS3ClientFactory AmazonS3ClientFactory { get; } + + public AwsBlobProvider(IAwsBlobNameCalculator awsBlobNameCalculator, + IAmazonS3ClientFactory amazonS3ClientFactory) + { + AwsBlobNameCalculator = awsBlobNameCalculator; + AmazonS3ClientFactory = amazonS3ClientFactory; + } + + public override async Task SaveAsync(BlobProviderSaveArgs args) + { + var blobName = AwsBlobNameCalculator.Calculate(args); + var configuration = args.Configuration.GetAwsConfiguration(); + var containerName = GetContainerName(args); + + using (var amazonS3Client = await GetAmazonS3Client(args)) + { + if (!args.OverrideExisting && await BlobExistsAsync(amazonS3Client, containerName, blobName)) + { + throw new BlobAlreadyExistsException( + $"Saving BLOB '{args.BlobName}' does already exists in the container '{containerName}'! Set {nameof(args.OverrideExisting)} if it should be overwritten."); + } + + if (configuration.CreateContainerIfNotExists) + { + await CreateContainerIfNotExists(amazonS3Client, containerName); + } + + await amazonS3Client.PutObjectAsync(new PutObjectRequest + { + BucketName = containerName, + Key = blobName, + InputStream = args.BlobStream + }); + } + } + + public override async Task DeleteAsync(BlobProviderDeleteArgs args) + { + var blobName = AwsBlobNameCalculator.Calculate(args); + var containerName = GetContainerName(args); + + using (var amazonS3Client = await GetAmazonS3Client(args)) + { + if (!await BlobExistsAsync(amazonS3Client, containerName, blobName)) + { + return false; + } + + await amazonS3Client.DeleteObjectAsync(new DeleteObjectRequest + { + BucketName = containerName, + Key = blobName + }); + + return true; + } + } + + public override async Task ExistsAsync(BlobProviderExistsArgs args) + { + var blobName = AwsBlobNameCalculator.Calculate(args); + var containerName = GetContainerName(args); + + using (var amazonS3Client = await GetAmazonS3Client(args)) + { + return await BlobExistsAsync(amazonS3Client, containerName, blobName); + } + } + + public override async Task GetOrNullAsync(BlobProviderGetArgs args) + { + var blobName = AwsBlobNameCalculator.Calculate(args); + var containerName = GetContainerName(args); + + using (var amazonS3Client = await GetAmazonS3Client(args)) + { + if (!await BlobExistsAsync(amazonS3Client, containerName, blobName)) + { + return null; + } + + var response = await amazonS3Client.GetObjectAsync(new GetObjectRequest + { + BucketName = containerName, + Key = blobName + }); + + var memoryStream = new MemoryStream(); + await response.ResponseStream.CopyToAsync(memoryStream); + return memoryStream; + } + } + + protected virtual async Task GetAmazonS3Client(BlobProviderArgs args) + { + var configuration = args.Configuration.GetAwsConfiguration(); + return await AmazonS3ClientFactory.GetAmazonS3Client(configuration); + } + + private async Task BlobExistsAsync(AmazonS3Client amazonS3Client, string containerName, string blobName) + { + // Make sure Blob Container exists. + if (!await AmazonS3Util.DoesS3BucketExistV2Async(amazonS3Client, containerName)) + { + return false; + } + + try + { + await amazonS3Client.GetObjectMetadataAsync(containerName, blobName); + } + catch (Exception ex) + { + if (ex is AmazonS3Exception) + { + return false; + } + + throw; + } + + return true; + } + + protected virtual async Task CreateContainerIfNotExists(AmazonS3Client amazonS3Client, string containerName) + { + if (!await AmazonS3Util.DoesS3BucketExistV2Async(amazonS3Client, containerName)) + { + await amazonS3Client.PutBucketAsync(new PutBucketRequest + { + BucketName = containerName + }); + } + } + + private static string GetContainerName(BlobProviderArgs args) + { + var configuration = args.Configuration.GetAwsConfiguration(); + return configuration.ContainerName.IsNullOrWhiteSpace() + ? args.ContainerName + : configuration.ContainerName; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfiguration.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfiguration.cs new file mode 100644 index 0000000000..c2d3297f4e --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfiguration.cs @@ -0,0 +1,112 @@ +using System; + +namespace Volo.Abp.BlobStoring.Aws +{ + public class AwsBlobProviderConfiguration + { + public string AccessKeyId + { + get => _containerConfiguration.GetConfigurationOrDefault(AwsBlobProviderConfigurationNames.AccessKeyId); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.AccessKeyId, value); + } + + public string SecretAccessKey + { + get => _containerConfiguration.GetConfigurationOrDefault(AwsBlobProviderConfigurationNames.SecretAccessKey); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.SecretAccessKey, value); + } + + public bool UseCredentials + { + get => _containerConfiguration.GetConfigurationOrDefault(AwsBlobProviderConfigurationNames.UseCredentials,false); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.UseCredentials, value); + } + + public bool UseTemporaryCredentials + { + get => _containerConfiguration.GetConfigurationOrDefault(AwsBlobProviderConfigurationNames.UseTemporaryCredentials,false); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.UseTemporaryCredentials, value); + } + + public bool UseTemporaryFederatedCredentials + { + get => _containerConfiguration.GetConfigurationOrDefault(AwsBlobProviderConfigurationNames.UseTemporaryFederatedCredentials,false); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.UseTemporaryFederatedCredentials, value); + } + + public string ProfileName + { + get => _containerConfiguration.GetConfigurationOrDefault(AwsBlobProviderConfigurationNames.ProfileName); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.ProfileName, value); + } + + public string ProfilesLocation + { + get => _containerConfiguration.GetConfigurationOrDefault(AwsBlobProviderConfigurationNames.ProfilesLocation); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.ProfilesLocation, value); + } + + /// + /// Set the validity period of the temporary access credential, the unit is s, the minimum is 900, and the maximum is 129600. + /// + public int DurationSeconds + { + get => _containerConfiguration.GetConfigurationOrDefault(AwsBlobProviderConfigurationNames.DurationSeconds, 0); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.DurationSeconds, value); + } + + public string Name + { + get => _containerConfiguration.GetConfiguration(AwsBlobProviderConfigurationNames.Name); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.Name, value); + } + + public string Policy + { + get => _containerConfiguration.GetConfigurationOrDefault(AwsBlobProviderConfigurationNames.Policy); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.Policy, value); + } + + public string Region + { + get => _containerConfiguration.GetConfiguration(AwsBlobProviderConfigurationNames.Region); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.Region, Check.NotNull(value, nameof(value))); + } + + /// + /// This name may only contain lowercase letters, numbers, and hyphens, and must begin with a letter or a number. + /// Each hyphen must be preceded and followed by a non-hyphen character. + /// The name must also be between 3 and 63 characters long. + /// If this parameter is not specified, the ContainerName of the will be used. + /// + public string ContainerName + { + get => _containerConfiguration.GetConfiguration(AwsBlobProviderConfigurationNames.ContainerName); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.ContainerName, Check.NotNullOrWhiteSpace(value, nameof(value))); + } + + /// + /// Default value: false. + /// + public bool CreateContainerIfNotExists + { + get => _containerConfiguration.GetConfigurationOrDefault(AwsBlobProviderConfigurationNames.CreateContainerIfNotExists, false); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.CreateContainerIfNotExists, value); + } + + private readonly string _temporaryCredentialsCacheKey; + public string TemporaryCredentialsCacheKey + { + get => _containerConfiguration.GetConfigurationOrDefault(AwsBlobProviderConfigurationNames.TemporaryCredentialsCacheKey, _temporaryCredentialsCacheKey); + set => _containerConfiguration.SetConfiguration(AwsBlobProviderConfigurationNames.TemporaryCredentialsCacheKey, value); + } + + private readonly BlobContainerConfiguration _containerConfiguration; + + public AwsBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration) + { + _containerConfiguration = containerConfiguration; + _temporaryCredentialsCacheKey = Guid.NewGuid().ToString("N"); + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfigurationNames.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfigurationNames.cs new file mode 100644 index 0000000000..24dfca53d2 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobProviderConfigurationNames.cs @@ -0,0 +1,20 @@ +namespace Volo.Abp.BlobStoring.Aws +{ + public static class AwsBlobProviderConfigurationNames + { + public const string AccessKeyId = "Aws.AccessKeyId"; + public const string SecretAccessKey = "Aws.SecretAccessKey"; + public const string UseCredentials = "Aws.UseCredentials"; + public const string UseTemporaryCredentials = "Aws.UseTemporaryCredentials"; + public const string UseTemporaryFederatedCredentials = "Aws.UseTemporaryFederatedCredentials"; + public const string ProfileName = "Aws.ProfileName"; + public const string ProfilesLocation = "Aws.ProfilesLocation"; + public const string DurationSeconds = "Aws.DurationSeconds"; + public const string TemporaryCredentialsCacheKey = "Aws.TemporaryCredentialsCacheKey"; + public const string Name = "Aws.Name"; + public const string Policy = "Aws.Policy"; + public const string Region = "Aws.Region"; + public const string ContainerName = "Aws.ContainerName"; + public const string CreateContainerIfNotExists = "Aws.CreateContainerIfNotExists"; + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsTemporaryCredentialsCacheItem.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsTemporaryCredentialsCacheItem.cs new file mode 100644 index 0000000000..887acddc5d --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsTemporaryCredentialsCacheItem.cs @@ -0,0 +1,26 @@ +using System; + +namespace Volo.Abp.BlobStoring.Aws +{ + [Serializable] + public class AwsTemporaryCredentialsCacheItem + { + public string AccessKeyId { get; set; } + + public string SecretAccessKey { get; set; } + + public string SessionToken { get; set; } + + public AwsTemporaryCredentialsCacheItem() + { + + } + + public AwsTemporaryCredentialsCacheItem(string accessKeyId,string secretAccessKey,string sessionToken) + { + AccessKeyId = accessKeyId; + SecretAccessKey = secretAccessKey; + SessionToken = sessionToken; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/DefaultAmazonS3ClientFactory.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/DefaultAmazonS3ClientFactory.cs new file mode 100644 index 0000000000..739818c0d9 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/DefaultAmazonS3ClientFactory.cs @@ -0,0 +1,193 @@ +using System; +using System.Threading.Tasks; +using Amazon; +using Amazon.Runtime; +using Amazon.Runtime.CredentialManagement; +using Amazon.S3; +using Amazon.SecurityToken; +using Amazon.SecurityToken.Model; +using Microsoft.Extensions.Caching.Distributed; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Security.Encryption; + +namespace Volo.Abp.BlobStoring.Aws +{ + public class DefaultAmazonS3ClientFactory : IAmazonS3ClientFactory, ITransientDependency + { + protected IDistributedCache Cache { get; } + + protected IStringEncryptionService StringEncryptionService { get; } + + public DefaultAmazonS3ClientFactory( + IDistributedCache cache, + IStringEncryptionService stringEncryptionService) + { + Cache = cache; + StringEncryptionService = stringEncryptionService; + } + + public virtual async Task GetAmazonS3Client( + AwsBlobProviderConfiguration configuration) + { + var region = RegionEndpoint.GetBySystemName(configuration.Region); + + if (configuration.UseCredentials) + { + var awsCredentials = GetAwsCredentials(configuration); + return awsCredentials == null + ? new AmazonS3Client(region) + : new AmazonS3Client(awsCredentials, region); + } + + if (configuration.UseTemporaryCredentials) + { + return new AmazonS3Client(await GetTemporaryCredentialsAsync(configuration), region); + } + + if (configuration.UseTemporaryFederatedCredentials) + { + return new AmazonS3Client(await GetTemporaryFederatedCredentialsAsync(configuration), + region); + } + + Check.NotNullOrWhiteSpace(configuration.AccessKeyId, nameof(configuration.AccessKeyId)); + Check.NotNullOrWhiteSpace(configuration.SecretAccessKey, nameof(configuration.SecretAccessKey)); + + return new AmazonS3Client(configuration.AccessKeyId, configuration.SecretAccessKey, configuration.Region); + } + + protected virtual AWSCredentials GetAwsCredentials( + AwsBlobProviderConfiguration configuration) + { + if (configuration.ProfileName.IsNullOrWhiteSpace()) + { + return null; + } + + var chain = new CredentialProfileStoreChain(configuration.ProfilesLocation); + + if (chain.TryGetAWSCredentials(configuration.ProfileName, out var awsCredentials)) + { + return awsCredentials; + } + + throw new AmazonS3Exception("Not found aws credentials"); + } + + protected virtual async Task GetTemporaryCredentialsAsync( + AwsBlobProviderConfiguration configuration) + { + var temporaryCredentialsCache = await Cache.GetAsync(configuration.TemporaryCredentialsCacheKey); + + if (temporaryCredentialsCache == null) + { + AmazonSecurityTokenServiceClient stsClient; + + if (!configuration.AccessKeyId.IsNullOrEmpty() && !configuration.SecretAccessKey.IsNullOrEmpty()) + { + stsClient = new AmazonSecurityTokenServiceClient(configuration.AccessKeyId, + configuration.SecretAccessKey); + } + else + { + var awsCredentials = GetAwsCredentials(configuration); + stsClient = awsCredentials == null + ? new AmazonSecurityTokenServiceClient() + : new AmazonSecurityTokenServiceClient(awsCredentials); + } + + using (stsClient) + { + var getSessionTokenRequest = new GetSessionTokenRequest + { + DurationSeconds = configuration.DurationSeconds + }; + + var sessionTokenResponse = + await stsClient.GetSessionTokenAsync(getSessionTokenRequest); + + var credentials = sessionTokenResponse.Credentials; + + temporaryCredentialsCache = + await SetTemporaryCredentialsCache(configuration, credentials); + } + } + + var sessionCredentials = new SessionAWSCredentials( + StringEncryptionService.Decrypt(temporaryCredentialsCache.AccessKeyId), + StringEncryptionService.Decrypt(temporaryCredentialsCache.SecretAccessKey), + StringEncryptionService.Decrypt(temporaryCredentialsCache.SessionToken)); + return sessionCredentials; + } + + protected virtual async Task GetTemporaryFederatedCredentialsAsync( + AwsBlobProviderConfiguration configuration) + { + Check.NotNullOrWhiteSpace(configuration.Name, nameof(configuration.Name)); + Check.NotNullOrWhiteSpace(configuration.Policy, nameof(configuration.Policy)); + + var temporaryCredentialsCache = await Cache.GetAsync(configuration.TemporaryCredentialsCacheKey); + + if (temporaryCredentialsCache == null) + { + AmazonSecurityTokenServiceClient stsClient; + + if (!configuration.AccessKeyId.IsNullOrEmpty() && !configuration.SecretAccessKey.IsNullOrEmpty()) + { + stsClient = new AmazonSecurityTokenServiceClient(configuration.AccessKeyId, + configuration.SecretAccessKey); + } + else + { + var awsCredentials = GetAwsCredentials(configuration); + stsClient = awsCredentials == null + ? new AmazonSecurityTokenServiceClient() + : new AmazonSecurityTokenServiceClient(awsCredentials); + } + + using (stsClient) + { + var federationTokenRequest = + new GetFederationTokenRequest + { + DurationSeconds = configuration.DurationSeconds, + Name = configuration.Name, + Policy = configuration.Policy + }; + + var federationTokenResponse = + await stsClient.GetFederationTokenAsync(federationTokenRequest); + var credentials = federationTokenResponse.Credentials; + + temporaryCredentialsCache = + await SetTemporaryCredentialsCache(configuration, credentials); + } + } + + var sessionCredentials = new SessionAWSCredentials( + StringEncryptionService.Decrypt(temporaryCredentialsCache.AccessKeyId), + StringEncryptionService.Decrypt(temporaryCredentialsCache.SecretAccessKey), + StringEncryptionService.Decrypt(temporaryCredentialsCache.SessionToken)); + return sessionCredentials; + } + + private async Task SetTemporaryCredentialsCache( + AwsBlobProviderConfiguration configuration, + Credentials credentials) + { + var temporaryCredentialsCache = new AwsTemporaryCredentialsCacheItem( + StringEncryptionService.Encrypt(credentials.AccessKeyId), + StringEncryptionService.Encrypt(credentials.SecretAccessKey), + StringEncryptionService.Encrypt(credentials.SessionToken)); + + await Cache.SetAsync(configuration.TemporaryCredentialsCacheKey, temporaryCredentialsCache, + new DistributedCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(configuration.DurationSeconds - 10) + }); + + return temporaryCredentialsCache; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/DefaultAwsBlobNameCalculator.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/DefaultAwsBlobNameCalculator.cs new file mode 100644 index 0000000000..9ff3511e6f --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/DefaultAwsBlobNameCalculator.cs @@ -0,0 +1,22 @@ +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.BlobStoring.Aws +{ + public class DefaultAwsBlobNameCalculator : IAwsBlobNameCalculator, ITransientDependency + { + protected ICurrentTenant CurrentTenant { get; } + + public DefaultAwsBlobNameCalculator(ICurrentTenant currentTenant) + { + CurrentTenant = currentTenant; + } + + public virtual string Calculate(BlobProviderArgs args) + { + return CurrentTenant.Id == null + ? $"host/{args.BlobName}" + : $"tenants/{CurrentTenant.Id.Value.ToString("D")}/{args.BlobName}"; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/IAmazonS3ClientFactory.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/IAmazonS3ClientFactory.cs new file mode 100644 index 0000000000..92d7ddc906 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/IAmazonS3ClientFactory.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Amazon.S3; + +namespace Volo.Abp.BlobStoring.Aws +{ + public interface IAmazonS3ClientFactory + { + Task GetAmazonS3Client(AwsBlobProviderConfiguration configuration); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/IAwsBlobNameCalculator.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/IAwsBlobNameCalculator.cs new file mode 100644 index 0000000000..e4c470c505 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/IAwsBlobNameCalculator.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.BlobStoring.Aws +{ + public interface IAwsBlobNameCalculator + { + string Calculate(BlobProviderArgs args); + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/FodyWeavers.xml b/framework/src/Volo.Abp.BlobStoring.Minio/FodyWeavers.xml new file mode 100644 index 0000000000..bc5a74a236 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Minio/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/FodyWeavers.xsd b/framework/src/Volo.Abp.BlobStoring.Minio/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Minio/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.BlobStoring.Minio/Volo.Abp.BlobStoring.Minio.csproj b/framework/src/Volo.Abp.BlobStoring.Minio/Volo.Abp.BlobStoring.Minio.csproj new file mode 100644 index 0000000000..1c080067ed --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Minio/Volo.Abp.BlobStoring.Minio.csproj @@ -0,0 +1,21 @@ + + + + + + netstandard2.0 + Volo.Abp.BlobStoring.Minio + Volo.Abp.BlobStoring.Minio + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioModule.cs b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioModule.cs new file mode 100644 index 0000000000..00874dfe01 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioModule.cs @@ -0,0 +1,10 @@ +using Volo.Abp.Modularity; + +namespace Volo.Abp.BlobStoring.Minio +{ + [DependsOn(typeof(AbpBlobStoringModule))] + public class AbpBlobStoringMinioModule : AbpModule + { + + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/DefaultMinioBlobNameCalculator.cs b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/DefaultMinioBlobNameCalculator.cs new file mode 100644 index 0000000000..b6d35671e2 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/DefaultMinioBlobNameCalculator.cs @@ -0,0 +1,22 @@ +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.BlobStoring.Minio +{ + public class DefaultMinioBlobNameCalculator : IMinioBlobNameCalculator, ITransientDependency + { + protected ICurrentTenant CurrentTenant { get; } + + public DefaultMinioBlobNameCalculator(ICurrentTenant currentTenant) + { + CurrentTenant = currentTenant; + } + + public virtual string Calculate(BlobProviderArgs args) + { + return CurrentTenant.Id == null + ? $"host/{args.BlobName}" + : $"tenants/{CurrentTenant.Id.Value.ToString("D")}/{args.BlobName}"; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/IMinioBlobNameCalculator.cs b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/IMinioBlobNameCalculator.cs new file mode 100644 index 0000000000..9cf7503648 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/IMinioBlobNameCalculator.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.BlobStoring.Minio +{ + public interface IMinioBlobNameCalculator + { + string Calculate(BlobProviderArgs args); + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobContainerConfigurationExtensions.cs b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobContainerConfigurationExtensions.cs new file mode 100644 index 0000000000..3465914156 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobContainerConfigurationExtensions.cs @@ -0,0 +1,24 @@ +using System; + +namespace Volo.Abp.BlobStoring.Minio +{ + public static class MinioBlobContainerConfigurationExtensions + { + public static MinioBlobProviderConfiguration GetMinioConfiguration( + this BlobContainerConfiguration containerConfiguration) + { + return new MinioBlobProviderConfiguration(containerConfiguration); + } + + public static BlobContainerConfiguration UseMinio( + this BlobContainerConfiguration containerConfiguration, + Action minioConfigureAction) + { + containerConfiguration.ProviderType = typeof(MinioBlobProvider); + + minioConfigureAction(new MinioBlobProviderConfiguration(containerConfiguration)); + + return containerConfiguration; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobNamingNormalizer.cs b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobNamingNormalizer.cs new file mode 100644 index 0000000000..d350910105 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobNamingNormalizer.cs @@ -0,0 +1,52 @@ +using System.Text.RegularExpressions; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.BlobStoring.Minio +{ + public class MinioBlobNamingNormalizer : IBlobNamingNormalizer, ITransientDependency + { + /// + ///https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html + /// + public virtual string NormalizeContainerName(string containerName) + { + // All letters in a container name must be lowercase. + containerName = containerName.ToLower(); + + // Container names can contain only letters, numbers, and the dash (-) character. + containerName = Regex.Replace(containerName, "[^a-z0-9-]", string.Empty); + + // Every dash (-) character must be immediately preceded and followed by a letter or number; + // consecutive dashes are not permitted in container names. + // Container names must start or end with a letter or number + containerName = Regex.Replace(containerName, "-{2,}", "-"); + containerName = Regex.Replace(containerName, "^-", string.Empty); + containerName = Regex.Replace(containerName, "-$", string.Empty); + + // Container names must be from 3 through 63 characters long. + if (containerName.Length < 3) + { + var length = containerName.Length; + for (var i = 0; i < 3 - length; i++) + { + containerName += "0"; + } + } + + if (containerName.Length > 63) + { + containerName = containerName.Substring(0, 63); + } + + return containerName; + } + + /// + /// https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html + /// + public virtual string NormalizeBlobName(string blobName) + { + return blobName; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProvider.cs b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProvider.cs new file mode 100644 index 0000000000..204b1a6a1a --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProvider.cs @@ -0,0 +1,145 @@ +using Minio; +using Minio.Exceptions; +using System; +using System.IO; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.BlobStoring.Minio +{ + public class MinioBlobProvider : BlobProviderBase, ITransientDependency + { + protected IMinioBlobNameCalculator MinioBlobNameCalculator { get; } + + public MinioBlobProvider(IMinioBlobNameCalculator minioBlobNameCalculator) + { + MinioBlobNameCalculator = minioBlobNameCalculator; + } + + public override async Task SaveAsync(BlobProviderSaveArgs args) + { + var blobName = MinioBlobNameCalculator.Calculate(args); + var configuration = args.Configuration.GetMinioConfiguration(); + var client = GetMinioClient(args); + var containerName = GetContainerName(args); + + if (!args.OverrideExisting && await BlobExistsAsync(client, containerName, blobName)) + { + throw new BlobAlreadyExistsException($"Saving BLOB '{args.BlobName}' does already exists in the container '{containerName}'! Set {nameof(args.OverrideExisting)} if it should be overwritten."); + } + + if (configuration.CreateBucketIfNotExists) + { + await CreateBucketIfNotExists(client, containerName); + } + + await client.PutObjectAsync(containerName, blobName, args.BlobStream, args.BlobStream.Length); + } + + public override async Task DeleteAsync(BlobProviderDeleteArgs args) + { + var blobName = MinioBlobNameCalculator.Calculate(args); + var client = GetMinioClient(args); + var containerName = GetContainerName(args); + + if (await BlobExistsAsync(client, containerName, blobName)) + { + await client.RemoveObjectAsync(containerName, blobName); + return true; + } + + return false; + } + + public override async Task ExistsAsync(BlobProviderExistsArgs args) + { + var blobName = MinioBlobNameCalculator.Calculate(args); + var client = GetMinioClient(args); + var containerName = GetContainerName(args); + + return await BlobExistsAsync(client, containerName, blobName); + } + + public override async Task GetOrNullAsync(BlobProviderGetArgs args) + { + var blobName = MinioBlobNameCalculator.Calculate(args); + var client = GetMinioClient(args); + var containerName = GetContainerName(args); + + if (!await BlobExistsAsync(client, containerName, blobName)) + { + return null; + } + + var memoryStream = new MemoryStream(); + await client.GetObjectAsync(containerName, blobName, async stream => + { + if (stream != null) + { + await stream.CopyToAsync(memoryStream); + } + else + { + memoryStream = null; + } + }); + + return memoryStream; + } + + protected virtual MinioClient GetMinioClient(BlobProviderArgs args) + { + var configuration = args.Configuration.GetMinioConfiguration(); + var client = new MinioClient(configuration.EndPoint, configuration.AccessKey, configuration.SecretKey); + + if (configuration.WithSSL) + { + client.WithSSL(); + } + + return client; + } + + protected virtual async Task CreateBucketIfNotExists(MinioClient client,string containerName) + { + if (!await client.BucketExistsAsync(containerName)) + { + await client.MakeBucketAsync(containerName); + } + } + + private async Task BlobExistsAsync(MinioClient client, string containerName , string blobName) + { + // Make sure Blob Container exists. + if (await client.BucketExistsAsync(containerName)) + { + try + { + await client.StatObjectAsync(containerName, blobName); + } + catch (Exception e) + { + if (e is ObjectNotFoundException) + { + return false; + } + + throw; + } + + return true; + } + + return false; + } + + private static string GetContainerName(BlobProviderArgs args) + { + var configuration = args.Configuration.GetMinioConfiguration(); + + return configuration.BucketName.IsNullOrWhiteSpace() + ? args.ContainerName + : configuration.BucketName; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProviderConfiguration.cs b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProviderConfiguration.cs new file mode 100644 index 0000000000..63b0132d20 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProviderConfiguration.cs @@ -0,0 +1,63 @@ +namespace Volo.Abp.BlobStoring.Minio +{ + public class MinioBlobProviderConfiguration + { + public string BucketName + { + get => _containerConfiguration.GetConfiguration(MinioBlobProviderConfigurationNames.BucketName); + set => _containerConfiguration.SetConfiguration(MinioBlobProviderConfigurationNames.BucketName, Check.NotNullOrWhiteSpace(value, nameof(value))); + } + + /// + /// endPoint is an URL, domain name, IPv4 address or IPv6 address. + /// + public string EndPoint + { + get => _containerConfiguration.GetConfiguration(MinioBlobProviderConfigurationNames.EndPoint); + set => _containerConfiguration.SetConfiguration(MinioBlobProviderConfigurationNames.EndPoint, Check.NotNullOrWhiteSpace(value, nameof(value))); + } + + /// + /// accessKey is like user-id that uniquely identifies your account.This field is optional and can be omitted for anonymous access. + /// + public string AccessKey + { + get => _containerConfiguration.GetConfiguration(MinioBlobProviderConfigurationNames.AccessKey); + set => _containerConfiguration.SetConfiguration(MinioBlobProviderConfigurationNames.AccessKey, Check.NotNullOrWhiteSpace(value, nameof(value))); + } + + /// + /// secretKey is the password to your account.This field is optional and can be omitted for anonymous access. + /// + public string SecretKey + { + get => _containerConfiguration.GetConfiguration(MinioBlobProviderConfigurationNames.SecretKey); + set => _containerConfiguration.SetConfiguration(MinioBlobProviderConfigurationNames.SecretKey, Check.NotNullOrWhiteSpace(value, nameof(value))); + } + + /// + ///connect to to MinIO Client object to use https instead of http + /// + public bool WithSSL + { + get => _containerConfiguration.GetConfigurationOrDefault(MinioBlobProviderConfigurationNames.WithSSL, false); + set => _containerConfiguration.SetConfiguration(MinioBlobProviderConfigurationNames.WithSSL, value); + } + + /// + ///Default value: false. + /// + public bool CreateBucketIfNotExists + { + get => _containerConfiguration.GetConfigurationOrDefault(MinioBlobProviderConfigurationNames.CreateBucketIfNotExists, false); + set => _containerConfiguration.SetConfiguration(MinioBlobProviderConfigurationNames.CreateBucketIfNotExists, value); + } + + private readonly BlobContainerConfiguration _containerConfiguration; + + public MinioBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration) + { + _containerConfiguration = containerConfiguration; + } + } +} diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProviderConfigurationNames.cs b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProviderConfigurationNames.cs new file mode 100644 index 0000000000..3c12d98390 --- /dev/null +++ b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobProviderConfigurationNames.cs @@ -0,0 +1,12 @@ +namespace Volo.Abp.BlobStoring.Minio +{ + public static class MinioBlobProviderConfigurationNames + { + public const string BucketName = "Minio.BucketName"; + public const string EndPoint = "Minio.EndPoint"; + public const string AccessKey = "Minio.AccessKey"; + public const string SecretKey = "Minio.SecretKey"; + public const string WithSSL = "Minio.WithSSL"; + public const string CreateBucketIfNotExists = "Minio.CreateBucketIfNotExists"; + } +} diff --git a/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo.Abp.Caching.StackExchangeRedis.csproj b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo.Abp.Caching.StackExchangeRedis.csproj index c362c16bff..95a15ddc8e 100644 --- a/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo.Abp.Caching.StackExchangeRedis.csproj +++ b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo.Abp.Caching.StackExchangeRedis.csproj @@ -19,7 +19,7 @@ - + diff --git a/framework/src/Volo.Abp.Caching/Volo.Abp.Caching.csproj b/framework/src/Volo.Abp.Caching/Volo.Abp.Caching.csproj index 5ee096a493..19d5b456ec 100644 --- a/framework/src/Volo.Abp.Caching/Volo.Abp.Caching.csproj +++ b/framework/src/Volo.Abp.Caching/Volo.Abp.Caching.csproj @@ -15,7 +15,7 @@ - + diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs index ba59bf039e..d133ac110b 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs @@ -5,12 +5,14 @@ using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; using Volo.Abp.Serialization; using Volo.Abp.Threading; +using Volo.Abp.Uow; namespace Volo.Abp.Caching { [DependsOn( typeof(AbpThreadingModule), typeof(AbpSerializationModule), + typeof(AbpUnitOfWorkModule), typeof(AbpMultiTenancyModule), typeof(AbpJsonModule))] public class AbpCachingModule : AbpModule diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs index 5bb1dd2481..b26feec136 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs @@ -14,6 +14,7 @@ using Volo.Abp.DependencyInjection; using Volo.Abp.ExceptionHandling; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; +using Volo.Abp.Uow; namespace Volo.Abp.Caching { @@ -30,13 +31,15 @@ namespace Volo.Abp.Caching ICancellationTokenProvider cancellationTokenProvider, IDistributedCacheSerializer serializer, IDistributedCacheKeyNormalizer keyNormalizer, - IHybridServiceScopeFactory serviceScopeFactory) : base( - distributedCacheOption: distributedCacheOption, - cache: cache, - cancellationTokenProvider: cancellationTokenProvider, - serializer: serializer, - keyNormalizer: keyNormalizer, - serviceScopeFactory: serviceScopeFactory) + IHybridServiceScopeFactory serviceScopeFactory, + IUnitOfWorkManager unitOfWorkManager) : base( + distributedCacheOption: distributedCacheOption, + cache: cache, + cancellationTokenProvider: cancellationTokenProvider, + serializer: serializer, + keyNormalizer: keyNormalizer, + serviceScopeFactory: serviceScopeFactory, + unitOfWorkManager:unitOfWorkManager) { } } @@ -50,6 +53,8 @@ namespace Volo.Abp.Caching public class DistributedCache : IDistributedCache where TCacheItem : class { + public const string UowCacheName = "AbpDistributedCache"; + public ILogger> Logger { get; set; } protected string CacheName { get; set; } @@ -66,6 +71,8 @@ namespace Volo.Abp.Caching protected IHybridServiceScopeFactory ServiceScopeFactory { get; } + protected IUnitOfWorkManager UnitOfWorkManager { get; } + protected SemaphoreSlim SyncSemaphore { get; } protected DistributedCacheEntryOptions DefaultCacheOptions; @@ -78,7 +85,8 @@ namespace Volo.Abp.Caching ICancellationTokenProvider cancellationTokenProvider, IDistributedCacheSerializer serializer, IDistributedCacheKeyNormalizer keyNormalizer, - IHybridServiceScopeFactory serviceScopeFactory) + IHybridServiceScopeFactory serviceScopeFactory, + IUnitOfWorkManager unitOfWorkManager) { _distributedCacheOption = distributedCacheOption.Value; Cache = cache; @@ -87,6 +95,7 @@ namespace Volo.Abp.Caching Serializer = serializer; KeyNormalizer = keyNormalizer; ServiceScopeFactory = serviceScopeFactory; + UnitOfWorkManager = unitOfWorkManager; SyncSemaphore = new SemaphoreSlim(1, 1); @@ -134,13 +143,24 @@ namespace Volo.Abp.Caching /// /// The key of cached item to be retrieved from the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The cache item, or null. public virtual TCacheItem Get( TCacheKey key, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + if (ShouldConsiderUow(considerUow)) + { + var value = GetUnitOfWorkCache().GetOrDefault(key)?.GetUnRemovedValueOrNull(); + if (value != null) + { + return value; + } + } + byte[] cachedBytes; try @@ -163,7 +183,8 @@ namespace Volo.Abp.Caching public virtual KeyValuePair[] GetMany( IEnumerable keys, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { var keyArray = keys.ToArray(); @@ -172,16 +193,39 @@ namespace Volo.Abp.Caching { return GetManyFallback( keyArray, - hideErrors + hideErrors, + considerUow ); } + var notCachedKeys = new List(); + var cachedValues = new List>(); + if (ShouldConsiderUow(considerUow)) + { + var uowCache = GetUnitOfWorkCache(); + foreach (var key in keyArray) + { + var value = uowCache.GetOrDefault(key)?.GetUnRemovedValueOrNull(); + if (value != null) + { + cachedValues.Add(new KeyValuePair(key, value)); + } + } + + notCachedKeys = keyArray.Except(cachedValues.Select(x => x.Key)).ToList(); + if (!notCachedKeys.Any()) + { + return cachedValues.ToArray(); + } + } + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; byte[][] cachedBytes; + var readKeys = notCachedKeys.Any() ? notCachedKeys.ToArray() : keyArray; try { - cachedBytes = cacheSupportsMultipleItems.GetMany(keyArray.Select(NormalizeKey)); + cachedBytes = cacheSupportsMultipleItems.GetMany(readKeys.Select(NormalizeKey)); } catch (Exception ex) { @@ -193,13 +237,14 @@ namespace Volo.Abp.Caching throw; } - - return ToCacheItems(cachedBytes, keyArray); + + return cachedValues.Concat(ToCacheItems(cachedBytes, readKeys)).ToArray(); } protected virtual KeyValuePair[] GetManyFallback( TCacheKey[] keys, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -208,7 +253,7 @@ namespace Volo.Abp.Caching return keys .Select(key => new KeyValuePair( key, - Get(key, hideErrors: false) + Get(key, false, considerUow) ) ).ToArray(); } @@ -227,6 +272,7 @@ namespace Volo.Abp.Caching public virtual async Task[]> GetManyAsync( IEnumerable keys, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { var keyArray = keys.ToArray(); @@ -237,17 +283,41 @@ namespace Volo.Abp.Caching return await GetManyFallbackAsync( keyArray, hideErrors, + considerUow, token ); } + var notCachedKeys = new List(); + var cachedValues = new List>(); + if (ShouldConsiderUow(considerUow)) + { + var uowCache = GetUnitOfWorkCache(); + foreach (var key in keyArray) + { + var value = uowCache.GetOrDefault(key)?.GetUnRemovedValueOrNull(); + if (value != null) + { + cachedValues.Add(new KeyValuePair(key, value)); + } + } + + notCachedKeys = keyArray.Except(cachedValues.Select(x => x.Key)).ToList(); + if (!notCachedKeys.Any()) + { + return cachedValues.ToArray(); + } + } + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; byte[][] cachedBytes; + var readKeys = notCachedKeys.Any() ? notCachedKeys.ToArray() : keyArray; + try { cachedBytes = await cacheSupportsMultipleItems.GetManyAsync( - keyArray.Select(NormalizeKey), + readKeys.Select(NormalizeKey), CancellationTokenProvider.FallbackToProvider(token) ); } @@ -261,13 +331,14 @@ namespace Volo.Abp.Caching throw; } - - return ToCacheItems(cachedBytes, keyArray); + + return cachedValues.Concat(ToCacheItems(cachedBytes, readKeys)).ToArray(); } protected virtual async Task[]> GetManyFallbackAsync( TCacheKey[] keys, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -280,7 +351,7 @@ namespace Volo.Abp.Caching { result.Add(new KeyValuePair( key, - await GetAsync(key, false, token)) + await GetAsync(key, false, considerUow, token: token)) ); } @@ -303,15 +374,26 @@ namespace Volo.Abp.Caching /// /// The key of cached item to be retrieved from the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The for the task. /// The cache item, or null. public virtual async Task GetAsync( TCacheKey key, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + if (ShouldConsiderUow(considerUow)) + { + var value = GetUnitOfWorkCache().GetOrDefault(key)?.GetUnRemovedValueOrNull(); + if (value != null) + { + return value; + } + } + byte[] cachedBytes; try @@ -348,14 +430,16 @@ namespace Volo.Abp.Caching /// The factory delegate is used to provide the cache item when no cache item is found for the given . /// The cache options for the factory delegate. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The cache item. public virtual TCacheItem GetOrAdd( TCacheKey key, Func factory, Func optionsFactory = null, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { - var value = Get(key, hideErrors); + var value = Get(key, hideErrors, considerUow); if (value != null) { return value; @@ -363,14 +447,28 @@ namespace Volo.Abp.Caching using (SyncSemaphore.Lock()) { - value = Get(key, hideErrors); + value = Get(key, hideErrors, considerUow); if (value != null) { return value; } value = factory(); - Set(key, value, optionsFactory?.Invoke(), hideErrors); + + if (ShouldConsiderUow(considerUow)) + { + var uowCache = GetUnitOfWorkCache(); + if (uowCache.TryGetValue(key, out var item)) + { + item.SetValue(value); + } + else + { + uowCache.Add(key, new UnitOfWorkCacheItem(value)); + } + } + + Set(key, value, optionsFactory?.Invoke(), hideErrors, considerUow); } return value; @@ -384,6 +482,7 @@ namespace Volo.Abp.Caching /// The factory delegate is used to provide the cache item when no cache item is found for the given . /// The cache options for the factory delegate. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The for the task. /// The cache item. public virtual async Task GetOrAddAsync( @@ -391,10 +490,11 @@ namespace Volo.Abp.Caching Func> factory, Func optionsFactory = null, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { token = CancellationTokenProvider.FallbackToProvider(token); - var value = await GetAsync(key, hideErrors, token); + var value = await GetAsync(key, hideErrors, considerUow, token); if (value != null) { return value; @@ -402,14 +502,28 @@ namespace Volo.Abp.Caching using (await SyncSemaphore.LockAsync(token)) { - value = await GetAsync(key, hideErrors, token); + value = await GetAsync(key, hideErrors, considerUow, token); if (value != null) { return value; } value = await factory(); - await SetAsync(key, value, optionsFactory?.Invoke(), hideErrors, token); + + if (ShouldConsiderUow(considerUow)) + { + var uowCache = GetUnitOfWorkCache(); + if (uowCache.TryGetValue(key, out var item)) + { + item.SetValue(value); + } + else + { + uowCache.Add(key, new UnitOfWorkCacheItem(value)); + } + } + + await SetAsync(key, value, optionsFactory?.Invoke(), hideErrors, considerUow, token); } return value; @@ -422,34 +536,62 @@ namespace Volo.Abp.Caching /// The cache item value to set in the cache. /// The cache options for the value. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. public virtual void Set( TCacheKey key, TCacheItem value, DistributedCacheEntryOptions options = null, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - - try + void SetRealCache() { - Cache.Set( - NormalizeKey(key), - Serializer.Serialize(value), - options ?? DefaultCacheOptions - ); + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + Cache.Set( + NormalizeKey(key), + Serializer.Serialize(value), + options ?? DefaultCacheOptions + ); + } + catch (Exception ex) + { + if (hideErrors == true) + { + HandleException(ex); + return; + } + + throw; + } } - catch (Exception ex) + + if (ShouldConsiderUow(considerUow)) { - if (hideErrors == true) + var uowCache = GetUnitOfWorkCache(); + if (uowCache.TryGetValue(key, out _)) { - HandleException(ex); - return; + uowCache[key].SetValue(value); + } + else + { + uowCache.Add(key, new UnitOfWorkCacheItem(value)); } - throw; + // ReSharper disable once PossibleNullReferenceException + UnitOfWorkManager.Current.OnCompleted(() => + { + SetRealCache(); + return Task.CompletedTask; + }); + } + else + { + SetRealCache(); } } - /// /// Sets the cache item value for the provided key. /// @@ -457,6 +599,7 @@ namespace Volo.Abp.Caching /// The cache item value to set in the cache. /// The cache options for the value. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The for the task. /// The indicating that the operation is asynchronous. public virtual async Task SetAsync( @@ -464,35 +607,60 @@ namespace Volo.Abp.Caching TCacheItem value, DistributedCacheEntryOptions options = null, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - - try + async Task SetRealCache() { - await Cache.SetAsync( - NormalizeKey(key), - Serializer.Serialize(value), - options ?? DefaultCacheOptions, - CancellationTokenProvider.FallbackToProvider(token) - ); - } - catch (Exception ex) - { - if (hideErrors == true) + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try { - await HandleExceptionAsync(ex); - return; + await Cache.SetAsync( + NormalizeKey(key), + Serializer.Serialize(value), + options ?? DefaultCacheOptions, + CancellationTokenProvider.FallbackToProvider(token) + ); } + catch (Exception ex) + { + if (hideErrors == true) + { + await HandleExceptionAsync(ex); + return; + } - throw; + throw; + } } + + if (ShouldConsiderUow(considerUow)) + { + var uowCache = GetUnitOfWorkCache(); + if (uowCache.TryGetValue(key, out _)) + { + uowCache[key].SetValue(value); + } + else + { + uowCache.Add(key, new UnitOfWorkCacheItem(value)); + } + + // ReSharper disable once PossibleNullReferenceException + UnitOfWorkManager.Current.OnCompleted(SetRealCache); + } + else + { + await SetRealCache(); + } } public void SetMany( IEnumerable> items, DistributedCacheEntryOptions options = null, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { var itemsArray = items.ToArray(); @@ -502,40 +670,73 @@ namespace Volo.Abp.Caching SetManyFallback( itemsArray, options, - hideErrors + hideErrors, + considerUow ); - + return; } - - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - try + void SetRealCache() { - cacheSupportsMultipleItems.SetMany( - ToRawCacheItems(itemsArray), - options ?? DefaultCacheOptions - ); + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + cacheSupportsMultipleItems.SetMany( + ToRawCacheItems(itemsArray), + options ?? DefaultCacheOptions + ); + } + catch (Exception ex) + { + if (hideErrors == true) + { + HandleException(ex); + return; + } + + throw; + } } - catch (Exception ex) + + if (ShouldConsiderUow(considerUow)) { - if (hideErrors == true) + var uowCache = GetUnitOfWorkCache(); + + foreach (var pair in itemsArray) { - HandleException(ex); - return; + if (uowCache.TryGetValue(pair.Key, out _)) + { + uowCache[pair.Key].SetValue(pair.Value); + } + else + { + uowCache.Add(pair.Key, new UnitOfWorkCacheItem(pair.Value)); + } } - throw; + // ReSharper disable once PossibleNullReferenceException + UnitOfWorkManager.Current.OnCompleted(() => + { + SetRealCache(); + return Task.CompletedTask; + }); + } + else + { + SetRealCache(); } } - + protected virtual void SetManyFallback( KeyValuePair[] items, DistributedCacheEntryOptions options = null, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - + try { foreach (var item in items) @@ -543,8 +744,9 @@ namespace Volo.Abp.Caching Set( item.Key, item.Value, - options: options, - hideErrors: false + options, + false, + considerUow ); } } @@ -564,6 +766,7 @@ namespace Volo.Abp.Caching IEnumerable> items, DistributedCacheEntryOptions options = null, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { var itemsArray = items.ToArray(); @@ -575,38 +778,67 @@ namespace Volo.Abp.Caching itemsArray, options, hideErrors, + considerUow, token ); - + return; } - - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - try + async Task SetRealCache() { - await cacheSupportsMultipleItems.SetManyAsync( - ToRawCacheItems(itemsArray), - options ?? DefaultCacheOptions, - CancellationTokenProvider.FallbackToProvider(token) - ); + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + await cacheSupportsMultipleItems.SetManyAsync( + ToRawCacheItems(itemsArray), + options ?? DefaultCacheOptions, + CancellationTokenProvider.FallbackToProvider(token) + ); + } + catch (Exception ex) + { + if (hideErrors == true) + { + await HandleExceptionAsync(ex); + return; + } + + throw; + } } - catch (Exception ex) + + if (ShouldConsiderUow(considerUow)) { - if (hideErrors == true) + var uowCache = GetUnitOfWorkCache(); + + foreach (var pair in itemsArray) { - await HandleExceptionAsync(ex); - return; + if (uowCache.TryGetValue(pair.Key, out _)) + { + uowCache[pair.Key].SetValue(pair.Value); + } + else + { + uowCache.Add(pair.Key, new UnitOfWorkCacheItem(pair.Value)); + } } - throw; + // ReSharper disable once PossibleNullReferenceException + UnitOfWorkManager.Current.OnCompleted(SetRealCache); + } + else + { + await SetRealCache(); } } - + protected virtual async Task SetManyFallbackAsync( KeyValuePair[] items, DistributedCacheEntryOptions options = null, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -618,8 +850,9 @@ namespace Volo.Abp.Caching await SetAsync( item.Key, item.Value, - options: options, - hideErrors: false, + options, + false, + considerUow, token: token ); } @@ -636,9 +869,14 @@ namespace Volo.Abp.Caching } } + /// + /// Refreshes the cache value of the given key, and resets its sliding expiration timeout. + /// + /// The key of cached item to be retrieved from the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. public virtual void Refresh( - TCacheKey key, bool? - hideErrors = null) + TCacheKey key, + bool? hideErrors = null) { hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; @@ -658,6 +896,13 @@ namespace Volo.Abp.Caching } } + /// + /// Refreshes the cache value of the given key, and resets its sliding expiration timeout. + /// + /// The key of cached item to be retrieved from the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// The for the task. + /// The indicating that the operation is asynchronous. public virtual async Task RefreshAsync( TCacheKey key, bool? hideErrors = null, @@ -681,48 +926,106 @@ namespace Volo.Abp.Caching } } + /// + /// Removes the cache item for given key from cache. + /// + /// The key of cached item to be retrieved from the cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. public virtual void Remove( TCacheKey key, - bool? hideErrors = null) + bool? hideErrors = null, + bool considerUow = false) { - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - - try + void RemoveRealCache() { - Cache.Remove(NormalizeKey(key)); + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + Cache.Remove(NormalizeKey(key)); + } + catch (Exception ex) + { + if (hideErrors == true) + { + HandleException(ex); + return; + } + + throw; + } } - catch (Exception ex) + + if (ShouldConsiderUow(considerUow)) { - if (hideErrors == true) + var uowCache = GetUnitOfWorkCache(); + if (uowCache.TryGetValue(key, out _)) { - HandleException(ex); - return; + uowCache[key].RemoveValue(); } - throw; + // ReSharper disable once PossibleNullReferenceException + UnitOfWorkManager.Current.OnCompleted(() => + { + RemoveRealCache(); + return Task.CompletedTask; + }); + } + else + { + RemoveRealCache(); } } + /// + /// Removes the cache item for given key from cache. + /// + /// The key of cached item to be retrieved from the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. + /// The for the task. + /// The indicating that the operation is asynchronous. public virtual async Task RemoveAsync( TCacheKey key, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default) { - hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; - - try + async Task RemoveRealCache() { - await Cache.RemoveAsync(NormalizeKey(key), CancellationTokenProvider.FallbackToProvider(token)); + hideErrors = hideErrors ?? _distributedCacheOption.HideErrors; + + try + { + await Cache.RemoveAsync(NormalizeKey(key), CancellationTokenProvider.FallbackToProvider(token)); + } + catch (Exception ex) + { + if (hideErrors == true) + { + await HandleExceptionAsync(ex); + return; + } + + throw; + } } - catch (Exception ex) + + if (ShouldConsiderUow(considerUow)) { - if (hideErrors == true) + var uowCache = GetUnitOfWorkCache(); + if (uowCache.TryGetValue(key, out _)) { - await HandleExceptionAsync(ex); - return; + uowCache[key].RemoveValue(); } - throw; + // ReSharper disable once PossibleNullReferenceException + UnitOfWorkManager.Current.OnCompleted(RemoveRealCache); + } + else + { + await RemoveRealCache(); } } @@ -730,7 +1033,7 @@ namespace Volo.Abp.Caching { AsyncHelper.RunSync(() => HandleExceptionAsync(ex)); } - + protected virtual async Task HandleExceptionAsync(Exception ex) { Logger.LogException(ex, LogLevel.Warning); @@ -742,7 +1045,7 @@ namespace Volo.Abp.Caching .NotifyAsync(new ExceptionNotificationContext(ex, LogLevel.Warning)); } } - + protected virtual KeyValuePair[] ToCacheItems(byte[][] itemBytes, TCacheKey[] itemKeys) { if (itemBytes.Length != itemKeys.Length) @@ -764,7 +1067,7 @@ namespace Volo.Abp.Caching return result.ToArray(); } - + [CanBeNull] protected virtual TCacheItem ToCacheItem([CanBeNull] byte[] bytes) { @@ -786,12 +1089,33 @@ namespace Volo.Abp.Caching ) ).ToArray(); } - + private static KeyValuePair[] ToCacheItemsWithDefaultValues(TCacheKey[] keys) { return keys .Select(key => new KeyValuePair(key, default)) .ToArray(); } + + protected virtual bool ShouldConsiderUow(bool considerUow) + { + return considerUow && UnitOfWorkManager.Current != null; + } + + protected virtual string GetUnitOfWorkCacheKey() + { + return UowCacheName + CacheName; + } + + protected virtual Dictionary> GetUnitOfWorkCache() + { + if (UnitOfWorkManager.Current == null) + { + throw new AbpException($"There is no active UOW."); + } + + return UnitOfWorkManager.Current.GetOrAddItem(GetUnitOfWorkCacheKey(), + key => new Dictionary>()); + } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs index 80fe124785..2f74fd678b 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs @@ -30,42 +30,48 @@ namespace Volo.Abp.Caching /// /// The key of cached item to be retrieved from the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The cache item, or null. TCacheItem Get( TCacheKey key, - bool? hideErrors = null + bool? hideErrors = null, + bool considerUow = false ); - + /// /// Gets multiple cache items with the given keys. /// /// The returned list contains exactly the same count of items specified in the given keys. /// An item in the return list can not be null, but an item in the list has null value - /// if the related key not found in the cache. + /// if the related key not found in the cache. /// /// The keys of cached items to be retrieved from the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// List of cache items. KeyValuePair[] GetMany( IEnumerable keys, - bool? hideErrors = null + bool? hideErrors = null, + bool considerUow = false ); - + /// /// Gets multiple cache items with the given keys. /// /// The returned list contains exactly the same count of items specified in the given keys. /// An item in the return list can not be null, but an item in the list has null value /// if the related key not found in the cache. - /// + /// /// /// The keys of cached items to be retrieved from the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// /// The for the task. /// List of cache items. Task[]> GetManyAsync( IEnumerable keys, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default ); @@ -74,11 +80,13 @@ namespace Volo.Abp.Caching /// /// The key of cached item to be retrieved from the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The for the task. /// The cache item, or null. Task GetAsync( [NotNull] TCacheKey key, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default ); @@ -90,12 +98,14 @@ namespace Volo.Abp.Caching /// The factory delegate is used to provide the cache item when no cache item is found for the given . /// The cache options for the factory delegate. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The cache item. TCacheItem GetOrAdd( TCacheKey key, Func factory, Func optionsFactory = null, - bool? hideErrors = null + bool? hideErrors = null, + bool considerUow = false ); /// @@ -106,6 +116,7 @@ namespace Volo.Abp.Caching /// The factory delegate is used to provide the cache item when no cache item is found for the given . /// The cache options for the factory delegate. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The for the task. /// The cache item. Task GetOrAddAsync( @@ -113,6 +124,7 @@ namespace Volo.Abp.Caching Func> factory, Func optionsFactory = null, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default ); @@ -123,11 +135,13 @@ namespace Volo.Abp.Caching /// The cache item value to set in the cache. /// The cache options for the value. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. void Set( TCacheKey key, TCacheItem value, DistributedCacheEntryOptions options = null, - bool? hideErrors = null + bool? hideErrors = null, + bool considerUow = false ); /// @@ -137,6 +151,7 @@ namespace Volo.Abp.Caching /// The cache item value to set in the cache. /// The cache options for the value. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The for the task. /// The indicating that the operation is asynchronous. Task SetAsync( @@ -144,6 +159,7 @@ namespace Volo.Abp.Caching [NotNull] TCacheItem value, [CanBeNull] DistributedCacheEntryOptions options = null, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default ); @@ -154,12 +170,14 @@ namespace Volo.Abp.Caching /// Items to set on the cache /// The cache options for the value. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. void SetMany( IEnumerable> items, DistributedCacheEntryOptions options = null, - bool? hideErrors = null + bool? hideErrors = null, + bool considerUow = false ); - + /// /// Sets multiple cache items. /// Based on the implementation, this can be more efficient than setting multiple items individually. @@ -167,12 +185,14 @@ namespace Volo.Abp.Caching /// Items to set on the cache /// The cache options for the value. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The for the task. /// The indicating that the operation is asynchronous. Task SetManyAsync( IEnumerable> items, DistributedCacheEntryOptions options = null, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default ); @@ -204,9 +224,11 @@ namespace Volo.Abp.Caching /// /// The key of cached item to be retrieved from the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. void Remove( TCacheKey key, - bool? hideErrors = null + bool? hideErrors = null, + bool considerUow = false ); /// @@ -214,11 +236,13 @@ namespace Volo.Abp.Caching /// /// The key of cached item to be retrieved from the cache. /// Indicates to throw or hide the exceptions for the distributed cache. + /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache. /// The for the task. /// The indicating that the operation is asynchronous. Task RemoveAsync( TCacheKey key, bool? hideErrors = null, + bool considerUow = false, CancellationToken token = default ); } diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItem.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItem.cs new file mode 100644 index 0000000000..c37475d022 --- /dev/null +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItem.cs @@ -0,0 +1,43 @@ +using System; + +namespace Volo.Abp.Caching +{ + [Serializable] + public class UnitOfWorkCacheItem + where TValue : class + { + public bool IsRemoved { get; set; } + + public TValue Value { get; set; } + + public UnitOfWorkCacheItem() + { + + } + + public UnitOfWorkCacheItem(TValue value) + { + Value = value; + } + + public UnitOfWorkCacheItem(TValue value, bool isRemoved) + { + Value = value; + IsRemoved = isRemoved; + } + + public UnitOfWorkCacheItem SetValue(TValue value) + { + Value = value; + IsRemoved = false; + return this; + } + + public UnitOfWorkCacheItem RemoveValue() + { + Value = null; + IsRemoved = true; + return this; + } + } +} diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItemExtensions.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItemExtensions.cs new file mode 100644 index 0000000000..46757f80cf --- /dev/null +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/UnitOfWorkCacheItemExtensions.cs @@ -0,0 +1,11 @@ +namespace Volo.Abp.Caching +{ + public static class UnitOfWorkCacheItemExtensions + { + public static TValue GetUnRemovedValueOrNull(this UnitOfWorkCacheItem item) + where TValue : class + { + return item != null && !item.IsRemoved ? item.Value : null; + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs index dbffa820c8..0244d46c47 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs @@ -1,5 +1,4 @@ -using System; -using System.Text; +using System.Text; using Volo.Abp.Cli.Commands; using Volo.Abp.Domain; using Volo.Abp.IdentityModel; @@ -31,8 +30,9 @@ namespace Volo.Abp.Cli options.Commands["logout"] = typeof(LogoutCommand); options.Commands["generate-proxy"] = typeof(GenerateProxyCommand); options.Commands["suite"] = typeof(SuiteCommand); - options.Commands["switch-to-preview"] = typeof(SwitchNightlyPreviewCommand); - options.Commands["switch-to-stable"] = typeof(SwitchStableCommand); + options.Commands["switch-to-preview"] = typeof(SwitchToPreviewCommand); + options.Commands["switch-to-stable"] = typeof(SwitchToStableCommand); + options.Commands["switch-to-nightly"] = typeof(SwitchToNightlyCommand); options.Commands["translate"] = typeof(TranslateCommand); }); } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs index 436e169aad..ef6eb79042 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs @@ -158,7 +158,7 @@ namespace Volo.Abp.Cli return await NuGetService.GetLatestVersionOrNullAsync("Volo.Abp.Cli"); case UpdateChannel.Prerelease: - return await NuGetService.GetLatestVersionOrNullAsync("Volo.Abp.Cli", includePreviews: true); + return await NuGetService.GetLatestVersionOrNullAsync("Volo.Abp.Cli", includeReleaseCandidates: true); case UpdateChannel.Nightly: return await NuGetService.GetLatestVersionOrNullAsync("Volo.Abp.Cli", includeNightly: true); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs index b37f67fd1a..5e690d8bb0 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs @@ -75,6 +75,7 @@ namespace Volo.Abp.Cli.Commands sb.AppendLine(" -s|--solution Specify the solution file explicitly."); sb.AppendLine(" --skip-db-migrations Specify if a new migration will be added or not."); sb.AppendLine(" -sp|--startup-project Relative path to the project folder of the startup project. Default value is the current folder."); + sb.AppendLine(" -v|--version Specify the version of the module. Default is your project's ABP version."); sb.AppendLine(""); sb.AppendLine("Examples:"); sb.AppendLine(""); @@ -152,7 +153,7 @@ namespace Volo.Abp.Cli.Commands public const string Skip = "skip-db-migrations"; } - public static class StartupProject + public static class StartupProject { public const string Short = "sp"; public const string Long = "startup-project"; diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs index 03e228f255..7ea20eccf4 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs @@ -34,9 +34,12 @@ namespace Volo.Abp.Cli.Commands ); } + var version = commandLineArgs.Options.GetOrNull(Options.Version.Short, Options.Version.Long); + await ProjectNugetPackageAdder.AddAsync( GetProjectFile(commandLineArgs), - commandLineArgs.Target + commandLineArgs.Target, + version ); } @@ -55,6 +58,7 @@ namespace Volo.Abp.Cli.Commands sb.AppendLine("Options:"); sb.AppendLine(""); sb.AppendLine(" -p|--project Specify the project file explicitly."); + sb.AppendLine(" -v|--version Specify the version of the package. Default is your project's ABP version or latest ABP version."); sb.AppendLine(""); sb.AppendLine("Examples:"); sb.AppendLine(""); @@ -119,6 +123,12 @@ namespace Volo.Abp.Cli.Commands public const string Short = "p"; public const string Long = "project"; } + + public static class Version + { + public const string Short = "v"; + public const string Long = "version"; + } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GetSourceCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GetSourceCommand.cs index 0a6155fa67..7cda59b999 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GetSourceCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GetSourceCommand.cs @@ -61,7 +61,7 @@ namespace Volo.Abp.Cli.Commands } commandLineArgs.Options.Add(CliConsts.Command, commandLineArgs.Command); - + await _sourceCodeDownloadService.DownloadAsync( commandLineArgs.Target, outputFolder, version, gitHubAbpLocalRepositoryPath, gitHubVoloLocalRepositoryPath, commandLineArgs.Options); } @@ -99,6 +99,7 @@ namespace Volo.Abp.Cli.Commands sb.AppendLine(""); sb.AppendLine("-o|--output-folder (default: current folder)"); sb.AppendLine("-v|--version (default: latest version)"); + sb.AppendLine("--preview (Use latest pre-release version if there is at least one pre-release after latest stable version)"); sb.AppendLine(""); sb.AppendLine("Examples:"); sb.AppendLine(""); @@ -138,6 +139,11 @@ namespace Volo.Abp.Cli.Commands public const string Short = "v"; public const string Long = "version"; } + + public static class Preview + { + public const string Long = "preview"; + } } } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs index 4c9a074cd2..4a713ceb9e 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs @@ -62,6 +62,12 @@ namespace Volo.Abp.Cli.Commands Logger.LogInformation("Tiered: yes"); } + var preview = commandLineArgs.Options.ContainsKey(Options.Preview.Long); + if (preview) + { + Logger.LogInformation("Preview: yes if any exist for next version."); + } + var databaseProvider = GetDatabaseProvider(commandLineArgs); if (databaseProvider != DatabaseProvider.NotSpecified) { @@ -104,7 +110,7 @@ namespace Volo.Abp.Cli.Commands Logger.LogInformation("Template Source: " + templateSource); } - var createSolutionFolder = (commandLineArgs.Options.GetOrNull(Options.CreateSolutionFolder.Short, Options.CreateSolutionFolder.Long) ?? "true").ToLowerInvariant() != "false"; + var createSolutionFolder = GetCreateSolutionFolderPreference(commandLineArgs); if (!createSolutionFolder) { Logger.LogInformation("Create Solution Folder: no"); @@ -183,6 +189,18 @@ namespace Volo.Abp.Cli.Commands Logger.LogInformation($"'{projectName}' has been successfully created to '{outputFolder}'"); } + private bool GetCreateSolutionFolderPreference(CommandLineArgs commandLineArgs) + { + var longKey = commandLineArgs.Options.ContainsKey(Options.CreateSolutionFolder.Long); + + if (longKey == false) + { + return commandLineArgs.Options.ContainsKey(Options.CreateSolutionFolder.Short); + } + + return longKey; + } + private static string GetConnectionString(CommandLineArgs commandLineArgs) { var connectionString = commandLineArgs.Options.GetOrNull(Options.ConnectionString.Short, Options.ConnectionString.Long); @@ -206,6 +224,7 @@ namespace Volo.Abp.Cli.Commands sb.AppendLine("-d|--database-provider (if supported by the template)"); sb.AppendLine("-o|--output-folder (default: current folder)"); sb.AppendLine("-v|--version (default: latest version)"); + sb.AppendLine("--preview (Use latest pre-release version if there is at least one pre-release after latest stable version)"); sb.AppendLine("-ts|--template-source (your local or network abp template source)"); sb.AppendLine("-csf|--create-solution-folder (default: true)"); sb.AppendLine("-cs|--connection-string (your database connection string)"); @@ -357,6 +376,11 @@ namespace Volo.Abp.Cli.Commands { public const string Long = "tiered"; } + + public static class Preview + { + public const string Long = "preview"; + } } } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchNightlyPreviewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchNightlyPreviewCommand.cs deleted file mode 100644 index 883ac4db2f..0000000000 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchNightlyPreviewCommand.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Text; -using System.Threading.Tasks; -using Volo.Abp.Cli.Args; -using Volo.Abp.Cli.ProjectModification; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Cli.Commands -{ - public class SwitchNightlyPreviewCommand : IConsoleCommand, ITransientDependency - { - private readonly PackageSourceSwitcher _packageSourceSwitcher; - - public SwitchNightlyPreviewCommand(PackageSourceSwitcher packageSourceSwitcher) - { - _packageSourceSwitcher = packageSourceSwitcher; - } - - public async Task ExecuteAsync(CommandLineArgs commandLineArgs) - { - await _packageSourceSwitcher.SwitchToPreview(commandLineArgs); - } - - public string GetUsageInfo() - { - var sb = new StringBuilder(); - - sb.AppendLine(""); - sb.AppendLine("Usage:"); - sb.AppendLine(" abp switch-to-preview [options]"); - sb.AppendLine(""); - sb.AppendLine("Options:"); - sb.AppendLine("-sd|--solution-directory"); - sb.AppendLine(""); - sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); - - return sb.ToString(); - } - - public string GetShortDescription() - { - return "Switches packages to nightly preview ABP version."; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchStableCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchStableCommand.cs deleted file mode 100644 index 54db7bc082..0000000000 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchStableCommand.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Text; -using System.Threading.Tasks; -using Volo.Abp.Cli.Args; -using Volo.Abp.Cli.ProjectModification; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Cli.Commands -{ - public class SwitchStableCommand : IConsoleCommand, ITransientDependency - { - private readonly PackageSourceSwitcher _packageSourceSwitcher; - - public SwitchStableCommand(PackageSourceSwitcher packageSourceSwitcher) - { - _packageSourceSwitcher = packageSourceSwitcher; - } - - public async Task ExecuteAsync(CommandLineArgs commandLineArgs) - { - await _packageSourceSwitcher.SwitchToStable(commandLineArgs); - } - - public string GetUsageInfo() - { - var sb = new StringBuilder(); - - sb.AppendLine(""); - sb.AppendLine("Usage:"); - sb.AppendLine(" abp switch-to-stable [options]"); - sb.AppendLine(""); - sb.AppendLine("Options:"); - sb.AppendLine("-sd|--solution-directory"); - sb.AppendLine(""); - sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); - - return sb.ToString(); - } - - public string GetShortDescription() - { - return "Switches packages to stable ABP version from preview version."; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToNightlyCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToNightlyCommand.cs new file mode 100644 index 0000000000..473489990f --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToNightlyCommand.cs @@ -0,0 +1,44 @@ +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.Cli.Args; +using Volo.Abp.Cli.ProjectModification; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Commands +{ + public class SwitchToNightlyCommand : IConsoleCommand, ITransientDependency + { + private readonly PackagePreviewSwitcher _packagePreviewSwitcher; + + public SwitchToNightlyCommand(PackagePreviewSwitcher packagePreviewSwitcher) + { + _packagePreviewSwitcher = packagePreviewSwitcher; + } + + public async Task ExecuteAsync(CommandLineArgs commandLineArgs) + { + await _packagePreviewSwitcher.SwitchToNightlyPreview(commandLineArgs); + } + + public string GetUsageInfo() + { + var sb = new StringBuilder(); + + sb.AppendLine(""); + sb.AppendLine("Usage:"); + sb.AppendLine(" abp switch-to-nightly [options]"); + sb.AppendLine(""); + sb.AppendLine("Options:"); + sb.AppendLine("-sd|--solution-directory"); + sb.AppendLine(""); + sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); + + return sb.ToString(); + } + + public string GetShortDescription() + { + return "Switches packages to nightly preview ABP version."; + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToPreviewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToPreviewCommand.cs new file mode 100644 index 0000000000..e6c401a8b5 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToPreviewCommand.cs @@ -0,0 +1,44 @@ +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.Cli.Args; +using Volo.Abp.Cli.ProjectModification; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Commands +{ + public class SwitchToPreviewCommand : IConsoleCommand, ITransientDependency + { + private readonly PackagePreviewSwitcher _packagePreviewSwitcher; + + public SwitchToPreviewCommand(PackagePreviewSwitcher packagePreviewSwitcher) + { + _packagePreviewSwitcher = packagePreviewSwitcher; + } + + public async Task ExecuteAsync(CommandLineArgs commandLineArgs) + { + await _packagePreviewSwitcher.SwitchToPreview(commandLineArgs); + } + + public string GetUsageInfo() + { + var sb = new StringBuilder(); + + sb.AppendLine(""); + sb.AppendLine("Usage:"); + sb.AppendLine(" abp switch-to-preview [options]"); + sb.AppendLine(""); + sb.AppendLine("Options:"); + sb.AppendLine("-sd|--solution-directory"); + sb.AppendLine(""); + sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); + + return sb.ToString(); + } + + public string GetShortDescription() + { + return "Switches packages to preview ABP version."; + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToStableCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToStableCommand.cs new file mode 100644 index 0000000000..61545a3da5 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToStableCommand.cs @@ -0,0 +1,44 @@ +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.Cli.Args; +using Volo.Abp.Cli.ProjectModification; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Commands +{ + public class SwitchToStableCommand : IConsoleCommand, ITransientDependency + { + private readonly PackagePreviewSwitcher _packagePreviewSwitcher; + + public SwitchToStableCommand(PackagePreviewSwitcher packagePreviewSwitcher) + { + _packagePreviewSwitcher = packagePreviewSwitcher; + } + + public async Task ExecuteAsync(CommandLineArgs commandLineArgs) + { + await _packagePreviewSwitcher.SwitchToStable(commandLineArgs); + } + + public string GetUsageInfo() + { + var sb = new StringBuilder(); + + sb.AppendLine(""); + sb.AppendLine("Usage:"); + sb.AppendLine(" abp switch-to-stable [options]"); + sb.AppendLine(""); + sb.AppendLine("Options:"); + sb.AppendLine("-sd|--solution-directory"); + sb.AppendLine(""); + sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); + + return sb.ToString(); + } + + public string GetShortDescription() + { + return "Switches packages to stable ABP version from preview version."; + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs index 0fd35582bf..c962ceaf92 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs @@ -39,7 +39,7 @@ namespace Volo.Abp.Cli.NuGet Logger = NullLogger.Instance; } - public async Task GetLatestVersionOrNullAsync(string packageId, bool includePreviews = false, bool includeNightly = false) + public async Task GetLatestVersionOrNullAsync(string packageId, bool includeNightly = false, bool includeReleaseCandidates = false) { if (AuthService.IsLoggedIn()) { @@ -78,11 +78,12 @@ namespace Volo.Abp.Cli.NuGet var versions = JsonSerializer .Deserialize(responseContent) .Versions - .Select(SemanticVersion.Parse); + .Select(SemanticVersion.Parse) + .OrderByDescending(v=> v, new VersionComparer()).ToList(); - if (!includePreviews && !includeNightly) + if (!includeNightly && !includeReleaseCandidates) { - versions = versions.Where(x => !x.IsPrerelease); + versions = versions.Where(x => !x.IsPrerelease).ToList(); } var semanticVersions = versions.ToList(); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs index f221186636..106d7b5bcc 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs @@ -52,10 +52,11 @@ namespace Volo.Abp.Cli.ProjectBuilding string name, string type, string version = null, - string templateSource = null) + string templateSource = null, + bool includePreReleases = false) { DirectoryHelper.CreateIfNotExists(CliPaths.TemplateCache); - var latestVersion = version ?? await GetLatestSourceCodeVersionAsync(name, type); + var latestVersion = version ?? await GetLatestSourceCodeVersionAsync(name, type, null, includePreReleases); if (version == null) { @@ -110,7 +111,8 @@ namespace Volo.Abp.Cli.ProjectBuilding Name = name, Type = type, TemplateSource = templateSource, - Version = version + Version = version, + IncludePreReleases = includePreReleases } ); @@ -122,7 +124,7 @@ namespace Volo.Abp.Cli.ProjectBuilding return new TemplateFile(fileContent, version, latestVersion, nugetVersion); } - private async Task GetLatestSourceCodeVersionAsync(string name, string type, string url = null) + private async Task GetLatestSourceCodeVersionAsync(string name, string type, string url = null, bool includePreReleases = false) { if (url == null) { @@ -137,7 +139,7 @@ namespace Volo.Abp.Cli.ProjectBuilding url, new StringContent( JsonSerializer.Serialize( - new GetLatestSourceCodeVersionDto { Name = name } + new GetLatestSourceCodeVersionDto { Name = name, IncludePreReleases = includePreReleases } ), Encoding.UTF8, MimeTypes.Application.Json @@ -171,7 +173,7 @@ namespace Volo.Abp.Cli.ProjectBuilding url, new StringContent( JsonSerializer.Serialize( - new GetTemplateNugetVersionDto { Name = name, Version = version } + new GetTemplateNugetVersionDto { Name = name, Version = version} ), Encoding.UTF8, MimeTypes.Application.Json @@ -260,11 +262,15 @@ namespace Volo.Abp.Cli.ProjectBuilding public string Type { get; set; } public string TemplateSource { get; set; } + + public bool IncludePreReleases { get; set; } } public class GetLatestSourceCodeVersionDto { public string Name { get; set; } + + public bool IncludePreReleases { get; set; } } public class GetTemplateNugetVersionDto @@ -272,6 +278,8 @@ namespace Volo.Abp.Cli.ProjectBuilding public string Name { get; set; } public string Version { get; set; } + + public bool IncludePreReleases { get; set; } } public class GetVersionResultDto diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SolutionRenameStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SolutionRenameStep.cs index bf5ad938af..abdb9e45f5 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SolutionRenameStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SolutionRenameStep.cs @@ -64,7 +64,8 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps RenameHelper.RenameAll(_entries, _projectNamePlaceHolder, _projectName); RenameHelper.RenameAll(_entries, _projectNamePlaceHolder.ToCamelCase(), _projectName.ToCamelCase()); RenameHelper.RenameAll(_entries, _projectNamePlaceHolder.ToKebabCase(), _projectName.ToKebabCase()); + RenameHelper.RenameAll(_entries, _projectNamePlaceHolder.ToSnakeCase().ToUpper(), _projectName.ToSnakeCase().ToUpper()); } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ISourceCodeStore.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ISourceCodeStore.cs index de082d4bcf..af03a6095b 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ISourceCodeStore.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ISourceCodeStore.cs @@ -9,7 +9,8 @@ namespace Volo.Abp.Cli.ProjectBuilding string name, string type, [CanBeNull] string version = null, - [CanBeNull] string templateSource = null + [CanBeNull] string templateSource = null, + bool includePreReleases = false ); } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ModuleInfoProvider.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ModuleInfoProvider.cs index f0472ad4df..81bc704151 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ModuleInfoProvider.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ModuleInfoProvider.cs @@ -49,15 +49,16 @@ namespace Volo.Abp.Cli.ProjectBuilding { using (var client = new CliHttpClient()) { - var responseMessage = await client.GetAsync( + using (var responseMessage = await client.GetAsync( $"{CliUrls.WwwAbpIo}api/download/modules/", CancellationTokenProvider.Token - ); - - await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(responseMessage); - var result = await responseMessage.Content.ReadAsStringAsync(); - return JsonSerializer.Deserialize>(result); + )) + { + await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(responseMessage); + var result = await responseMessage.Content.ReadAsStringAsync(); + return JsonSerializer.Deserialize>(result); + } } } } -} +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ModuleProjectBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ModuleProjectBuilder.cs index 6a7132769a..17b9585f16 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ModuleProjectBuilder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ModuleProjectBuilder.cs @@ -48,7 +48,9 @@ namespace Volo.Abp.Cli.ProjectBuilding var templateFile = await SourceCodeStore.GetAsync( args.TemplateName, SourceCodeTypes.Module, - args.Version + args.Version, + null, + args.ExtraProperties.ContainsKey(GetSourceCommand.Options.Preview.Long) ); var apiKeyResult = await ApiKeyService.GetApiKeyOrNullAsync(); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateProjectBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateProjectBuilder.cs index e8ddc804c2..02b26eba39 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateProjectBuilder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateProjectBuilder.cs @@ -57,7 +57,8 @@ namespace Volo.Abp.Cli.ProjectBuilding args.TemplateName, SourceCodeTypes.Template, args.Version, - args.TemplateSource + args.TemplateSource, + args.ExtraProperties.ContainsKey(NewCommand.Options.Preview.Long) ); DeveloperApiKeyResult apiKeyResult = null; @@ -119,6 +120,7 @@ namespace Volo.Abp.Cli.ProjectBuilding var options = args.ExtraProperties .Where(x => !x.Key.Equals(CliConsts.Command, StringComparison.InvariantCultureIgnoreCase)) .Where(x => !x.Key.Equals(NewCommand.Options.Tiered.Long, StringComparison.InvariantCultureIgnoreCase)) + .Where(x => !x.Key.Equals(NewCommand.Options.Preview.Long, StringComparison.InvariantCultureIgnoreCase)) .Where(x => !x.Key.Equals(NewCommand.Options.DatabaseProvider.Long, StringComparison.InvariantCultureIgnoreCase) && !x.Key.Equals(NewCommand.Options.DatabaseProvider.Short, StringComparison.InvariantCultureIgnoreCase)) .Where(x => !x.Key.Equals(NewCommand.Options.OutputFolder.Long, StringComparison.InvariantCultureIgnoreCase) && diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs index 4730fed899..359ad09684 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using NuGet.Versioning; using Volo.Abp.Cli.Http; using Volo.Abp.Cli.Utils; using Volo.Abp.DependencyInjection; @@ -42,7 +43,7 @@ namespace Volo.Abp.Cli.ProjectModification Logger = NullLogger.Instance; } - public async Task Update(string rootDirectory, bool includePreviews = false, bool switchToStable = false) + public async Task Update(string rootDirectory, bool includePreviews = false, bool includeReleaseCandidates = false, bool switchToStable = false) { var fileList = _packageJsonFileFinder.Find(rootDirectory); @@ -57,7 +58,7 @@ namespace Volo.Abp.Cli.ProjectModification async Task UpdateAsync(string file) { - var updated = await UpdatePackagesInFile(file, includePreviews, switchToStable); + var updated = await UpdatePackagesInFile(file, includePreviews,includeReleaseCandidates, switchToStable); packagesUpdated.TryAdd(file, updated); } @@ -176,7 +177,10 @@ namespace Volo.Abp.Cli.ProjectModification return File.Exists(Path.Combine(fileDirectory, "angular.json")); } - protected virtual async Task UpdatePackagesInFile(string filePath, bool includePreviews = false, + protected virtual async Task UpdatePackagesInFile( + string filePath, + bool includePreviews = false, + bool includeReleaseCandidates = false, bool switchToStable = false) { var packagesUpdated = false; @@ -191,7 +195,7 @@ namespace Volo.Abp.Cli.ProjectModification foreach (var abpPackage in abpPackages) { - var updated = await TryUpdatingPackage(filePath, abpPackage, includePreviews, switchToStable); + var updated = await TryUpdatingPackage(filePath, abpPackage, includePreviews, includeReleaseCandidates, switchToStable); if (updated) { @@ -210,18 +214,26 @@ namespace Volo.Abp.Cli.ProjectModification string filePath, JProperty package, bool includePreviews = false, + bool includeReleaseCandidates = false, bool switchToStable = false) { var currentVersion = (string) package.Value; var version = ""; - if (includePreviews || (!switchToStable && currentVersion.Contains("-preview"))) + if ((includePreviews || (!switchToStable && currentVersion.Contains("-preview"))) && !includeReleaseCandidates) { version = "preview"; } else { - version = await GetLatestVersion(package); + if (!switchToStable && SemanticVersion.Parse(currentVersion).IsPrerelease) + { + version = await GetLatestVersion(package, true); + } + else + { + version = await GetLatestVersion(package, includeReleaseCandidates); + } } if (version == currentVersion) @@ -237,14 +249,22 @@ namespace Volo.Abp.Cli.ProjectModification } protected virtual async Task GetLatestVersion( - JProperty package) + JProperty package, + bool includeReleaseCandidates = false) { if (_fileVersionStorage.ContainsKey(package.Name)) { return _fileVersionStorage[package.Name]; } - var newVersion = CmdHelper.RunCmdAndGetOutput($"npm show {package.Name} version"); + var versionListAsJson = CmdHelper.RunCmdAndGetOutput($"npm show {package.Name} versions"); + var versionList = JsonConvert.DeserializeObject(versionListAsJson) + .OrderByDescending(SemanticVersion.Parse, new VersionComparer()).ToList(); + + var newVersion = includeReleaseCandidates + ? versionList.First() + : versionList.FirstOrDefault(v => !SemanticVersion.Parse(v).IsPrerelease); + var newVersionWithPrefix = $"~{newVersion}"; _fileVersionStorage[package.Name] = newVersionWithPrefix; diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackagePreviewSwitcher.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackagePreviewSwitcher.cs new file mode 100644 index 0000000000..60c7fd21b9 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackagePreviewSwitcher.cs @@ -0,0 +1,124 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.Cli.Args; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.ProjectModification +{ + public class PackagePreviewSwitcher : ITransientDependency + { + private readonly PackageSourceManager _packageSourceManager; + private readonly NpmPackagesUpdater _npmPackagesUpdater; + private readonly VoloNugetPackagesVersionUpdater _nugetPackagesVersionUpdater; + + public ILogger Logger { get; set; } + + public PackagePreviewSwitcher(PackageSourceManager packageSourceManager, + NpmPackagesUpdater npmPackagesUpdater, + VoloNugetPackagesVersionUpdater nugetPackagesVersionUpdater) + { + _packageSourceManager = packageSourceManager; + _npmPackagesUpdater = npmPackagesUpdater; + _nugetPackagesVersionUpdater = nugetPackagesVersionUpdater; + Logger = NullLogger.Instance; + } + + public async Task SwitchToPreview(CommandLineArgs commandLineArgs) + { + var solutionPath = GetSolutionPath(commandLineArgs); + var solutionFolder = GetSolutionFolder(commandLineArgs); + + await _nugetPackagesVersionUpdater.UpdateSolutionAsync( + solutionPath, + includeReleaseCandidates: true); + + await _npmPackagesUpdater.Update( + solutionFolder, + false, + true); + } + + public async Task SwitchToNightlyPreview(CommandLineArgs commandLineArgs) + { + var solutionPath = GetSolutionPath(commandLineArgs); + var solutionFolder = GetSolutionFolder(commandLineArgs); + + _packageSourceManager.Add(solutionFolder, "ABP Nightly", "https://www.myget.org/F/abp-nightly/api/v3/index.json"); + + await _nugetPackagesVersionUpdater.UpdateSolutionAsync( + solutionPath, + true); + + await _npmPackagesUpdater.Update( + solutionFolder, + true); + } + + public async Task SwitchToStable(CommandLineArgs commandLineArgs) + { + var solutionPath = GetSolutionPath(commandLineArgs); + var solutionFolder = GetSolutionFolder(commandLineArgs); + + _packageSourceManager.Remove(solutionFolder, "ABP Nightly"); + + await _nugetPackagesVersionUpdater.UpdateSolutionAsync( + solutionPath, + false, + false, + true); + + await _npmPackagesUpdater.Update( + solutionFolder, + false, + false, + true); + } + + private string GetSolutionPath(CommandLineArgs commandLineArgs) + { + var directory = commandLineArgs.Options.GetOrNull(Options.SolutionDirectory.Short, Options.SolutionDirectory.Long) + ?? Directory.GetCurrentDirectory(); + + var solutionPath = Directory.GetFiles(directory, "*.sln").FirstOrDefault(); + + if (solutionPath == null) + { + var subDirectories = Directory.GetDirectories(directory); + + foreach (var subDirectory in subDirectories) + { + var slnInSubDirectory = Directory.GetFiles(subDirectory, "*.sln").FirstOrDefault(); + + if (slnInSubDirectory != null) + { + return Path.Combine(subDirectory, slnInSubDirectory); + } + } + + Logger.LogError("There is no solution or more that one solution in current directory."); + return null; + } + + return solutionPath; + } + + private string GetSolutionFolder(CommandLineArgs commandLineArgs) + { + return commandLineArgs.Options.GetOrNull(Options.SolutionDirectory.Short, Options.SolutionDirectory.Long) + ?? Directory.GetCurrentDirectory(); + } + + public static class Options + { + public static class SolutionDirectory + { + public const string Short = "sd"; + public const string Long = "solution-directory"; + } + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackageSourceAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackageSourceAdder.cs deleted file mode 100644 index 3e651b3bf1..0000000000 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackageSourceAdder.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.IO; -using System.Xml; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Cli.ProjectModification -{ - public class PackageSourceAdder: ITransientDependency - { - public ILogger Logger { get; set; } - - public PackageSourceAdder() - { - Logger = NullLogger.Instance; - } - - public void Add(string sourceKey, string sourceValue) - { - var nugetConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), - "NuGet", "NuGet.Config"); - - if (!File.Exists(nugetConfigPath)) - { - return; - } - - var fileContent = File.ReadAllText(nugetConfigPath); - - if (fileContent.Contains($"\"{sourceValue}\"")) - { - return; - } - - Logger.LogInformation($"Adding \"{sourceValue}\" ({sourceKey}) to nuget sources..."); - - try - { - var doc = new XmlDocument() { PreserveWhitespace = true }; - - doc.Load(GenerateStreamFromString(fileContent)); - - var sourceNodes = doc.SelectNodes("/configuration/packageSources"); - - var newNode = doc.CreateElement("add"); - - var includeAttr = doc.CreateAttribute("key"); - includeAttr.Value = sourceKey; - newNode.Attributes.Append(includeAttr); - - var versionAttr = doc.CreateAttribute("value"); - versionAttr.Value = sourceValue; - newNode.Attributes.Append(versionAttr); - - sourceNodes?[0]?.AppendChild(newNode); - - File.WriteAllText(nugetConfigPath, doc.OuterXml); - } - catch - { - Logger.LogWarning($"Adding \"{sourceValue}\" ({sourceKey}) to nuget sources FAILED."); - } - } - - private static Stream GenerateStreamFromString(string s) - { - var stream = new MemoryStream(); - var writer = new StreamWriter(stream); - writer.Write(s); - writer.Flush(); - stream.Position = 0; - return stream; - } - } -} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackageSourceManager.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackageSourceManager.cs new file mode 100644 index 0000000000..48c6e80030 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackageSourceManager.cs @@ -0,0 +1,126 @@ +using System; +using System.IO; +using System.Xml; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.ProjectModification +{ + public class PackageSourceManager: ITransientDependency + { + public ILogger Logger { get; set; } + + public PackageSourceManager() + { + Logger = NullLogger.Instance; + } + + public void Add(string solutionFolder, string sourceKey, string sourceValue) + { + var nugetConfigPath = GetNugetConfigPath(solutionFolder); + + Logger.LogInformation($"Adding \"{sourceValue}\" ({sourceKey}) to nuget sources..."); + + if (!File.Exists(nugetConfigPath)) + { + File.WriteAllText(nugetConfigPath, "\n" + + "\n" + + " \n" + + " \n" + + $" \n" + + " \n" + + ""); + return; + } + + var fileContent = File.ReadAllText(nugetConfigPath); + + if (fileContent.Contains($"\"{sourceValue}\"")) + { + return; + } + + try + { + var doc = new XmlDocument() { PreserveWhitespace = true }; + + doc.Load(GenerateStreamFromString(fileContent)); + + var sourceNodes = doc.SelectNodes("/configuration/packageSources"); + + var newNode = doc.CreateElement("add"); + + var includeAttr = doc.CreateAttribute("key"); + includeAttr.Value = sourceKey; + newNode.Attributes.Append(includeAttr); + + var versionAttr = doc.CreateAttribute("value"); + versionAttr.Value = sourceValue; + newNode.Attributes.Append(versionAttr); + + sourceNodes?[0]?.AppendChild(newNode); + + File.WriteAllText(nugetConfigPath, doc.OuterXml); + } + catch + { + Logger.LogWarning($"Adding \"{sourceValue}\" ({sourceKey}) to nuget sources FAILED."); + } + } + + public void Remove(string solutionFolder, string sourceKey) + { + var nugetConfigPath = GetNugetConfigPath(solutionFolder); + + if (!File.Exists(nugetConfigPath)) + { + return; + } + + var fileContent = File.ReadAllText(nugetConfigPath); + + if (!fileContent.Contains($"\"{sourceKey}\"")) + { + return; + } + + Logger.LogInformation($"Removing \"{sourceKey}\" from nuget sources..."); + + try + { + var doc = new XmlDocument() { PreserveWhitespace = true }; + + doc.Load(GenerateStreamFromString(fileContent)); + + var nodes = doc.SelectNodes($"/configuration/packageSources/add[@key='{sourceKey}']"); + + if (nodes != null && nodes.Count > 0) + { + nodes[0].ParentNode.RemoveChild(nodes[0]); + } + + File.WriteAllText(nugetConfigPath, doc.OuterXml); + } + catch + { + Logger.LogWarning($"Removing \"{sourceKey}\" from nuget sources FAILED."); + } + } + + private static string GetNugetConfigPath(string solutionFolder) + { + return Path.Combine(solutionFolder, "NuGet.Config"); + } + + private static Stream GenerateStreamFromString(string s) + { + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + writer.Write(s); + writer.Flush(); + stream.Position = 0; + return stream; + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackageSourceSwitcher.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackageSourceSwitcher.cs deleted file mode 100644 index f6c89be039..0000000000 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackageSourceSwitcher.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Volo.Abp.Cli.Args; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Cli.ProjectModification -{ - public class PackageSourceSwitcher : ITransientDependency - { - private readonly PackageSourceAdder _packageSourceAdder; - private readonly NpmPackagesUpdater _npmPackagesUpdater; - private readonly VoloNugetPackagesVersionUpdater _nugetPackagesVersionUpdater; - - public ILogger Logger { get; set; } - - public PackageSourceSwitcher(PackageSourceAdder packageSourceAdder, - NpmPackagesUpdater npmPackagesUpdater, - VoloNugetPackagesVersionUpdater nugetPackagesVersionUpdater) - { - _packageSourceAdder = packageSourceAdder; - _npmPackagesUpdater = npmPackagesUpdater; - _nugetPackagesVersionUpdater = nugetPackagesVersionUpdater; - Logger = NullLogger.Instance; - } - - public async Task SwitchToPreview(CommandLineArgs commandLineArgs) - { - _packageSourceAdder.Add("ABP Nightly", "https://www.myget.org/F/abp-nightly/api/v3/index.json"); - - var solutionPath = GetSolutionPath(commandLineArgs); - var solutionFolder = GetSolutionFolder(commandLineArgs); - - await _nugetPackagesVersionUpdater.UpdateSolutionAsync( - solutionPath, - true); - - await _npmPackagesUpdater.Update( - solutionFolder, - true); - } - - public async Task SwitchToStable(CommandLineArgs commandLineArgs) - { - var solutionPath = GetSolutionPath(commandLineArgs); - var solutionFolder = GetSolutionFolder(commandLineArgs); - - await _nugetPackagesVersionUpdater.UpdateSolutionAsync( - solutionPath, - false, - true); - - await _npmPackagesUpdater.Update( - solutionFolder, - false, - true); - } - - private string GetSolutionPath(CommandLineArgs commandLineArgs) - { - var directory = commandLineArgs.Options.GetOrNull(Options.SolutionDirectory.Short, Options.SolutionDirectory.Long) - ?? Directory.GetCurrentDirectory(); - - var solutionPath = Directory.GetFiles(directory, "*.sln").FirstOrDefault(); - - if (solutionPath == null) - { - var subDirectories = Directory.GetDirectories(directory); - - foreach (var subDirectory in subDirectories) - { - var slnInSubDirectory = Directory.GetFiles(subDirectory, "*.sln").FirstOrDefault(); - - if (slnInSubDirectory != null) - { - return Path.Combine(subDirectory, slnInSubDirectory); - } - } - - Logger.LogError("There is no solution or more that one solution in current directory."); - return null; - } - - return solutionPath; - } - - private string GetSolutionFolder(CommandLineArgs commandLineArgs) - { - return commandLineArgs.Options.GetOrNull(Options.SolutionDirectory.Short, Options.SolutionDirectory.Long) - ?? Directory.GetCurrentDirectory(); - } - - public static class Options - { - public static class SolutionDirectory - { - public const string Short = "sd"; - public const string Long = "solution-directory"; - } - } - } -} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNugetPackageAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNugetPackageAdder.cs index a69957a497..5f1e18efbd 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNugetPackageAdder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNugetPackageAdder.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Net; using System.Threading.Tasks; +using System.Xml; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.Cli.Http; @@ -40,26 +41,36 @@ namespace Volo.Abp.Cli.ProjectModification Logger = NullLogger.Instance; } - public async Task AddAsync(string projectFile, string packageName) + public async Task AddAsync(string projectFile, string packageName, string version = null) { await AddAsync( projectFile, - await FindNugetPackageInfoAsync(packageName) + await FindNugetPackageInfoAsync(packageName), + version ); } - public Task AddAsync(string projectFile, NugetPackageInfo package) + public Task AddAsync(string projectFile, NugetPackageInfo package, string version = null) { - if (File.ReadAllText(projectFile).Contains($"\"{package.Name}\"")) + var projectFileContent = File.ReadAllText(projectFile); + + if (projectFileContent.Contains($"\"{package.Name}\"")) { return Task.CompletedTask; } + if (version == null) + { + version = GetAbpVersionOrNull(projectFileContent); + } + using (DirectoryHelper.ChangeCurrentDirectory(Path.GetDirectoryName(projectFile))) { Logger.LogInformation($"Installing '{package.Name}' package to the project '{Path.GetFileNameWithoutExtension(projectFile)}'..."); - CmdHelper.Run("dotnet", "add package " + package.Name); + var versionOption = version == null ? "" : $" -v {version}"; + + CmdHelper.Run("dotnet", $"add package {package.Name}{versionOption}"); var moduleFiles = ModuleClassFinder.Find(projectFile, "AbpModule"); if (moduleFiles.Count == 0) @@ -80,11 +91,22 @@ namespace Volo.Abp.Cli.ProjectModification return Task.CompletedTask; } - protected virtual async Task FindNugetPackageInfoAsync(string moduleName) + private string GetAbpVersionOrNull(string projectFileContent) + { + var doc = new XmlDocument() { PreserveWhitespace = true }; + + doc.Load(StreamHelper.GenerateStreamFromString(projectFileContent)); + + var nodes = doc.SelectNodes("/Project/ItemGroup/PackageReference[starts-with(@Include, 'Volo.')]"); + + return nodes?[0]?.Attributes?["Version"]?.Value; + } + + protected virtual async Task FindNugetPackageInfoAsync(string packageName) { using (var client = new CliHttpClient()) { - var url = $"{CliUrls.WwwAbpIo}api/app/nugetPackage/byName/?name=" + moduleName; + var url = $"{CliUrls.WwwAbpIo}api/app/nugetPackage/byName/?name=" + packageName; var response = await client.GetAsync(url); @@ -92,7 +114,7 @@ namespace Volo.Abp.Cli.ProjectModification { if (response.StatusCode == HttpStatusCode.NotFound) { - throw new CliUsageException($"'{moduleName}' nuget package could not be found!"); + throw new CliUsageException($"'{packageName}' nuget package could not be found!"); } await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(response); @@ -103,4 +125,4 @@ namespace Volo.Abp.Cli.ProjectModification } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/VoloNugetPackagesVersionUpdater.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/VoloNugetPackagesVersionUpdater.cs index 9661dceddd..275bf23c76 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/VoloNugetPackagesVersionUpdater.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/VoloNugetPackagesVersionUpdater.cs @@ -25,23 +25,30 @@ namespace Volo.Abp.Cli.ProjectModification Logger = NullLogger.Instance; } - public async Task UpdateSolutionAsync(string solutionPath, bool includePreviews = false, bool switchToStable = false, bool checkAll = false) + public async Task UpdateSolutionAsync(string solutionPath, bool includePreviews = false, bool includeReleaseCandidates = false, bool switchToStable = false, bool checkAll = false) { var projectPaths = ProjectFinder.GetProjectFiles(solutionPath); if (checkAll) { - Task.WaitAll(projectPaths.Select(projectPath => UpdateInternalAsync(projectPath, includePreviews, switchToStable)).ToArray()); + Task.WaitAll(projectPaths.Select(projectPath => UpdateInternalAsync(projectPath, includePreviews, includeReleaseCandidates, switchToStable)).ToArray()); } else { - var latestVersionFromNuget = await _nuGetService.GetLatestVersionOrNullAsync("Volo.Abp.Core"); + var latestVersionFromNuget = await _nuGetService.GetLatestVersionOrNullAsync("Volo.Abp.Core", includeReleaseCandidates : includeReleaseCandidates); + var latestReleaseCandidateVersionFromNuget = await _nuGetService.GetLatestVersionOrNullAsync("Volo.Abp.Core", includeReleaseCandidates : true); var latestVersionFromMyGet = await GetLatestVersionFromMyGet("Volo.Abp.Core"); async Task UpdateAsync(string filePath) { var fileContent = File.ReadAllText(filePath); - var updatedContent = await UpdateVoloPackagesAsync(fileContent, includePreviews, switchToStable, latestVersionFromNuget, latestVersionFromMyGet); + var updatedContent = await UpdateVoloPackagesAsync(fileContent, + includePreviews, + includeReleaseCandidates, + switchToStable, + latestVersionFromNuget, + latestReleaseCandidateVersionFromNuget, + latestVersionFromMyGet); File.WriteAllText(filePath, updatedContent); } @@ -50,33 +57,47 @@ namespace Volo.Abp.Cli.ProjectModification } } - public async Task UpdateProjectAsync(string projectPath, bool includePreviews = false, bool switchToStable = false, bool checkAll = false) + public async Task UpdateProjectAsync(string projectPath, bool includeNightlyPreviews = false, bool includeReleaseCandidates = false, bool switchToStable = false, bool checkAll = false) { if (checkAll) { - await UpdateInternalAsync(projectPath, includePreviews, switchToStable); + await UpdateInternalAsync(projectPath, includeNightlyPreviews, includeReleaseCandidates, switchToStable); } else { var latestVersionFromNuget = await _nuGetService.GetLatestVersionOrNullAsync("Volo.Abp.Core"); + var latestReleaseCandidateVersionFromNuget = await _nuGetService.GetLatestVersionOrNullAsync("Volo.Abp.Core", includeReleaseCandidates : true); var latestVersionFromMyGet = await GetLatestVersionFromMyGet("Volo.Abp.Core"); var fileContent = File.ReadAllText(projectPath); - var updatedContent = await UpdateVoloPackagesAsync(fileContent, includePreviews, switchToStable, latestVersionFromNuget, latestVersionFromMyGet); + + var updatedContent = await UpdateVoloPackagesAsync(fileContent, + includeNightlyPreviews, + includeReleaseCandidates, + switchToStable, + latestVersionFromNuget, + latestReleaseCandidateVersionFromNuget, + latestVersionFromMyGet); File.WriteAllText(projectPath, updatedContent); } } - protected virtual async Task UpdateInternalAsync(string projectPath, bool includePreviews = false, bool switchToStable = false) + protected virtual async Task UpdateInternalAsync(string projectPath, bool includeNightlyPreviews = false, bool includeReleaseCandidates = false, bool switchToStable = false) { var fileContent = File.ReadAllText(projectPath); - var updatedContent = await UpdateVoloPackagesAsync(fileContent, includePreviews, switchToStable); + var updatedContent = await UpdateVoloPackagesAsync(fileContent, includeNightlyPreviews, includeReleaseCandidates, switchToStable); File.WriteAllText(projectPath, updatedContent); } - private async Task UpdateVoloPackagesAsync(string content, bool includePreviews = false, bool switchToStable = false, SemanticVersion latestNugetVersion = null, string latestMyGetVersion = null) + private async Task UpdateVoloPackagesAsync(string content, + bool includeNightlyPreviews = false, + bool includeReleaseCandidates = false, + bool switchToStable = false, + SemanticVersion latestNugetVersion = null, + SemanticVersion latestNugetReleaseCandidateVersion = null, + string latestMyGetVersion = null) { string packageId = null; @@ -108,7 +129,7 @@ namespace Volo.Abp.Cli.ProjectModification Logger.LogDebug("Checking package: \"{0}\" - Current version: {1}", packageId, currentSemanticVersion); - if (includePreviews || (currentVersion.Contains("-preview") && !switchToStable)) + if ((includeNightlyPreviews || (currentVersion.Contains("-preview") && !switchToStable)) && !includeReleaseCandidates) { var latestVersion = latestMyGetVersion ?? await GetLatestVersionFromMyGet(packageId); @@ -124,9 +145,17 @@ namespace Volo.Abp.Cli.ProjectModification } else { - var latestVersion = latestNugetVersion ?? await _nuGetService.GetLatestVersionOrNullAsync(packageId); + SemanticVersion latestVersion; + if (currentSemanticVersion.IsPrerelease && !switchToStable) + { + latestVersion = latestNugetReleaseCandidateVersion ?? await _nuGetService.GetLatestVersionOrNullAsync(packageId, includeReleaseCandidates: true); + } + else + { + latestVersion = latestNugetVersion ?? await _nuGetService.GetLatestVersionOrNullAsync(packageId, includeReleaseCandidates: includeReleaseCandidates); + } - if (latestVersion != null && (currentVersion.Contains("-preview") || currentSemanticVersion < latestVersion)) + if (latestVersion != null && (currentSemanticVersion < latestVersion || (currentSemanticVersion.IsPrerelease && switchToStable))) { Logger.LogInformation("Updating package \"{0}\" from v{1} to v{2}.", packageId, currentSemanticVersion.ToString(), latestVersion.ToString()); versionAttribute.Value = latestVersion.ToString(); diff --git a/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj b/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj index 424e09b115..489ea99ac5 100644 --- a/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj +++ b/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj @@ -18,12 +18,12 @@ - - + + - \ No newline at end of file + diff --git a/framework/src/Volo.Abp.Core/Microsoft/Extensions/Logging/AbpLoggerExtensions.cs b/framework/src/Volo.Abp.Core/Microsoft/Extensions/Logging/AbpLoggerExtensions.cs index a711271cd2..5d975b2e58 100644 --- a/framework/src/Volo.Abp.Core/Microsoft/Extensions/Logging/AbpLoggerExtensions.cs +++ b/framework/src/Volo.Abp.Core/Microsoft/Extensions/Logging/AbpLoggerExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text; using Volo.Abp.ExceptionHandling; using Volo.Abp.Logging; @@ -87,12 +88,14 @@ namespace Microsoft.Extensions.Logging return; } - logger.LogWithLevel(logLevel, "---------- Exception Data ----------"); - + var exceptionData = new StringBuilder(); + exceptionData.AppendLine("---------- Exception Data ----------"); foreach (var key in exception.Data.Keys) { - logger.LogWithLevel(logLevel, $"{key} = {exception.Data[key]}"); + exceptionData.AppendLine($"{key} = {exception.Data[key]}"); } + + logger.LogWithLevel(logLevel, exceptionData.ToString()); } private static void LogSelfLogging(ILogger logger, Exception exception) diff --git a/framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs b/framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs index f8086c8ecd..6aa3deb4f0 100644 --- a/framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs +++ b/framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Globalization; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; @@ -312,6 +313,75 @@ namespace System : Regex.Replace(str, "[a-z][A-Z]", m => m.Value[0] + "-" + char.ToLowerInvariant(m.Value[1])); } + /// + /// Converts given PascalCase/camelCase string to snake case. + /// Example: "ThisIsSampleSentence" is converted to "this_is_a_sample_sentence". + /// https://github.com/npgsql/npgsql/blob/dev/src/Npgsql/NameTranslation/NpgsqlSnakeCaseNameTranslator.cs#L51 + /// + /// String to convert. + /// + public static string ToSnakeCase(this string str) + { + if (string.IsNullOrWhiteSpace(str)) + { + return str; + } + + var builder = new StringBuilder(str.Length + Math.Min(2, str.Length / 5)); + var previousCategory = default(UnicodeCategory?); + + for (var currentIndex = 0; currentIndex < str.Length; currentIndex++) + { + var currentChar = str[currentIndex]; + if (currentChar == '_') + { + builder.Append('_'); + previousCategory = null; + continue; + } + + var currentCategory = char.GetUnicodeCategory(currentChar); + switch (currentCategory) + { + case UnicodeCategory.UppercaseLetter: + case UnicodeCategory.TitlecaseLetter: + if (previousCategory == UnicodeCategory.SpaceSeparator || + previousCategory == UnicodeCategory.LowercaseLetter || + previousCategory != UnicodeCategory.DecimalDigitNumber && + previousCategory != null && + currentIndex > 0 && + currentIndex + 1 < str.Length && + char.IsLower(str[currentIndex + 1])) + { + builder.Append('_'); + } + + currentChar = char.ToLower(currentChar); + break; + + case UnicodeCategory.LowercaseLetter: + case UnicodeCategory.DecimalDigitNumber: + if (previousCategory == UnicodeCategory.SpaceSeparator) + { + builder.Append('_'); + } + break; + + default: + if (previousCategory != null) + { + previousCategory = UnicodeCategory.SpaceSeparator; + } + continue; + } + + builder.Append(currentChar); + previousCategory = currentCategory; + } + + return builder.ToString(); + } + /// /// Converts string to enum value. /// @@ -476,4 +546,4 @@ namespace System return encoding.GetBytes(str); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Core/System/Collections/Generic/AbpListExtensions.cs b/framework/src/Volo.Abp.Core/System/Collections/Generic/AbpListExtensions.cs index ec7a640a39..ac71649793 100644 --- a/framework/src/Volo.Abp.Core/System/Collections/Generic/AbpListExtensions.cs +++ b/framework/src/Volo.Abp.Core/System/Collections/Generic/AbpListExtensions.cs @@ -9,6 +9,14 @@ namespace System.Collections.Generic /// public static class AbpListExtensions { + public static void InsertRange(this IList source, int index, IEnumerable items) + { + foreach (var item in items) + { + source.Insert(index++, item); + } + } + public static int FindIndex(this IList source, Predicate selector) { for (var i = 0; i < source.Count; ++i) @@ -182,7 +190,7 @@ namespace System.Collections.Generic /// Function to resolve the dependencies /// /// Returns a new list ordered by dependencies. - /// If A depends on B, then B will come before than A in the resulting list. + /// If A depends on B, then B will come before than A in the resulting list. /// public static List SortByDependencies(this IEnumerable source, Func> getDependencies) { @@ -202,14 +210,15 @@ namespace System.Collections.Generic } /// - /// + /// /// /// The type of the members of values. /// Item to resolve /// Function to resolve the dependencies /// List with the sortet items /// Dictionary with the visited items - private static void SortByDependenciesVisit(T item, Func> getDependencies, List sorted, Dictionary visited) + private static void SortByDependenciesVisit(T item, Func> getDependencies, List sorted, + Dictionary visited) { bool inProcess; var alreadyVisited = visited.TryGetValue(item, out inProcess); diff --git a/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj b/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj index 000433c83d..e1842d0447 100644 --- a/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj +++ b/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj @@ -16,15 +16,15 @@ - - - - - - - - - + + + + + + + + + diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs index 83c2c4c38e..cf3805249d 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs @@ -71,7 +71,7 @@ namespace Volo.Abp.Reflection return true; } - if (includeNullables && IsNullable(type)) + if (includeNullables && IsNullable(type) && type.GenericTypeArguments.Any()) { return IsPrimitiveExtendedInternal(type.GenericTypeArguments[0], includeEnums); } @@ -197,7 +197,7 @@ namespace Volo.Abp.Reflection return $"{genericType.FullName.Left(genericType.FullName.IndexOf('`'))}<{type.GenericTypeArguments.Select(GetFullNameHandlingNullableAndGenerics).JoinAsString(",")}>"; } - return type.FullName; + return type.FullName ?? type.Name; } public static string GetSimplifiedName([NotNull] Type type) @@ -295,8 +295,12 @@ namespace Volo.Abp.Reflection { return "number"; } + else if (type == typeof(object)) + { + return "object"; + } - return type.FullName; + return type.FullName ?? type.Name; } public static object ConvertFromString(string value) diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Localization/Resources/AbpDdd/de.json b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Localization/Resources/AbpDdd/de.json index b96b58c2b3..42bc7edb13 100644 --- a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Localization/Resources/AbpDdd/de.json +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Localization/Resources/AbpDdd/de.json @@ -1,6 +1,6 @@ { "culture": "de", "texts": { - "MaxResultCountExceededExceptionMessage": "{0} kann nicht mehr als {1} sein! Erhöhen Sie {2}.{3} auf der Serverseite, um mehr Ergebnisse zu ermöglichen." + "MaxResultCountExceededExceptionMessage": "{0} kann nicht mehr als {1} sein! Erhöhen Sie {2}.{3} auf der Serverseite, um mehr Ergebnisse zu ermöglichen." } } diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs index 11550c456f..91879f030e 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Application.Dtos; using Volo.Abp.Domain.Entities; @@ -51,6 +52,11 @@ namespace Volo.Abp.Application.Services } + protected override Task MapToGetListOutputDtoAsync(TEntity entity) + { + return MapToGetOutputDtoAsync(entity); + } + protected override TEntityDto MapToGetListOutputDto(TEntity entity) { return MapToGetOutputDto(entity); @@ -80,13 +86,13 @@ namespace Volo.Abp.Application.Services { await CheckCreatePolicyAsync(); - var entity = MapToEntity(input); + var entity = await MapToEntityAsync(input); TryToSetTenantId(entity); await Repository.InsertAsync(entity, autoSave: true); - return MapToGetOutputDto(entity); + return await MapToGetOutputDtoAsync(entity); } public virtual async Task UpdateAsync(TKey id, TUpdateInput input) @@ -95,10 +101,10 @@ namespace Volo.Abp.Application.Services var entity = await GetEntityByIdAsync(id); //TODO: Check if input has id different than given id and normalize if it's default value, throw ex otherwise - MapToEntity(input, entity); + await MapToEntityAsync(input, entity); await Repository.UpdateAsync(entity, autoSave: true); - return MapToGetOutputDto(entity); + return await MapToGetOutputDtoAsync(entity); } public virtual async Task DeleteAsync(TKey id) @@ -125,6 +131,17 @@ namespace Volo.Abp.Application.Services await CheckPolicyAsync(DeletePolicyName); } + /// + /// Maps to to create a new entity. + /// It uses by default. + /// It can be overriden for custom mapping. + /// Overriding this has higher priority than overriding the + /// + protected virtual Task MapToEntityAsync(TCreateInput createInput) + { + return Task.FromResult(MapToEntity(createInput)); + } + /// /// Maps to to create a new entity. /// It uses by default. @@ -153,6 +170,18 @@ namespace Volo.Abp.Application.Services } } + /// + /// Maps to to update the entity. + /// It uses by default. + /// It can be overriden for custom mapping. + /// Overriding this has higher priority than overriding the + /// + protected virtual Task MapToEntityAsync(TUpdateInput updateInput, TEntity entity) + { + MapToEntity(updateInput, entity); + return Task.CompletedTask; + } + /// /// Maps to to update the entity. /// It uses by default. @@ -190,4 +219,4 @@ namespace Volo.Abp.Application.Services return entity.GetType().GetProperty(nameof(IMultiTenant.TenantId)) != null; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs index 9085434211..b9ffe3359b 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Linq.Dynamic.Core; using System.Threading.Tasks; @@ -6,6 +7,7 @@ using Volo.Abp.Application.Dtos; using Volo.Abp.Auditing; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories; +using Volo.Abp.ObjectMapping; namespace Volo.Abp.Application.Services { @@ -52,7 +54,8 @@ namespace Volo.Abp.Application.Services await CheckGetPolicyAsync(); var entity = await GetEntityByIdAsync(id); - return MapToGetOutputDto(entity); + + return await MapToGetOutputDtoAsync(entity); } public virtual async Task> GetListAsync(TGetListInput input) @@ -67,10 +70,11 @@ namespace Volo.Abp.Application.Services query = ApplyPaging(query, input); var entities = await AsyncExecuter.ToListAsync(query); + var entityDtos = await MapToGetListOutputDtosAsync(entities); return new PagedResultDto( totalCount, - entities.Select(MapToGetListOutputDto).ToList() + entityDtos ); } @@ -118,9 +122,9 @@ namespace Volo.Abp.Application.Services /// The query. protected virtual IQueryable ApplyDefaultSorting(IQueryable query) { - if (typeof(TEntity).IsAssignableTo()) + if (typeof(TEntity).IsAssignableTo()) { - return query.OrderByDescending(e => ((ICreationAuditedObject)e).CreationTime); + return query.OrderByDescending(e => ((IHasCreationTime)e).CreationTime); } throw new AbpException("No sorting specified but this query requires sorting. Override the ApplyDefaultSorting method for your application service derived from AbstractKeyReadOnlyAppService!"); @@ -161,6 +165,17 @@ namespace Volo.Abp.Application.Services return ReadOnlyRepository; } + /// + /// Maps to . + /// It internally calls the by default. + /// It can be overriden for custom mapping. + /// Overriding this has higher priority than overriding the + /// + protected virtual Task MapToGetOutputDtoAsync(TEntity entity) + { + return Task.FromResult(MapToGetOutputDto(entity)); + } + /// /// Maps to . /// It uses by default. @@ -171,6 +186,33 @@ namespace Volo.Abp.Application.Services return ObjectMapper.Map(entity); } + /// + /// Maps a list of to objects. + /// It uses method for each item in the list. + /// + protected virtual async Task> MapToGetListOutputDtosAsync(List entities) + { + var dtos = new List(); + + foreach (var entity in entities) + { + dtos.Add(await MapToGetListOutputDtoAsync(entity)); + } + + return dtos; + } + + /// + /// Maps to . + /// It internally calls the by default. + /// It can be overriden for custom mapping. + /// Overriding this has higher priority than overriding the + /// + protected virtual Task MapToGetListOutputDtoAsync(TEntity entity) + { + return Task.FromResult(MapToGetListOutputDto(entity)); + } + /// /// Maps to . /// It uses by default. diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs index 7832e8a4ab..db66881fb7 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs @@ -55,6 +55,11 @@ namespace Volo.Abp.Application.Services } + protected override Task MapToGetListOutputDtoAsync(TEntity entity) + { + return base.MapToGetOutputDtoAsync(entity); + } + protected override TEntityDto MapToGetListOutputDto(TEntity entity) { return MapToGetOutputDto(entity); @@ -97,9 +102,9 @@ namespace Volo.Abp.Application.Services protected override IQueryable ApplyDefaultSorting(IQueryable query) { - if (typeof(TEntity).IsAssignableTo()) + if (typeof(TEntity).IsAssignableTo()) { - return query.OrderByDescending(e => ((ICreationAuditedObject)e).CreationTime); + return query.OrderByDescending(e => ((IHasCreationTime)e).CreationTime); } else { diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/AggregateRoot.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/AggregateRoot.cs index b0c20131ef..09af979e93 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/AggregateRoot.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/AggregateRoot.cs @@ -9,9 +9,7 @@ using Volo.Abp.ObjectExtending; namespace Volo.Abp.Domain.Entities { [Serializable] - public abstract class AggregateRoot : Entity, - IAggregateRoot, - IGeneratesDomainEvents, + public abstract class AggregateRoot : BasicAggregateRoot, IHasExtraProperties, IHasConcurrencyStamp { @@ -20,9 +18,6 @@ namespace Volo.Abp.Domain.Entities [DisableAuditing] public virtual string ConcurrencyStamp { get; set; } - private readonly ICollection _localEvents = new Collection(); - private readonly ICollection _distributedEvents = new Collection(); - protected AggregateRoot() { ConcurrencyStamp = Guid.NewGuid().ToString("N"); @@ -30,36 +25,6 @@ namespace Volo.Abp.Domain.Entities this.SetDefaultsForExtraProperties(); } - protected virtual void AddLocalEvent(object eventData) - { - _localEvents.Add(eventData); - } - - protected virtual void AddDistributedEvent(object eventData) - { - _distributedEvents.Add(eventData); - } - - public virtual IEnumerable GetLocalEvents() - { - return _localEvents; - } - - public virtual IEnumerable GetDistributedEvents() - { - return _distributedEvents; - } - - public virtual void ClearLocalEvents() - { - _localEvents.Clear(); - } - - public virtual void ClearDistributedEvents() - { - _distributedEvents.Clear(); - } - public virtual IEnumerable Validate(ValidationContext validationContext) { return ExtensibleObjectValidator.GetValidationErrors( @@ -70,9 +35,7 @@ namespace Volo.Abp.Domain.Entities } [Serializable] - public abstract class AggregateRoot : Entity, - IAggregateRoot, - IGeneratesDomainEvents, + public abstract class AggregateRoot : BasicAggregateRoot, IHasExtraProperties, IHasConcurrencyStamp { @@ -81,9 +44,6 @@ namespace Volo.Abp.Domain.Entities [DisableAuditing] public virtual string ConcurrencyStamp { get; set; } - private readonly ICollection _localEvents = new Collection(); - private readonly ICollection _distributedEvents = new Collection(); - protected AggregateRoot() { ConcurrencyStamp = Guid.NewGuid().ToString("N"); @@ -99,36 +59,6 @@ namespace Volo.Abp.Domain.Entities this.SetDefaultsForExtraProperties(); } - protected virtual void AddLocalEvent(object eventData) - { - _localEvents.Add(eventData); - } - - protected virtual void AddDistributedEvent(object eventData) - { - _distributedEvents.Add(eventData); - } - - public virtual IEnumerable GetLocalEvents() - { - return _localEvents; - } - - public virtual IEnumerable GetDistributedEvents() - { - return _distributedEvents; - } - - public virtual void ClearLocalEvents() - { - _localEvents.Clear(); - } - - public virtual void ClearDistributedEvents() - { - _distributedEvents.Clear(); - } - public virtual IEnumerable Validate(ValidationContext validationContext) { return ExtensibleObjectValidator.GetValidationErrors( @@ -137,4 +67,4 @@ namespace Volo.Abp.Domain.Entities ); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/BasicAggregateRoot.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/BasicAggregateRoot.cs new file mode 100644 index 0000000000..42b0ba1320 --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/BasicAggregateRoot.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Volo.Abp.Domain.Entities +{ + [Serializable] + public abstract class BasicAggregateRoot : Entity, + IAggregateRoot, + IGeneratesDomainEvents + { + private readonly ICollection _distributedEvents = new Collection(); + private readonly ICollection _localEvents = new Collection(); + + public virtual IEnumerable GetLocalEvents() + { + return _localEvents; + } + + public virtual IEnumerable GetDistributedEvents() + { + return _distributedEvents; + } + + public virtual void ClearLocalEvents() + { + _localEvents.Clear(); + } + + public virtual void ClearDistributedEvents() + { + _distributedEvents.Clear(); + } + + protected virtual void AddLocalEvent(object eventData) + { + _localEvents.Add(eventData); + } + + protected virtual void AddDistributedEvent(object eventData) + { + _distributedEvents.Add(eventData); + } + } + + [Serializable] + public abstract class BasicAggregateRoot : Entity, + IAggregateRoot, + IGeneratesDomainEvents + { + private readonly ICollection _distributedEvents = new Collection(); + private readonly ICollection _localEvents = new Collection(); + + protected BasicAggregateRoot() + { + + } + + protected BasicAggregateRoot(TKey id) + : base(id) + { + + } + + public virtual IEnumerable GetLocalEvents() + { + return _localEvents; + } + + public virtual IEnumerable GetDistributedEvents() + { + return _distributedEvents; + } + + public virtual void ClearLocalEvents() + { + _localEvents.Clear(); + } + + public virtual void ClearDistributedEvents() + { + _distributedEvents.Clear(); + } + + protected virtual void AddLocalEvent(object eventData) + { + _localEvents.Add(eventData); + } + + protected virtual void AddDistributedEvent(object eventData) + { + _distributedEvents.Add(eventData); + } + } +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityCreatedEto.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityCreatedEto.cs index d39b69c857..c96b5cc366 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityCreatedEto.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityCreatedEto.cs @@ -1,11 +1,12 @@ using System; using Volo.Abp.EventBus; +using Volo.Abp.MultiTenancy; namespace Volo.Abp.Domain.Entities.Events.Distributed { [Serializable] [GenericEventName(Postfix = ".Created")] - public class EntityCreatedEto + public class EntityCreatedEto : IEventDataMayHaveTenantId { public TEntityEto Entity { get; set; } @@ -13,5 +14,17 @@ namespace Volo.Abp.Domain.Entities.Events.Distributed { Entity = entity; } + + public virtual bool IsMultiTenant(out Guid? tenantId) + { + if (Entity is IMultiTenant multiTenantEntity) + { + tenantId = multiTenantEntity.TenantId; + return true; + } + + tenantId = null; + return false; + } } } diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityDeletedEto.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityDeletedEto.cs index 9cf930d7db..6f9594427a 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityDeletedEto.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityDeletedEto.cs @@ -1,11 +1,12 @@ using System; using Volo.Abp.EventBus; +using Volo.Abp.MultiTenancy; namespace Volo.Abp.Domain.Entities.Events.Distributed { [Serializable] [GenericEventName(Postfix = ".Deleted")] - public class EntityDeletedEto + public class EntityDeletedEto : IEventDataMayHaveTenantId { public TEntityEto Entity { get; set; } @@ -13,5 +14,17 @@ namespace Volo.Abp.Domain.Entities.Events.Distributed { Entity = entity; } + + public virtual bool IsMultiTenant(out Guid? tenantId) + { + if (Entity is IMultiTenant multiTenantEntity) + { + tenantId = multiTenantEntity.TenantId; + return true; + } + + tenantId = null; + return false; + } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityUpdatedEto.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityUpdatedEto.cs index 654dd4243f..54b0454903 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityUpdatedEto.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityUpdatedEto.cs @@ -1,11 +1,12 @@ using System; using Volo.Abp.EventBus; +using Volo.Abp.MultiTenancy; namespace Volo.Abp.Domain.Entities.Events.Distributed { [Serializable] [GenericEventName(Postfix = ".Updated")] - public class EntityUpdatedEto + public class EntityUpdatedEto : IEventDataMayHaveTenantId { public TEntityEto Entity { get; set; } @@ -13,5 +14,17 @@ namespace Volo.Abp.Domain.Entities.Events.Distributed { Entity = entity; } + + public virtual bool IsMultiTenant(out Guid? tenantId) + { + if (Entity is IMultiTenant multiTenantEntity) + { + tenantId = multiTenantEntity.TenantId; + return true; + } + + tenantId = null; + return false; + } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntityEventData.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntityEventData.cs index 698648ead4..d8ade9c01c 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntityEventData.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntityEventData.cs @@ -1,5 +1,6 @@ using System; using Volo.Abp.EventBus; +using Volo.Abp.MultiTenancy; namespace Volo.Abp.Domain.Entities.Events { @@ -8,7 +9,7 @@ namespace Volo.Abp.Domain.Entities.Events /// /// Entity type [Serializable] - public class EntityEventData : IEventDataWithInheritableGenericArgument + public class EntityEventData : IEventDataWithInheritableGenericArgument, IEventDataMayHaveTenantId { /// /// Related entity with this event. @@ -28,5 +29,17 @@ namespace Volo.Abp.Domain.Entities.Events { return new object[] { Entity }; } + + public virtual bool IsMultiTenant(out Guid? tenantId) + { + if (Entity is IMultiTenant multiTenantEntity) + { + tenantId = multiTenantEntity.TenantId; + return true; + } + + tenantId = null; + return false; + } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/BasicRepositoryBase.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/BasicRepositoryBase.cs index d13d52ed97..ae1b03475c 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/BasicRepositoryBase.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/BasicRepositoryBase.cs @@ -35,6 +35,8 @@ namespace Volo.Abp.Domain.Repositories public abstract Task GetCountAsync(CancellationToken cancellationToken = default); + public abstract Task> GetPagedListAsync(int skipCount, int maxResultCount, string sorting, bool includeDetails = false, CancellationToken cancellationToken = default); + protected virtual CancellationToken GetCancellationToken(CancellationToken preferredValue = default) { return CancellationTokenProvider.FallbackToProvider(preferredValue); diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IReadOnlyBasicRepository.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IReadOnlyBasicRepository.cs index 828e305ff8..b0d270a97e 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IReadOnlyBasicRepository.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IReadOnlyBasicRepository.cs @@ -21,6 +21,15 @@ namespace Volo.Abp.Domain.Repositories /// Gets total count of all entities. /// Task GetCountAsync(CancellationToken cancellationToken = default); + + Task> GetPagedListAsync( + int skipCount, + int maxResultCount, + string sorting, + bool includeDetails = false, + CancellationToken cancellationToken = default); + + } public interface IReadOnlyBasicRepository : IReadOnlyBasicRepository diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/ar.json b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/ar.json index 23b8e152d7..a4bcaee0a2 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/ar.json +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/ar.json @@ -1,4 +1,4 @@ -{ +{ "culture": "ar", "texts": { "DisplayName:Abp.Mailing.DefaultFromAddress": "العنوان الإفتراضي", @@ -18,6 +18,8 @@ "Description:Abp.Mailing.Smtp.Password": "كلمة المرور لاسم المستخدم المرتبط ببيانات الاعتماد.", "Description:Abp.Mailing.Smtp.Domain": "اسم المجال أو الكمبيوتر الذي يتحقق من بيانات الاعتماد.", "Description:Abp.Mailing.Smtp.EnableSsl": "ما إذا كان SmtpClient يستخدم (SSL) لتشفير الاتصال.", - "Description:Abp.Mailing.Smtp.UseDefaultCredentials": "إرسال الصلاحيات الافتراضية مع الطلب." + "Description:Abp.Mailing.Smtp.UseDefaultCredentials": "إرسال الصلاحيات الافتراضية مع الطلب.", + "TextTemplate:StandardEmailTemplates.Layout": "نموذج تخطيط إفتراضي للبريد الإلكتروني", + "TextTemplate:StandardEmailTemplates.Message": "نموذج بسيط للرسائل الإلكترونية" } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/de.json b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/de.json index 8d6ae8c9ff..b2de67d39e 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/de.json +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/de.json @@ -12,12 +12,12 @@ "DisplayName:Abp.Mailing.Smtp.UseDefaultCredentials": "Standard-Anmeldeinformationen verwenden", "Description:Abp.Mailing.DefaultFromAddress": "Die Standard-Absenderadresse", "Description:Abp.Mailing.DefaultFromDisplayName": "Der Standard-Absendername", - "Description:Abp.Mailing.Smtp.Host": "Der Name oder die IP-Adresse des für SMTP-Transaktionen verwendeten Hosts.", - "Description:Abp.Mailing.Smtp.Port": "Der für SMTP-Transaktionen verwendete Port.", - "Description:Abp.Mailing.Smtp.UserName": "Benutzername, der mit den Anmeldedaten verknüpft ist.", - "Description:Abp.Mailing.Smtp.Password": "Das Passwort für den Benutzernamen, der mit den Anmeldeinformationen verknüpft ist.", - "Description:Abp.Mailing.Smtp.Domain": "Die Domäne oder der Computername, der die Anmeldeinformationen verifiziert.", - "Description:Abp.Mailing.Smtp.EnableSsl": "Bestimmt, ob der SmptClient Secure Sockets Layer (SSL) zur Verschlüsselung der Verbindung verwendet.", + "Description:Abp.Mailing.Smtp.Host": "Der Name oder die IP-Adresse des für SMTP-Transaktionen verwendeten Hosts.", + "Description:Abp.Mailing.Smtp.Port": "Der für SMTP-Transaktionen verwendete Port.", + "Description:Abp.Mailing.Smtp.UserName": "Benutzername, der mit den Anmeldedaten verknüpft ist.", + "Description:Abp.Mailing.Smtp.Password": "Das Passwort für den Benutzernamen, der mit den Anmeldeinformationen verknüpft ist.", + "Description:Abp.Mailing.Smtp.Domain": "Die Domäne oder der Computername, der die Anmeldeinformationen verifiziert.", + "Description:Abp.Mailing.Smtp.EnableSsl": "Bestimmt, ob der SmptClient Secure Sockets Layer (SSL) zur Verschlüsselung der Verbindung verwendet.", "Description:Abp.Mailing.Smtp.UseDefaultCredentials": "Bestimmt, ob die DefaultCredentials mit Anfragen gesendet werden." } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo.Abp.EntityFrameworkCore.SqlServer.csproj b/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo.Abp.EntityFrameworkCore.SqlServer.csproj index 988c3688ee..366958b213 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo.Abp.EntityFrameworkCore.SqlServer.csproj +++ b/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo.Abp.EntityFrameworkCore.SqlServer.csproj @@ -19,7 +19,7 @@ - + diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo.Abp.EntityFrameworkCore.Sqlite.csproj b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo.Abp.EntityFrameworkCore.Sqlite.csproj index ce5c9a1716..6a8fd26db2 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo.Abp.EntityFrameworkCore.Sqlite.csproj +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo.Abp.EntityFrameworkCore.Sqlite.csproj @@ -2,7 +2,7 @@ - + netstandard2.0 Volo.Abp.EntityFrameworkCore.Sqlite @@ -13,11 +13,11 @@ false - + - + - + diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj b/framework/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj index da97fb5977..4c6fb7fbbc 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj @@ -20,8 +20,8 @@ - - + + diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs index a48e5d7da5..4f2ce6b434 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Dynamic.Core; using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; @@ -10,10 +11,11 @@ using Microsoft.Extensions.Options; using Volo.Abp.Domain.Entities; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore.DependencyInjection; +using Volo.Abp.Guids; namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore { - public class EfCoreRepository : RepositoryBase, IEfCoreRepository + public class EfCoreRepository : RepositoryBase, IEfCoreRepository, IAsyncEnumerable where TDbContext : IEfCoreDbContext where TEntity : class, IEntity { @@ -28,9 +30,12 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore private readonly IDbContextProvider _dbContextProvider; private readonly Lazy> _entityOptionsLazy; + protected virtual IGuidGenerator GuidGenerator { get; set; } + public EfCoreRepository(IDbContextProvider dbContextProvider) { _dbContextProvider = dbContextProvider; + GuidGenerator = SimpleGuidGenerator.Instance; _entityOptionsLazy = new Lazy>( () => ServiceProvider @@ -39,9 +44,11 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore .GetOrNull() ?? AbpEntityOptions.Empty ); } - + public override async Task InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default) { + CheckAndSetId(entity); + var savedEntity = DbSet.Add(entity).Entity; if (autoSave) @@ -65,7 +72,7 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore return updatedEntity; } - + public override async Task DeleteAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default) { DbSet.Remove(entity); @@ -88,13 +95,28 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore return await DbSet.LongCountAsync(GetCancellationToken(cancellationToken)); } + public override async Task> GetPagedListAsync( + int skipCount, + int maxResultCount, + string sorting, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + var queryable = includeDetails ? WithDetails() : DbSet; + + return await queryable + .OrderBy(sorting) + .PageBy(skipCount, maxResultCount) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + protected override IQueryable GetQueryable() { return DbSet.AsQueryable(); } public override async Task FindAsync( - Expression> predicate, + Expression> predicate, bool includeDetails = true, CancellationToken cancellationToken = default) { @@ -172,16 +194,43 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore return query; } + + public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return DbSet.AsAsyncEnumerable().GetAsyncEnumerator(cancellationToken); + } + + protected virtual void CheckAndSetId(TEntity entity) + { + if (entity is IEntity entityWithGuidId) + { + TrySetGuidId(entityWithGuidId); + } + } + + protected virtual void TrySetGuidId(IEntity entity) + { + if (entity.Id != default) + { + return; + } + + EntityHelper.TrySetId( + entity, + () => GuidGenerator.Create(), + true + ); + } } - public class EfCoreRepository : EfCoreRepository, + public class EfCoreRepository : EfCoreRepository, IEfCoreRepository, ISupportsExplicitLoading where TDbContext : IEfCoreDbContext where TEntity : class, IEntity { - public EfCoreRepository(IDbContextProvider dbContextProvider) + public EfCoreRepository(IDbContextProvider dbContextProvider) : base(dbContextProvider) { diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs index 92ac4a8d8c..c82912f0f1 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; +using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -182,6 +183,14 @@ namespace Volo.Abp.EntityFrameworkCore } } + /// + /// This method will call the DbContext method directly of EF Core, which doesn't apply concepts of abp. + /// + public virtual Task SaveChangesOnDbContextAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) + { + return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); + } + public virtual void Initialize(AbpEfCoreDbContextInitializationContext initializationContext) { if (initializationContext.UnitOfWork.Options.Timeout.HasValue && @@ -314,7 +323,25 @@ namespace Volo.Abp.EntityFrameworkCore continue; } - entry.Property(property.Name).CurrentValue = entity.GetProperty(property.Name); + var entryProperty = entry.Property(property.Name); + var entityProperty = entity.GetProperty(property.Name); + if (entityProperty == null) + { + entryProperty.CurrentValue = null; + continue; + } + + if (entryProperty.Metadata.ClrType == entityProperty.GetType()) + { + entryProperty.CurrentValue = entityProperty; + } + else + { + if (TypeHelper.IsPrimitiveExtended(entryProperty.Metadata.ClrType, includeEnums: true)) + { + entryProperty.CurrentValue = Convert.ChangeType(entityProperty, entryProperty.Metadata.ClrType, CultureInfo.InvariantCulture); + } + } } } @@ -491,6 +518,11 @@ namespace Volo.Abp.EntityFrameworkCore return; } + if (!typeof(IEntity).IsAssignableFrom(typeof(TEntity))) + { + return; + } + modelBuilder.Entity().ConfigureByConvention(); ConfigureGlobalFilters(modelBuilder, mutableEntityType); 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 796c6e20fb..50de4f1b56 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 @@ -242,7 +242,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory } } - if (IsBaseAuditProperty(propertyInfo, entityType)) + if (propertyInfo != null && IsBaseAuditProperty(propertyInfo, entityType)) { return false; } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/IEfCoreDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/IEfCoreDbContext.cs index 761e15b6f2..931cbd6d04 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/IEfCoreDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/IEfCoreDbContext.cs @@ -24,6 +24,11 @@ namespace Volo.Abp.EntityFrameworkCore Task SaveChangesAsync(CancellationToken cancellationToken = default); + /// + /// This method will call the DbContext method directly of EF Core, which doesn't apply concepts of abp. + /// + Task SaveChangesOnDbContextAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default); + DbSet Set() where T: class; @@ -50,7 +55,7 @@ namespace Volo.Abp.EntityFrameworkCore void AttachRange([NotNull] IEnumerable entities); void AttachRange([NotNull] params object[] entities); - + EntityEntry Entry([NotNull] TEntity entity) where TEntity : class; EntityEntry Entry([NotNull] object entity); diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionInfoExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionInfoExtensions.cs index ef3d87119a..91184ecdeb 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionInfoExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionInfoExtensions.cs @@ -37,5 +37,37 @@ namespace Volo.Abp.ObjectExtending } ); } + + public static ObjectExtensionInfo MapEfCoreProperty( + [NotNull] this ObjectExtensionInfo objectExtensionInfo, + [NotNull] string propertyName, + [CanBeNull] Action entityTypeAndPropertyBuildAction) + { + return objectExtensionInfo.MapEfCoreProperty( + typeof(TProperty), + propertyName, + entityTypeAndPropertyBuildAction + ); + } + + public static ObjectExtensionInfo MapEfCoreProperty( + [NotNull] this ObjectExtensionInfo objectExtensionInfo, + [NotNull] Type propertyType, + [NotNull] string propertyName, + [CanBeNull] Action entityTypeAndPropertyBuildAction) + { + Check.NotNull(objectExtensionInfo, nameof(objectExtensionInfo)); + + return objectExtensionInfo.AddOrUpdateProperty( + propertyType, + propertyName, + options => + { + options.MapEfCore( + entityTypeAndPropertyBuildAction + ); + } + ); + } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionManagerExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionManagerExtensions.cs index 52dca0c105..cf6d0e1368 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionManagerExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionManagerExtensions.cs @@ -8,10 +8,41 @@ namespace Volo.Abp.ObjectExtending { public static class EfCoreObjectExtensionManagerExtensions { + public static ObjectExtensionManager MapEfCoreProperty( + [NotNull] this ObjectExtensionManager objectExtensionManager, + [NotNull] string propertyName) + where TEntity : IHasExtraProperties, IEntity + { + return objectExtensionManager.MapEfCoreProperty( + typeof(TEntity), + typeof(TProperty), + propertyName + ); + } + + public static ObjectExtensionManager MapEfCoreProperty( + [NotNull] this ObjectExtensionManager objectExtensionManager, + [NotNull] Type entityType, + [NotNull] Type propertyType, + [NotNull] string propertyName) + { + Check.NotNull(objectExtensionManager, nameof(objectExtensionManager)); + + return objectExtensionManager.AddOrUpdateProperty( + entityType, + propertyType, + propertyName, + options => + { + options.MapEfCore(); + } + ); + } + public static ObjectExtensionManager MapEfCoreProperty( [NotNull] this ObjectExtensionManager objectExtensionManager, [NotNull] string propertyName, - [CanBeNull] Action propertyBuildAction = null) + [CanBeNull] Action propertyBuildAction) where TEntity : IHasExtraProperties, IEntity { return objectExtensionManager.MapEfCoreProperty( @@ -27,7 +58,7 @@ namespace Volo.Abp.ObjectExtending [NotNull] Type entityType, [NotNull] Type propertyType, [NotNull] string propertyName, - [CanBeNull] Action propertyBuildAction = null) + [CanBeNull] Action propertyBuildAction) { Check.NotNull(objectExtensionManager, nameof(objectExtensionManager)); @@ -44,6 +75,42 @@ namespace Volo.Abp.ObjectExtending ); } + public static ObjectExtensionManager MapEfCoreProperty( + [NotNull] this ObjectExtensionManager objectExtensionManager, + [NotNull] string propertyName, + [CanBeNull] Action entityTypeAndPropertyBuildAction) + where TEntity : IHasExtraProperties, IEntity + { + return objectExtensionManager.MapEfCoreProperty( + typeof(TEntity), + typeof(TProperty), + propertyName, + entityTypeAndPropertyBuildAction + ); + } + + public static ObjectExtensionManager MapEfCoreProperty( + [NotNull] this ObjectExtensionManager objectExtensionManager, + [NotNull] Type entityType, + [NotNull] Type propertyType, + [NotNull] string propertyName, + [CanBeNull] Action entityTypeAndPropertyBuildAction) + { + Check.NotNull(objectExtensionManager, nameof(objectExtensionManager)); + + return objectExtensionManager.AddOrUpdateProperty( + entityType, + propertyType, + propertyName, + options => + { + options.MapEfCore( + entityTypeAndPropertyBuildAction + ); + } + ); + } + public static void ConfigureEfCoreEntity( [NotNull] this ObjectExtensionManager objectExtensionManager, [NotNull] EntityTypeBuilder typeBuilder) @@ -73,6 +140,7 @@ namespace Volo.Abp.ObjectExtending var propertyBuilder = typeBuilder.Property(property.Type, property.Name); + efCoreMapping.EntityTypeAndPropertyBuildAction?.Invoke(typeBuilder, propertyBuilder); efCoreMapping.PropertyBuildAction?.Invoke(propertyBuilder); } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionPropertyInfoExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionPropertyInfoExtensions.cs index fda5d88ceb..9fb466c4cd 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionPropertyInfoExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionPropertyInfoExtensions.cs @@ -9,10 +9,24 @@ namespace Volo.Abp.ObjectExtending { public const string EfCorePropertyConfigurationName = "EfCoreMapping"; + [NotNull] + public static ObjectExtensionPropertyInfo MapEfCore( + [NotNull] this ObjectExtensionPropertyInfo propertyExtension) + { + Check.NotNull(propertyExtension, nameof(propertyExtension)); + + propertyExtension.Configuration[EfCorePropertyConfigurationName] = + new ObjectExtensionPropertyInfoEfCoreMappingOptions( + propertyExtension + ); + + return propertyExtension; + } + [NotNull] public static ObjectExtensionPropertyInfo MapEfCore( [NotNull] this ObjectExtensionPropertyInfo propertyExtension, - [CanBeNull] Action propertyBuildAction = null) + [CanBeNull] Action propertyBuildAction) { Check.NotNull(propertyExtension, nameof(propertyExtension)); @@ -25,6 +39,22 @@ namespace Volo.Abp.ObjectExtending return propertyExtension; } + [NotNull] + public static ObjectExtensionPropertyInfo MapEfCore( + [NotNull] this ObjectExtensionPropertyInfo propertyExtension, + [CanBeNull] Action entityTypeAndPropertyBuildAction) + { + Check.NotNull(propertyExtension, nameof(propertyExtension)); + + propertyExtension.Configuration[EfCorePropertyConfigurationName] = + new ObjectExtensionPropertyInfoEfCoreMappingOptions( + propertyExtension, + entityTypeAndPropertyBuildAction: entityTypeAndPropertyBuildAction + ); + + return propertyExtension; + } + [CanBeNull] public static ObjectExtensionPropertyInfoEfCoreMappingOptions GetEfCoreMappingOrNull( [NotNull] this ObjectExtensionPropertyInfo propertyExtension) diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoEfCoreMappingOptions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoEfCoreMappingOptions.cs index 4189c87962..15a818478b 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoEfCoreMappingOptions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoEfCoreMappingOptions.cs @@ -15,13 +15,18 @@ namespace Volo.Abp.ObjectExtending [CanBeNull] public Action PropertyBuildAction { get; set; } + [CanBeNull] + public Action EntityTypeAndPropertyBuildAction { get; set; } + public ObjectExtensionPropertyInfoEfCoreMappingOptions( [NotNull] ObjectExtensionPropertyInfo extensionProperty, - [CanBeNull] Action propertyBuildAction = null) + [CanBeNull] Action propertyBuildAction = null, + [CanBeNull] Action entityTypeAndPropertyBuildAction = null) { ExtensionProperty = Check.NotNull(extensionProperty, nameof(extensionProperty)); - + PropertyBuildAction = propertyBuildAction; + EntityTypeAndPropertyBuildAction = entityTypeAndPropertyBuildAction; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs index ed7d37db20..e8c7fc6c5b 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs @@ -9,6 +9,7 @@ using RabbitMQ.Client; using RabbitMQ.Client.Events; using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus.Distributed; +using Volo.Abp.MultiTenancy; using Volo.Abp.RabbitMQ; using Volo.Abp.Threading; @@ -26,7 +27,7 @@ namespace Volo.Abp.EventBus.RabbitMq protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; } protected IConnectionPool ConnectionPool { get; } protected IRabbitMqSerializer Serializer { get; } - + //TODO: Accessing to the List may not be thread-safe! protected ConcurrentDictionary> HandlerFactories { get; } protected ConcurrentDictionary EventTypes { get; } @@ -37,17 +38,18 @@ namespace Volo.Abp.EventBus.RabbitMq IOptions options, IConnectionPool connectionPool, IRabbitMqSerializer serializer, - IServiceScopeFactory serviceScopeFactory, + IServiceScopeFactory serviceScopeFactory, IOptions distributedEventBusOptions, - IRabbitMqMessageConsumerFactory messageConsumerFactory) - : base(serviceScopeFactory) + IRabbitMqMessageConsumerFactory messageConsumerFactory, + ICurrentTenant currentTenant) + : base(serviceScopeFactory, currentTenant) { ConnectionPool = connectionPool; Serializer = serializer; MessageConsumerFactory = messageConsumerFactory; AbpDistributedEventBusOptions = distributedEventBusOptions.Value; AbpRabbitMqEventBusOptions = options.Value; - + HandlerFactories = new ConcurrentDictionary>(); EventTypes = new ConcurrentDictionary(); } @@ -178,7 +180,7 @@ namespace Volo.Abp.EventBus.RabbitMq "direct", durable: true ); - + var properties = channel.CreateBasicProperties(); properties.DeliveryMode = RabbitMqConsts.DeliveryModes.Persistent; diff --git a/framework/src/Volo.Abp.EventBus/Volo.Abp.EventBus.csproj b/framework/src/Volo.Abp.EventBus/Volo.Abp.EventBus.csproj index 566767e111..a1799c6673 100644 --- a/framework/src/Volo.Abp.EventBus/Volo.Abp.EventBus.csproj +++ b/framework/src/Volo.Abp.EventBus/Volo.Abp.EventBus.csproj @@ -16,6 +16,7 @@ + diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs index c5a5022f77..81c3393a50 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs @@ -4,10 +4,12 @@ using System.Collections.Generic; using Volo.Abp.EventBus.Distributed; using Volo.Abp.EventBus.Local; using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; using Volo.Abp.Reflection; namespace Volo.Abp.EventBus { + [DependsOn(typeof(AbpMultiTenancyModule))] public class AbpEventBusModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs index a7883950d1..80d4134db2 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Collections; using Volo.Abp.EventBus.Distributed; +using Volo.Abp.MultiTenancy; using Volo.Abp.Reflection; namespace Volo.Abp.EventBus @@ -16,9 +17,12 @@ namespace Volo.Abp.EventBus { protected IServiceScopeFactory ServiceScopeFactory { get; } - protected EventBusBase(IServiceScopeFactory serviceScopeFactory) + protected ICurrentTenant CurrentTenant { get; } + + protected EventBusBase(IServiceScopeFactory serviceScopeFactory, ICurrentTenant currentTenant) { ServiceScopeFactory = serviceScopeFactory; + CurrentTenant = currentTenant; } /// @@ -162,31 +166,34 @@ namespace Volo.Abp.EventBus { var handlerType = eventHandlerWrapper.EventHandler.GetType(); - if (ReflectionHelper.IsAssignableToGenericType(handlerType, typeof(ILocalEventHandler<>))) - { - var method = typeof(ILocalEventHandler<>) - .MakeGenericType(eventType) - .GetMethod( - nameof(ILocalEventHandler.HandleEventAsync), - new[] { eventType } - ); - - await ((Task)method.Invoke(eventHandlerWrapper.EventHandler, new[] { eventData })); - } - else if (ReflectionHelper.IsAssignableToGenericType(handlerType, typeof(IDistributedEventHandler<>))) - { - var method = typeof(IDistributedEventHandler<>) - .MakeGenericType(eventType) - .GetMethod( - nameof(IDistributedEventHandler.HandleEventAsync), - new[] { eventType } - ); - - await ((Task)method.Invoke(eventHandlerWrapper.EventHandler, new[] { eventData })); - } - else + using (CurrentTenant.Change(GetEventDataTenantId(eventData))) { - throw new AbpException("The object instance is not an event handler. Object type: " + handlerType.AssemblyQualifiedName); + if (ReflectionHelper.IsAssignableToGenericType(handlerType, typeof(ILocalEventHandler<>))) + { + var method = typeof(ILocalEventHandler<>) + .MakeGenericType(eventType) + .GetMethod( + nameof(ILocalEventHandler.HandleEventAsync), + new[] { eventType } + ); + + await ((Task)method.Invoke(eventHandlerWrapper.EventHandler, new[] { eventData })); + } + else if (ReflectionHelper.IsAssignableToGenericType(handlerType, typeof(IDistributedEventHandler<>))) + { + var method = typeof(IDistributedEventHandler<>) + .MakeGenericType(eventType) + .GetMethod( + nameof(IDistributedEventHandler.HandleEventAsync), + new[] { eventType } + ); + + await ((Task)method.Invoke(eventHandlerWrapper.EventHandler, new[] { eventData })); + } + else + { + throw new AbpException("The object instance is not an event handler. Object type: " + handlerType.AssemblyQualifiedName); + } } } catch (TargetInvocationException ex) @@ -200,6 +207,16 @@ namespace Volo.Abp.EventBus } } + protected virtual Guid? GetEventDataTenantId(object eventData) + { + return eventData switch + { + IMultiTenant multiTenantEventData => multiTenantEventData.TenantId, + IEventDataMayHaveTenantId eventDataMayHaveTenantId when eventDataMayHaveTenantId.IsMultiTenant(out var tenantId) => tenantId, + _ => CurrentTenant.Id + }; + } + protected class EventTypeWithEventHandlerFactories { public Type EventType { get; } @@ -246,4 +263,4 @@ namespace Volo.Abp.EventBus } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/IEventDataMayHaveTenantId.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/IEventDataMayHaveTenantId.cs new file mode 100644 index 0000000000..72a8c753cd --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/IEventDataMayHaveTenantId.cs @@ -0,0 +1,28 @@ +using System; + +namespace Volo.Abp.EventBus +{ + /// + /// An event data object (or event transfer object) can implement this interface + /// to indicate that this event may be related to a tenant. + /// + /// If an event data class is always related to a tenant, then directly implement the + /// interface instead of this one. + /// + /// This interface is typically implemented by generic event handlers where the generic + /// parameter may implement or not. + /// + public interface IEventDataMayHaveTenantId + { + /// + /// Returns true if this event data has a Tenant Id information. + /// If so, it should set the our parameter. + /// Otherwise, the our parameter value should not be informative + /// (it will be null as expected, but doesn't indicate a tenant with null tenant id). + /// + /// + /// The tenant id that is set if this method returns true. + /// + bool IsMultiTenant(out Guid? tenantId); + } +} diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs index 7db5c18f01..8c7bef6f2d 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; namespace Volo.Abp.EventBus.Local @@ -29,8 +30,9 @@ namespace Volo.Abp.EventBus.Local public LocalEventBus( IOptions options, - IServiceScopeFactory serviceScopeFactory) - : base(serviceScopeFactory) + IServiceScopeFactory serviceScopeFactory, + ICurrentTenant currentTenant) + : base(serviceScopeFactory, currentTenant) { Options = options.Value; Logger = NullLogger.Instance; @@ -166,4 +168,4 @@ namespace Volo.Abp.EventBus.Local return false; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Http.Client.IdentityModel.Web/Properties/launchSettings.json b/framework/src/Volo.Abp.Http.Client.IdentityModel.Web/Properties/launchSettings.json new file mode 100644 index 0000000000..c5f82293b3 --- /dev/null +++ b/framework/src/Volo.Abp.Http.Client.IdentityModel.Web/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:53610/", + "sslPort": 44304 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Volo.Abp.Http.Client.IdentityModel.Web": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Http.Client/Volo.Abp.Http.Client.csproj b/framework/src/Volo.Abp.Http.Client/Volo.Abp.Http.Client.csproj index 4441e0bc17..75dbce3b03 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo.Abp.Http.Client.csproj +++ b/framework/src/Volo.Abp.Http.Client/Volo.Abp.Http.Client.csproj @@ -15,7 +15,7 @@ - + diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs index 533b76aa6c..af9eb59b7f 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs @@ -17,7 +17,6 @@ using Volo.Abp.Http.Modeling; using Volo.Abp.Http.ProxyScripting.Generators; using Volo.Abp.Json; using Volo.Abp.MultiTenancy; -using Volo.Abp.Reflection; using Volo.Abp.Threading; using Volo.Abp.Tracing; diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs index 162da88f16..e4b82b21e1 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -100,7 +101,20 @@ namespace Volo.Abp.Http.Client.DynamicProxying { urlBuilder.Append(isFirstParam ? "?" : "&"); - urlBuilder.Append(name + "=" + System.Net.WebUtility.UrlEncode(ConvertValueToString(value))); + if (value.GetType().IsArray || (value.GetType().IsGenericType && value is IEnumerable)) + { + var index = 0; + foreach (var item in (IEnumerable) value) + { + urlBuilder.Append(name + $"[{index++}]=" + System.Net.WebUtility.UrlEncode(ConvertValueToString(item)) + "&"); + } + //remove & at the end of the urlBuilder. + urlBuilder.Remove(urlBuilder.Length - 1, 1); + } + else + { + urlBuilder.Append(name + "=" + System.Net.WebUtility.UrlEncode(ConvertValueToString(value))); + } } private static string ConvertValueToString([NotNull] object value) diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApiTypeNameHelper.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApiTypeNameHelper.cs new file mode 100644 index 0000000000..46eb9e903d --- /dev/null +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApiTypeNameHelper.cs @@ -0,0 +1,38 @@ +using System; +using Volo.Abp.Reflection; + +namespace Volo.Abp.Http.Modeling +{ + public static class ApiTypeNameHelper + { + public static string GetTypeName(Type type) + { + if (TypeHelper.IsDictionary(type, out var keyType, out var valueType)) + { + return $"{{{GetTypeName(keyType)}:{GetTypeName(valueType)}}}"; + } + + if (TypeHelper.IsEnumerable(type, out var itemType, includePrimitives: false)) + { + return $"[{GetTypeName(itemType)}]"; + } + + return TypeHelper.GetFullNameHandlingNullableAndGenerics(type); + } + + public static string GetSimpleTypeName(Type type) + { + if (TypeHelper.IsDictionary(type, out var keyType, out var valueType)) + { + return $"{{{GetSimpleTypeName(keyType)}:{GetSimpleTypeName(valueType)}}}"; + } + + if (TypeHelper.IsEnumerable(type, out var itemType, includePrimitives: false)) + { + return $"[{GetSimpleTypeName(itemType)}]"; + } + + return TypeHelper.GetSimplifiedName(type); + } + } +} diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs index 211359d789..b4e272c2f3 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs @@ -30,11 +30,11 @@ namespace Volo.Abp.Http.Modeling { Name = parameterInfo.Name, TypeAsString = parameterInfo.ParameterType.GetFullNameWithAssemblyName(), - Type = parameterInfo.ParameterType != null ? TypeHelper.GetFullNameHandlingNullableAndGenerics(parameterInfo.ParameterType) : null, - TypeSimple = parameterInfo.ParameterType != null ? TypeHelper.GetSimplifiedName(parameterInfo.ParameterType) : null, + Type = TypeHelper.GetFullNameHandlingNullableAndGenerics(parameterInfo.ParameterType), + TypeSimple = ApiTypeNameHelper.GetSimpleTypeName(parameterInfo.ParameterType), IsOptional = parameterInfo.IsOptional, DefaultValue = parameterInfo.HasDefaultValue ? parameterInfo.DefaultValue : null }; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs index 0450861a41..675ba8bf54 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs @@ -26,7 +26,7 @@ namespace Volo.Abp.Http.Modeling private ParameterApiDescriptionModel() { - + } public static ParameterApiDescriptionModel Create(string name, string nameOnMethod, Type type, bool isOptional = false, object defaultValue = null, string[] constraintTypes = null, string bindingSourceId = null, string descriptorName = null) @@ -36,7 +36,7 @@ namespace Volo.Abp.Http.Modeling Name = name, NameOnMethod = nameOnMethod, Type = type != null ? TypeHelper.GetFullNameHandlingNullableAndGenerics(type) : null, - TypeSimple = type != null ? TypeHelper.GetSimplifiedName(type) : null, + TypeSimple = type != null ? ApiTypeNameHelper.GetSimpleTypeName(type) : null, IsOptional = isOptional, DefaultValue = defaultValue, ConstraintTypes = constraintTypes, @@ -45,4 +45,4 @@ namespace Volo.Abp.Http.Modeling }; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs index 820d9a46f9..610b6bd347 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs @@ -1,6 +1,5 @@ using System; using System.Reflection; -using Volo.Abp.Reflection; namespace Volo.Abp.Http.Modeling { @@ -16,31 +15,12 @@ namespace Volo.Abp.Http.Modeling //TODO: Validation rules for this property public static PropertyApiDescriptionModel Create(PropertyInfo propertyInfo) { - string typeName; - string simpleTypeName; - - if (TypeHelper.IsEnumerable(propertyInfo.PropertyType, out var itemType, includePrimitives: false)) - { - typeName = $"[{TypeHelper.GetFullNameHandlingNullableAndGenerics(itemType)}]"; - simpleTypeName = $"[{TypeHelper.GetSimplifiedName(itemType)}]"; - } - else if (TypeHelper.IsDictionary(propertyInfo.PropertyType, out var keyType, out var valueType)) - { - typeName = $"{{{TypeHelper.GetFullNameHandlingNullableAndGenerics(keyType)}:{TypeHelper.GetFullNameHandlingNullableAndGenerics(valueType)}}}"; - simpleTypeName = $"{{{TypeHelper.GetSimplifiedName(keyType)}:{TypeHelper.GetSimplifiedName(valueType)}}}"; - } - else - { - typeName = TypeHelper.GetFullNameHandlingNullableAndGenerics(propertyInfo.PropertyType); - simpleTypeName = TypeHelper.GetSimplifiedName(propertyInfo.PropertyType); - } - return new PropertyApiDescriptionModel { Name = propertyInfo.Name, - Type = typeName, - TypeSimple = simpleTypeName + Type = ApiTypeNameHelper.GetTypeName(propertyInfo.PropertyType), + TypeSimple = ApiTypeNameHelper.GetSimpleTypeName(propertyInfo.PropertyType) }; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs index 2e0862ccce..83fa4bb6be 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs @@ -23,8 +23,8 @@ namespace Volo.Abp.Http.Modeling return new ReturnValueApiDescriptionModel { Type = TypeHelper.GetFullNameHandlingNullableAndGenerics(unwrappedType), - TypeSimple = TypeHelper.GetSimplifiedName(unwrappedType) + TypeSimple = ApiTypeNameHelper.GetSimpleTypeName(unwrappedType) }; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs index 161584279b..4b0b0b870c 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs @@ -15,11 +15,13 @@ namespace Volo.Abp.Http.Modeling public object[] EnumValues { get; set; } + public string[] GenericArguments { get; set; } + public PropertyApiDescriptionModel[] Properties { get; set; } private TypeApiDescriptionModel() { - + } public static TypeApiDescriptionModel Create(Type type) @@ -45,11 +47,17 @@ namespace Volo.Abp.Http.Modeling { typeModel.Properties = type .GetProperties() + .Where(p => p.DeclaringType == type) .Select(PropertyApiDescriptionModel.Create) .ToArray(); + + if (type.IsGenericTypeDefinition) + { + typeModel.GenericArguments = type.GetGenericArguments().Select(a => a.Name).ToArray(); + } } return typeModel; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj b/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj index 0143db8265..b96da9be3e 100644 --- a/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj +++ b/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj @@ -16,7 +16,8 @@ - + + diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/AbpIdentityModelModule.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/AbpIdentityModelModule.cs index 7ab97c49fe..bf499e7b5f 100644 --- a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/AbpIdentityModelModule.cs +++ b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/AbpIdentityModelModule.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Caching; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; @@ -7,7 +8,8 @@ namespace Volo.Abp.IdentityModel { [DependsOn( typeof(AbpThreadingModule), - typeof(AbpMultiTenancyModule) + typeof(AbpMultiTenancyModule), + typeof(AbpCachingModule) )] public class AbpIdentityModelModule : AbpModule { diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityClientConfiguration.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityClientConfiguration.cs index 17040ff1db..5f2f4af573 100644 --- a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityClientConfiguration.cs +++ b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityClientConfiguration.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using IdentityModel; namespace Volo.Abp.IdentityModel @@ -81,21 +82,32 @@ namespace Volo.Abp.IdentityModel get => this.GetOrDefault(nameof(RequireHttps))?.To() ?? true; set => this[nameof(RequireHttps)] = value.ToString().ToLowerInvariant(); } - + + /// + /// Absolute expiration duration (as seconds) for the access token cache. + /// Default: 1800 seconds (30 minutes) + /// + public int CacheAbsoluteExpiration + { + get => this.GetOrDefault(nameof(CacheAbsoluteExpiration ))?.To() ?? 60 * 30; + set => this[nameof(CacheAbsoluteExpiration)] = value.ToString(CultureInfo.InvariantCulture); + } + public IdentityClientConfiguration() { - + } public IdentityClientConfiguration( string authority, string scope, - string clientId, - string clientSecret, + string clientId, + string clientSecret, string grantType = OidcConstants.GrantTypes.ClientCredentials, string userName = null, string userPassword = null, - bool requireHttps = true) + bool requireHttps = true, + int cacheAbsoluteExpiration = 60 * 30) { this[nameof(Authority)] = authority; this[nameof(Scope)] = scope; @@ -105,6 +117,7 @@ namespace Volo.Abp.IdentityModel this[nameof(UserName)] = userName; this[nameof(UserPassword)] = userPassword; this[nameof(RequireHttps)] = requireHttps.ToString().ToLowerInvariant(); + this[nameof(CacheAbsoluteExpiration)] = cacheAbsoluteExpiration.ToString(CultureInfo.InvariantCulture); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs index 5e8c67a9ab..0fb4c32648 100644 --- a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs +++ b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs @@ -10,6 +10,8 @@ using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Distributed; +using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; @@ -26,18 +28,24 @@ namespace Volo.Abp.IdentityModel protected IHttpClientFactory HttpClientFactory { get; } protected ICurrentTenant CurrentTenant { get; } protected IdentityModelHttpRequestMessageOptions IdentityModelHttpRequestMessageOptions { get; } + protected IDistributedCache TokenCache { get; } + protected IDistributedCache DiscoveryDocumentCache { get; } public IdentityModelAuthenticationService( IOptions options, ICancellationTokenProvider cancellationTokenProvider, IHttpClientFactory httpClientFactory, ICurrentTenant currentTenant, - IOptions identityModelHttpRequestMessageOptions) + IOptions identityModelHttpRequestMessageOptions, + IDistributedCache tokenCache, + IDistributedCache discoveryDocumentCache) { ClientOptions = options.Value; CancellationTokenProvider = cancellationTokenProvider; HttpClientFactory = httpClientFactory; CurrentTenant = currentTenant; + TokenCache = tokenCache; + DiscoveryDocumentCache = discoveryDocumentCache; IdentityModelHttpRequestMessageOptions = identityModelHttpRequestMessageOptions.Value; Logger = NullLogger.Instance; } @@ -70,27 +78,34 @@ namespace Volo.Abp.IdentityModel public virtual async Task GetAccessTokenAsync(IdentityClientConfiguration configuration) { - var discoveryResponse = await GetDiscoveryResponse(configuration); - if (discoveryResponse.IsError) + var cacheKey = CalculateTokenCacheKey(configuration); + var tokenCacheItem = await TokenCache.GetAsync(cacheKey); + if (tokenCacheItem == null) { - throw new AbpException($"Could not retrieve the OpenId Connect discovery document! ErrorType: {discoveryResponse.ErrorType}. Error: {discoveryResponse.Error}"); - } - - var tokenResponse = await GetTokenResponse(discoveryResponse, configuration); + var tokenResponse = await GetTokenResponse(configuration); - if (tokenResponse.IsError) - { - if (tokenResponse.ErrorDescription != null) + if (tokenResponse.IsError) { - throw new AbpException($"Could not get token from the OpenId Connect server! ErrorType: {tokenResponse.ErrorType}. Error: {tokenResponse.Error}. ErrorDescription: {tokenResponse.ErrorDescription}. HttpStatusCode: {tokenResponse.HttpStatusCode}"); + if (tokenResponse.ErrorDescription != null) + { + throw new AbpException($"Could not get token from the OpenId Connect server! ErrorType: {tokenResponse.ErrorType}. " + + $"Error: {tokenResponse.Error}. ErrorDescription: {tokenResponse.ErrorDescription}. HttpStatusCode: {tokenResponse.HttpStatusCode}"); + } + + var rawError = tokenResponse.Raw; + var withoutInnerException = rawError.Split(new string[] { "" }, StringSplitOptions.RemoveEmptyEntries); + throw new AbpException(withoutInnerException[0]); } - var rawError = tokenResponse.Raw; - var withoutInnerException = rawError.Split(new string[] { "" }, StringSplitOptions.RemoveEmptyEntries); - throw new AbpException(withoutInnerException[0]); + tokenCacheItem = new IdentityModelTokenCacheItem(tokenResponse.AccessToken); + await TokenCache.SetAsync(cacheKey, tokenCacheItem, + new DistributedCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(configuration.CacheAbsoluteExpiration) + }); } - return tokenResponse.AccessToken; + return tokenCacheItem.AccessToken; } protected virtual void SetAccessToken(HttpClient client, string accessToken) @@ -110,8 +125,33 @@ namespace Volo.Abp.IdentityModel ClientOptions.IdentityClients.Default; } - protected virtual async Task GetDiscoveryResponse( - IdentityClientConfiguration configuration) + protected virtual async Task GetTokenEndpoint(IdentityClientConfiguration configuration) + { + //TODO: Can use (configuration.Authority + /connect/token) directly? + + var tokenEndpointUrlCacheKey = CalculateDiscoveryDocumentCacheKey(configuration); + var discoveryDocumentCacheItem = await DiscoveryDocumentCache.GetAsync(tokenEndpointUrlCacheKey); + if (discoveryDocumentCacheItem == null) + { + var discoveryResponse = await GetDiscoveryResponse(configuration); + if (discoveryResponse.IsError) + { + throw new AbpException($"Could not retrieve the OpenId Connect discovery document! " + + $"ErrorType: {discoveryResponse.ErrorType}. Error: {discoveryResponse.Error}"); + } + + discoveryDocumentCacheItem = new IdentityModelDiscoveryDocumentCacheItem(discoveryResponse.TokenEndpoint); + await DiscoveryDocumentCache.SetAsync(tokenEndpointUrlCacheKey, discoveryDocumentCacheItem, + new DistributedCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(configuration.CacheAbsoluteExpiration) + }); + } + + return discoveryDocumentCacheItem.TokenEndpoint; + } + + protected virtual async Task GetDiscoveryResponse(IdentityClientConfiguration configuration) { using (var httpClient = HttpClientFactory.CreateClient(HttpClientName)) { @@ -128,10 +168,10 @@ namespace Volo.Abp.IdentityModel } } - protected virtual async Task GetTokenResponse( - DiscoveryDocumentResponse discoveryResponse, - IdentityClientConfiguration configuration) + protected virtual async Task GetTokenResponse(IdentityClientConfiguration configuration) { + var tokenEndpoint = await GetTokenEndpoint(configuration); + using (var httpClient = HttpClientFactory.CreateClient(HttpClientName)) { AddHeaders(httpClient); @@ -140,12 +180,12 @@ namespace Volo.Abp.IdentityModel { case OidcConstants.GrantTypes.ClientCredentials: return await httpClient.RequestClientCredentialsTokenAsync( - await CreateClientCredentialsTokenRequestAsync(discoveryResponse, configuration), + await CreateClientCredentialsTokenRequestAsync(tokenEndpoint, configuration), CancellationTokenProvider.Token ); case OidcConstants.GrantTypes.Password: return await httpClient.RequestPasswordTokenAsync( - await CreatePasswordTokenRequestAsync(discoveryResponse, configuration), + await CreatePasswordTokenRequestAsync(tokenEndpoint, configuration), CancellationTokenProvider.Token ); default: @@ -154,11 +194,11 @@ namespace Volo.Abp.IdentityModel } } - protected virtual Task CreatePasswordTokenRequestAsync(DiscoveryDocumentResponse discoveryResponse, IdentityClientConfiguration configuration) + protected virtual Task CreatePasswordTokenRequestAsync(string tokenEndpoint, IdentityClientConfiguration configuration) { var request = new PasswordTokenRequest { - Address = discoveryResponse.TokenEndpoint, + Address = tokenEndpoint, Scope = configuration.Scope, ClientId = configuration.ClientId, ClientSecret = configuration.ClientSecret, @@ -172,13 +212,11 @@ namespace Volo.Abp.IdentityModel return Task.FromResult(request); } - protected virtual Task CreateClientCredentialsTokenRequestAsync( - DiscoveryDocumentResponse discoveryResponse, - IdentityClientConfiguration configuration) + protected virtual Task CreateClientCredentialsTokenRequestAsync(string tokenEndpoint, IdentityClientConfiguration configuration) { var request = new ClientCredentialsTokenRequest { - Address = discoveryResponse.TokenEndpoint, + Address = tokenEndpoint, Scope = configuration.Scope, ClientId = configuration.ClientId, ClientSecret = configuration.ClientSecret @@ -209,5 +247,15 @@ namespace Volo.Abp.IdentityModel client.DefaultRequestHeaders.Add(TenantResolverConsts.DefaultTenantKey, CurrentTenant.Id.Value.ToString()); } } + + protected virtual string CalculateDiscoveryDocumentCacheKey(IdentityClientConfiguration configuration) + { + return IdentityModelDiscoveryDocumentCacheItem.CalculateCacheKey(configuration); + } + + protected virtual string CalculateTokenCacheKey(IdentityClientConfiguration configuration) + { + return IdentityModelTokenCacheItem.CalculateCacheKey(configuration); + } } } diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelDiscoveryDocumentCacheItem.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelDiscoveryDocumentCacheItem.cs new file mode 100644 index 0000000000..3a07cb3735 --- /dev/null +++ b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelDiscoveryDocumentCacheItem.cs @@ -0,0 +1,27 @@ +using System; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.IdentityModel +{ + [Serializable] + [IgnoreMultiTenancy] + public class IdentityModelDiscoveryDocumentCacheItem + { + public string TokenEndpoint { get; set; } + + public IdentityModelDiscoveryDocumentCacheItem() + { + + } + + public IdentityModelDiscoveryDocumentCacheItem(string tokenEndpoint) + { + TokenEndpoint = tokenEndpoint; + } + + public static string CalculateCacheKey(IdentityClientConfiguration configuration) + { + return configuration.Authority.ToLower().ToMd5(); + } + } +} diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelTokenCacheItem.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelTokenCacheItem.cs new file mode 100644 index 0000000000..e8f8dfb8db --- /dev/null +++ b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelTokenCacheItem.cs @@ -0,0 +1,28 @@ +using System; +using System.Linq; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.IdentityModel +{ + [Serializable] + [IgnoreMultiTenancy] + public class IdentityModelTokenCacheItem + { + public string AccessToken { get; set; } + + public IdentityModelTokenCacheItem() + { + + } + + public IdentityModelTokenCacheItem(string accessToken) + { + AccessToken = accessToken; + } + + public static string CalculateCacheKey(IdentityClientConfiguration configuration) + { + return string.Join(",", configuration.Select(x => x.Key + ":" + x.Value)).ToMd5(); + } + } +} diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpJsonModule.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpJsonModule.cs index dcb8f28dff..c3ea13da43 100644 --- a/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpJsonModule.cs +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/AbpJsonModule.cs @@ -1,4 +1,5 @@ -using Volo.Abp.Modularity; +using Volo.Abp.Json.Newtonsoft; +using Volo.Abp.Modularity; using Volo.Abp.Timing; namespace Volo.Abp.Json @@ -6,6 +7,12 @@ namespace Volo.Abp.Json [DependsOn(typeof(AbpTimingModule))] public class AbpJsonModule : AbpModule { - + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.Converters.Add(); + }); + } } } diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/Newtonsoft/AbpNewtonsoftJsonSerializerOptions.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/Newtonsoft/AbpNewtonsoftJsonSerializerOptions.cs new file mode 100644 index 0000000000..cb1c3d9f65 --- /dev/null +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/Newtonsoft/AbpNewtonsoftJsonSerializerOptions.cs @@ -0,0 +1,15 @@ +using Newtonsoft.Json; +using Volo.Abp.Collections; + +namespace Volo.Abp.Json.Newtonsoft +{ + public class AbpNewtonsoftJsonSerializerOptions + { + public ITypeList Converters { get; } + + public AbpNewtonsoftJsonSerializerOptions() + { + Converters = new TypeList(); + } + } +} diff --git a/framework/src/Volo.Abp.Json/Volo/Abp/Json/Newtonsoft/NewtonsoftJsonSerializer.cs b/framework/src/Volo.Abp.Json/Volo/Abp/Json/Newtonsoft/NewtonsoftJsonSerializer.cs index 99252b8448..20e49ac817 100644 --- a/framework/src/Volo.Abp.Json/Volo/Abp/Json/Newtonsoft/NewtonsoftJsonSerializer.cs +++ b/framework/src/Volo.Abp.Json/Volo/Abp/Json/Newtonsoft/NewtonsoftJsonSerializer.cs @@ -1,4 +1,8 @@ using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Volo.Abp.DependencyInjection; @@ -7,14 +11,19 @@ namespace Volo.Abp.Json.Newtonsoft { public class NewtonsoftJsonSerializer : IJsonSerializer, ITransientDependency { - private readonly AbpJsonIsoDateTimeConverter _dateTimeConverter; - private static readonly CamelCaseExceptDictionaryKeysResolver SharedCamelCaseExceptDictionaryKeysResolver = new CamelCaseExceptDictionaryKeysResolver(); - public NewtonsoftJsonSerializer(AbpJsonIsoDateTimeConverter dateTimeConverter) + protected List Converters { get; } + + public NewtonsoftJsonSerializer( + IOptions options, + IServiceProvider serviceProvider) { - _dateTimeConverter = dateTimeConverter; + Converters = options.Value + .Converters + .Select(c => (JsonConverter) serviceProvider.GetRequiredService(c)) + .ToList(); } public string Serialize(object obj, bool camelCase = true, bool indented = false) @@ -36,8 +45,8 @@ namespace Volo.Abp.Json.Newtonsoft { var settings = new JsonSerializerSettings(); - settings.Converters.Insert(0, _dateTimeConverter); - + settings.Converters.InsertRange(0, Converters); + if (camelCase) { settings.ContractResolver = SharedCamelCaseExceptDictionaryKeysResolver; @@ -47,7 +56,7 @@ namespace Volo.Abp.Json.Newtonsoft { settings.Formatting = Formatting.Indented; } - + return settings; } @@ -63,4 +72,4 @@ namespace Volo.Abp.Json.Newtonsoft } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Ldap/Volo.Abp.Ldap.csproj b/framework/src/Volo.Abp.Ldap/Volo.Abp.Ldap.csproj index 229f3c6c7d..849fe3ce3d 100644 --- a/framework/src/Volo.Abp.Ldap/Volo.Abp.Ldap.csproj +++ b/framework/src/Volo.Abp.Ldap/Volo.Abp.Ldap.csproj @@ -15,11 +15,19 @@ - + - + + + + + + - + + + + diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/AbpAbpLdapOptionsFactory.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/AbpAbpLdapOptionsFactory.cs new file mode 100644 index 0000000000..9579e169be --- /dev/null +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/AbpAbpLdapOptionsFactory.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Volo.Abp.Options; +using Volo.Abp.Settings; +using Volo.Abp.Threading; + +namespace Volo.Abp.Ldap +{ + public class AbpAbpLdapOptionsFactory : AbpOptionsFactory + { + protected ISettingProvider SettingProvider { get; } + + public AbpAbpLdapOptionsFactory( + IEnumerable> setups, + IEnumerable> postConfigures, + ISettingProvider settingProvider) + : base(setups, postConfigures) + { + SettingProvider = settingProvider; + } + + public override AbpLdapOptions Create(string name) + { + var options = base.Create(name); + + AsyncHelper.RunSync(() => OverrideOptionsAsync(options)); + + return options; + } + + protected virtual async Task OverrideOptionsAsync(AbpLdapOptions options) + { + options.ServerHost = await GetSettingOrDefaultValue(LdapSettingNames.ServerHost, options.ServerHost); + options.ServerPort = await SettingProvider.GetAsync(LdapSettingNames.ServerPort, options.ServerPort); + options.BaseDc = await GetSettingOrDefaultValue(LdapSettingNames.BaseDc, options.BaseDc); + options.UserName = await GetSettingOrDefaultValue(LdapSettingNames.UserName, options.UserName); + options.Password = await GetSettingOrDefaultValue(LdapSettingNames.Password, options.Password); + } + + protected virtual async Task GetSettingOrDefaultValue(string name, string defaultValue) + { + var value = await SettingProvider.GetOrNullAsync(name); + return value.IsNullOrWhiteSpace() ? defaultValue : value; + } + } +} diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/AbpLdapModule.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/AbpLdapModule.cs index b35e1b1d59..c842d4d76b 100644 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/AbpLdapModule.cs +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/AbpLdapModule.cs @@ -1,18 +1,44 @@ -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Autofac; +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using Volo.Abp.Ldap.Localization; +using Volo.Abp.Localization; using Volo.Abp.Modularity; +using Volo.Abp.Settings; +using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.Ldap { [DependsOn( - typeof(AbpAutofacModule) - )] + typeof(AbpSettingsModule), + typeof(AbpVirtualFileSystemModule), + typeof(AbpLocalizationModule))] public class AbpLdapModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { + context.Services.Replace(ServiceDescriptor.Transient, AbpAbpLdapOptionsFactory>()); + context.Services.Replace(ServiceDescriptor.Scoped, OptionsManager>()); + var configuration = context.Services.GetConfiguration(); - Configure(configuration.GetSection("LDAP")); + var ldapConfiguration = configuration["Ldap"]; + if (!ldapConfiguration.IsNullOrEmpty()) + { + Configure(configuration.GetSection("Ldap")); + } + + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + options.Resources + .Add("en") + .AddVirtualJson("/Volo/Abp/Ldap/Localization"); + }); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/AbpLdapOptions.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/AbpLdapOptions.cs index 671d623ff7..ab9c2d12f2 100644 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/AbpLdapOptions.cs +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/AbpLdapOptions.cs @@ -6,19 +6,16 @@ public int ServerPort { get; set; } - public bool UseSsl { get; set; } + public string BaseDc { get; set; } - public string SearchBase { get; set; } + /// + /// BindDN + /// + public string UserName { get; set; } - public string DomainName { get; set; } - - public string DomainDistinguishedName { get; set; } - - public LdapCredentials Credentials { get; set; } - - public AbpLdapOptions() - { - Credentials = new LdapCredentials(); - } + /// + ///BindPassword + /// + public string Password { get; set; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Exceptions/OrganizationNotExistException.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Exceptions/OrganizationNotExistException.cs deleted file mode 100644 index 9d7fb220cc..0000000000 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Exceptions/OrganizationNotExistException.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Volo.Abp.Ldap.Exceptions -{ - public class OrganizationNotExistException : BusinessException - { - public OrganizationNotExistException(string distinguishedName) - : base("LDAP:000001", $"the organization distinguished named {distinguishedName} does not exist.") - { - - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/ILdapManager.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/ILdapManager.cs index f14e88b409..6ad66b0cb3 100644 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/ILdapManager.cs +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/ILdapManager.cs @@ -1,72 +1,7 @@ -using System.Collections.Generic; -using Volo.Abp.Ldap.Modeling; - -namespace Volo.Abp.Ldap +namespace Volo.Abp.Ldap { public interface ILdapManager { - /// - /// query the specified organizations. - /// - /// filter: (&(name=xxx)(objectClass=organizationalUnit)) when name is not null - /// filter: (&(name=*)(objectClass=organizationalUnit)) when name is null - /// - /// - /// - /// - IList GetOrganizations(string name = null); - - /// - /// query the specified organization. - /// - /// filter: (&(distinguishedName=xxx)(objectClass=organizationalUnit)) when organizationName is not null - /// - /// - /// - /// - LdapOrganization GetOrganization(string distinguishedName); - - void AddSubOrganization(string organizationName, LdapOrganization parentOrganization); - void AddSubOrganization(string organizationName, string parentDistinguishedName); - - /// - /// query the specified users. - /// - /// filter: (&(name=xxx)(objectCategory=person)(objectClass=user)) when name is not null - /// filter: (&(name=*)(objectCategory=person)(objectClass=user)) when name is null - /// - /// filter: (&(displayName=xxx)(objectCategory=person)(objectClass=user)) when displayName is not null - /// filter: (&(displayName=*)(objectCategory=person)(objectClass=user)) when displayName is null - /// - /// filter: (&(cn=xxx)(objectCategory=person)(objectClass=user)) when commonName is not null - /// filter: (&(cn=*)(objectCategory=person)(objectClass=user)) when commonName is null - /// - /// - /// - /// - /// - /// - IList GetUsers(string name = null, string displayName = null, string commonName = null); - - /// - /// query the specified User. - /// - /// filter: (&(distinguishedName=xxx)(objectCategory=person)(objectClass=user)) when distinguishedName is not null - /// - /// - /// - /// - LdapUser GetUser(string distinguishedName); - - void AddUserToOrganization(string userName, string password, LdapOrganization parentOrganization); - void AddUserToOrganization(string userName, string password, string parentDistinguishedName); - - /// - /// Authenticate - /// - /// E.g administrator@yourdomain.com.cn - /// - /// - bool Authenticate(string userDomainName, string password); + bool Authenticate(string username, string password); } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapCredentials.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapCredentials.cs deleted file mode 100644 index a2d2eefd74..0000000000 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapCredentials.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Volo.Abp.Ldap -{ - public class LdapCredentials - { - public string DomainUserName { get; set; } - - public string Password { get; set; } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapHelps.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapHelps.cs deleted file mode 100644 index 49a60a964f..0000000000 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapHelps.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections.Generic; - -namespace Volo.Abp.Ldap -{ - public static class LdapHelps - { - public static string BuildCondition(string name, string value) - { - return string.IsNullOrWhiteSpace(value) ? "" : $"({name}={value})"; - } - - public static string BuildFilter(Dictionary conditions) - { - if (null == conditions ) - { - conditions = new Dictionary(); - } - - if (conditions.Keys.Count == 0) - { - conditions.Add("objectClass", "*"); // add default condition - } - - var subFilter = string.Empty; - foreach (var keyValuePair in conditions) - { - subFilter += BuildCondition(keyValuePair.Key, keyValuePair.Value); - } - - return $"(&{subFilter})"; - } - - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapManager.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapManager.cs index 9e0b8f04a7..2d890c10f7 100644 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapManager.cs +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapManager.cs @@ -1,343 +1,57 @@ using System; using Microsoft.Extensions.Options; using Novell.Directory.Ldap; -using System.Collections.Generic; -using System.Text; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.DependencyInjection; -using Volo.Abp.ExceptionHandling; -using Volo.Abp.Ldap.Exceptions; -using Volo.Abp.Ldap.Modeling; namespace Volo.Abp.Ldap { public class LdapManager : ILdapManager, ITransientDependency { - private readonly string _searchBase; - private readonly AbpLdapOptions _ldapOptions; - private readonly IHybridServiceScopeFactory _hybridServiceScopeFactory; + public ILogger Logger { get; set; } + protected AbpLdapOptions LdapOptions { get; } - private readonly string[] _attributes = + public LdapManager(IOptions ldapSettingsOptions) { - "objectCategory", "objectClass", "cn", "name", "distinguishedName", - "ou", - "sAMAccountName", "userPrincipalName", "telephoneNumber", "mail" - }; + LdapOptions = ldapSettingsOptions.Value; - public LdapManager(IOptions ldapSettingsOptions, IHybridServiceScopeFactory hybridServiceScopeFactory) - { - _hybridServiceScopeFactory = hybridServiceScopeFactory; - _ldapOptions = ldapSettingsOptions.Value; - _searchBase = _ldapOptions.SearchBase; - } - - #region Organization - /// - /// query the specified organizations. - /// - /// filter: (&(name=xxx)(objectClass=organizationalUnit)) when name is not null - /// filter: (&(objectClass=organizationalUnit)) when name is null - /// - /// - /// - /// - public IList GetOrganizations(string name = null) - { - var conditions = new Dictionary - { - {"name", name}, - {"objectClass", "organizationalUnit"}, - }; - return Query(_searchBase, conditions); - } - - /// - /// query the specified organization. - /// - /// filter: (&(distinguishedName=xxx)(objectClass=organizationalUnit)) when organizationName is not null - /// - /// - /// - /// - public LdapOrganization GetOrganization(string distinguishedName) - { - distinguishedName = Check.NotNullOrWhiteSpace(distinguishedName, nameof(distinguishedName)); - var conditions = new Dictionary - { - {"distinguishedName", distinguishedName}, - {"objectClass", "organizationalUnit"}, - }; - return QueryOne(_searchBase, conditions); - } - - public void AddSubOrganization(string organizationName, LdapOrganization parentOrganization) - { - organizationName = Check.NotNullOrWhiteSpace(organizationName, nameof(organizationName)); - var dn = $"OU={organizationName},{parentOrganization.DistinguishedName}"; - - var attributeSet = new LdapAttributeSet - { - new LdapAttribute("objectCategory", $"CN=Organizational-Unit,CN=Schema,CN=Configuration,{_ldapOptions.DomainDistinguishedName}"), - new LdapAttribute("objectClass", new[] {"top", "organizationalUnit"}), - new LdapAttribute("name", organizationName), - }; - - var newEntry = new LdapEntry(dn, attributeSet); - - using (var ldapConnection = GetConnection()) - { - ldapConnection.Add(newEntry); - } - } - - public void AddSubOrganization(string organizationName, string parentDistinguishedName) - { - organizationName = Check.NotNullOrWhiteSpace(organizationName, nameof(organizationName)); - parentDistinguishedName = - Check.NotNullOrWhiteSpace(parentDistinguishedName, nameof(parentDistinguishedName)); - - var parentOrganization = GetOrganization(parentDistinguishedName); - if (null == parentOrganization) - { - throw new OrganizationNotExistException(parentDistinguishedName); - } - - AddSubOrganization(organizationName, parentOrganization); - } - - #endregion - - #region User - /// - /// query the specified users. - /// - /// filter: (&(name=xxx)(objectCategory=person)(objectClass=user)) when name is not null - /// filter: (&(objectCategory=person)(objectClass=user)) when name is null - /// - /// filter: (&(displayName=xxx)(objectCategory=person)(objectClass=user)) when displayName is not null - /// filter: (&(objectCategory=person)(objectClass=user)) when displayName is null - /// - /// filter: (&(cn=xxx)(objectCategory=person)(objectClass=user)) when commonName is not null - /// filter: (&(objectCategory=person)(objectClass=user)) when commonName is null - /// - /// - /// - /// - /// - /// - public IList GetUsers(string name = null, string displayName = null, string commonName = null) - { - var conditions = new Dictionary - { - {"objectCategory", "person"}, - {"objectClass", "user"}, - {"name", name}, - {"displayName", displayName}, - {"cn", commonName}, - }; - return Query(_searchBase, conditions); + Logger = NullLogger.Instance; } - /// - /// query the specified User. - /// - /// filter: (&(distinguishedName=xxx)(objectCategory=person)(objectClass=user)) when distinguishedName is not null - /// - /// - /// - /// - public LdapUser GetUser(string distinguishedName) - { - distinguishedName = Check.NotNullOrWhiteSpace(distinguishedName, nameof(distinguishedName)); - var conditions = new Dictionary - { - {"objectCategory", "person"}, - {"objectClass", "user"}, - {"distinguishedName", distinguishedName}, - }; - return QueryOne(_searchBase, conditions); - } - - public void AddUserToOrganization(string userName, string password, LdapOrganization parentOrganization) - { - var dn = $"CN={userName},{parentOrganization.DistinguishedName}"; - var mail = $"{userName}@{_ldapOptions.DomainName}"; - sbyte[] encodedBytes = SupportClass.ToSByteArray(Encoding.Unicode.GetBytes($"\"{password}\"")); - - var attributeSet = new LdapAttributeSet - { - new LdapAttribute("instanceType", "4"), - new LdapAttribute("objectCategory", $"CN=Person,CN=Schema,CN=Configuration,{_ldapOptions.DomainDistinguishedName}"), - new LdapAttribute("objectClass", new[] {"top", "person", "organizationalPerson", "user"}), - new LdapAttribute("name", userName), - new LdapAttribute("cn", userName), - new LdapAttribute("sAMAccountName", userName), - new LdapAttribute("userPrincipalName", userName), - new LdapAttribute("sn", userName), - new LdapAttribute("displayName", userName), - new LdapAttribute("unicodePwd", encodedBytes), - new LdapAttribute("userAccountControl", "512"), - new LdapAttribute("mail", mail), - }; - var newEntry = new LdapEntry(dn, attributeSet); - - using (var ldapConnection = GetConnection()) - { - ldapConnection.Add(newEntry); - } - } - - public void AddUserToOrganization(string userName, string password, string parentDistinguishedName) - { - var dn = $"CN={userName},{parentDistinguishedName}"; - var mail = $"{userName}@{_ldapOptions.DomainName}"; - sbyte[] encodedBytes = SupportClass.ToSByteArray(Encoding.Unicode.GetBytes($"\"{password}\"")); - - var attributeSet = new LdapAttributeSet - { - new LdapAttribute("instanceType", "4"), - new LdapAttribute("objectCategory", $"CN=Person,CN=Schema,CN=Configuration,{_ldapOptions.DomainDistinguishedName}"), - new LdapAttribute("objectClass", new[] {"top", "person", "organizationalPerson", "user"}), - new LdapAttribute("name", userName), - new LdapAttribute("cn", userName), - new LdapAttribute("sAMAccountName", userName), - new LdapAttribute("userPrincipalName", userName), - new LdapAttribute("sn", userName), - new LdapAttribute("displayName", userName), - new LdapAttribute("unicodePwd", encodedBytes), - new LdapAttribute("userAccountControl", "512"), - new LdapAttribute("mail", mail), - }; - var newEntry = new LdapEntry(dn, attributeSet); - - using (var ldapConnection = GetConnection()) - { - ldapConnection.Add(newEntry); - } - } - - #endregion - - #region Authenticate - - /// - /// Authenticate - /// - /// E.g administrator@yourdomain.com.cn - /// - /// - public bool Authenticate(string userDomainName, string password) + public bool Authenticate(string username, string password) { try { - using (GetConnection(userDomainName, password)) + using (var conn = CreateLdapConnection()) { + AuthenticateLdapConnection(conn, username, password); return true; } } catch (Exception ex) { - using (var scope = _hybridServiceScopeFactory.CreateScope()) - { - scope.ServiceProvider - .GetRequiredService() - .NotifyAsync(ex); - } - + Logger.LogException(ex); return false; } } - #endregion - - private ILdapConnection GetConnection(string bindUserName = null, string bindUserPassword = null) + protected virtual ILdapConnection CreateLdapConnection() { - // bindUserName/bindUserPassword only be used when authenticate - bindUserName = bindUserName ?? _ldapOptions.Credentials.DomainUserName; - bindUserPassword = bindUserPassword ?? _ldapOptions.Credentials.Password; - - var ldapConnection = new LdapConnection() { SecureSocketLayer = _ldapOptions.UseSsl }; - if (_ldapOptions.UseSsl) - { - ldapConnection.UserDefinedServerCertValidationDelegate += (sender, certificate, chain, sslPolicyErrors) => true; - } - ldapConnection.Connect(_ldapOptions.ServerHost, _ldapOptions.ServerPort); - - if (_ldapOptions.UseSsl) - { - ldapConnection.Bind(LdapConnection.Ldap_V3, bindUserName, bindUserPassword); - } - else - { - ldapConnection.Bind(bindUserName, bindUserPassword); - } + var ldapConnection = new LdapConnection(); + ConfigureLdapConnection(ldapConnection); + ldapConnection.Connect(LdapOptions.ServerHost, LdapOptions.ServerPort); return ldapConnection; } - private IList Query(string searchBase, Dictionary conditions) where T : class, ILdapEntry + protected virtual void ConfigureLdapConnection(ILdapConnection connection) { - var filter = LdapHelps.BuildFilter(conditions); - - var result = new List(); - using (var ldapConnection = GetConnection()) - { - var search = ldapConnection.Search(searchBase, LdapConnection.SCOPE_SUB, filter, - _attributes, false, null, null); - - LdapMessage message; - while ((message = search.getResponse()) != null) - { - if (!(message is LdapSearchResult searchResultMessage)) - { - continue; - } - var entry = searchResultMessage.Entry; - if (typeof(T) == typeof(LdapOrganization)) - { - result.Add(new LdapOrganization(entry.getAttributeSet()) as T); - } - - if (typeof(T) == typeof(LdapUser)) - { - result.Add(new LdapUser(entry.getAttributeSet()) as T); - } - } - } - return result; } - private T QueryOne(string searchBase, Dictionary conditions) where T : class, ILdapEntry + protected virtual void AuthenticateLdapConnection(ILdapConnection connection, string username, string password) { - var filter = LdapHelps.BuildFilter(conditions); - - using (var ldapConnection = GetConnection()) - { - var search = ldapConnection.Search(searchBase, LdapConnection.SCOPE_SUB, filter, - _attributes, false, null, null); - - LdapMessage message; - while ((message = search.getResponse()) != null) - { - if (!(message is LdapSearchResult searchResultMessage)) - { - continue; - } - var entry = searchResultMessage.Entry; - if (typeof(T) == typeof(LdapOrganization)) - { - return new LdapOrganization(entry.getAttributeSet()) as T; - } - - if (typeof(T) == typeof(LdapUser)) - { - return new LdapUser(entry.getAttributeSet()) as T; - } - return null; - } - } - return null; + connection.Bind(username, password); } - } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapSettingNames.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapSettingNames.cs new file mode 100644 index 0000000000..14b2546bbf --- /dev/null +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapSettingNames.cs @@ -0,0 +1,15 @@ +namespace Volo.Abp.Ldap +{ + public static class LdapSettingNames + { + public const string ServerHost = "Abp.Ldap.ServerHost"; + + public const string ServerPort = "Abp.Ldap.ServerPort"; + + public const string BaseDc = "Abp.Ldap.BaseDc"; + + public const string UserName = "Abp.Ldap.UserName"; + + public const string Password = "Abp.Ldap.Password"; + } +} diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapSettingProvider.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapSettingProvider.cs new file mode 100644 index 0000000000..edcfc2e4ef --- /dev/null +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapSettingProvider.cs @@ -0,0 +1,50 @@ +using Volo.Abp.Ldap.Localization; +using Volo.Abp.Localization; +using Volo.Abp.Settings; + +namespace Volo.Abp.Ldap +{ + public class LdapSettingProvider : SettingDefinitionProvider + { + public override void Define(ISettingDefinitionContext context) + { + context.Add( + new SettingDefinition( + LdapSettingNames.ServerHost, + "", + L("DisplayName:Abp.Ldap.ServerHost"), + L("Description:Abp.Ldap.ServerHost")), + + new SettingDefinition( + LdapSettingNames.ServerPort, + "389", + L("DisplayName:Abp.Ldap.ServerPort"), + L("Description:Abp.Ldap.ServerPort")), + + new SettingDefinition( + LdapSettingNames.BaseDc, + "", + L("DisplayName:Abp.Ldap.BaseDc"), + L("Description:Abp.Ldap.BaseDc")), + + new SettingDefinition( + LdapSettingNames.UserName, + "", + L("DisplayName:Abp.Ldap.UserName"), + L("Description:Abp.Ldap.UserName")), + + new SettingDefinition( + LdapSettingNames.Password, + "", + L("DisplayName:Abp.Ldap.Password"), + L("Description:Abp.Ldap.Password"), + isEncrypted: true) + ); + } + + private static LocalizableString L(string name) + { + return LocalizableString.Create(name); + } + } +} diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/LdapResource.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/LdapResource.cs new file mode 100644 index 0000000000..e45b6797b5 --- /dev/null +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/LdapResource.cs @@ -0,0 +1,10 @@ +using Volo.Abp.Localization; + +namespace Volo.Abp.Ldap.Localization +{ + [LocalizationResourceName("AbpLdap")] + public class LdapResource + { + + } +} diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/en.json b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/en.json new file mode 100644 index 0000000000..1d720fc26f --- /dev/null +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/en.json @@ -0,0 +1,19 @@ +{ + "culture": "en", + "texts": { + "DisplayName:Abp.Ldap.ServerHost": "Server host", + "Description:Abp.Ldap.ServerHost": "Server host", + + "DisplayName:Abp.Ldap.ServerPort": "Server port", + "Description:Abp.Ldap.ServerPort": "Server port", + + "DisplayName:Abp.Ldap.BaseDc": "Base domain component", + "Description:Abp.Ldap.BaseDc": "Base domain component", + + "DisplayName:Abp.Ldap.UserName": "Username", + "Description:Abp.Ldap.UserName": "Username", + + "DisplayName:Abp.Ldap.Password": "Password", + "Description:Abp.Ldap.Password": "Password" + } +} diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/tr.json b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/tr.json new file mode 100644 index 0000000000..e6c90ea87c --- /dev/null +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/tr.json @@ -0,0 +1,19 @@ +{ + "culture": "tr", + "texts": { + "DisplayName:Abp.Ldap.ServerHost": "Sunucu Ana Bilgisayarı", + "Description:Abp.Ldap.ServerHost": "Sunucu Ana Bilgisayarı", + + "DisplayName:Abp.Ldap.ServerPort": "Sunucu portu", + "Description:Abp.Ldap.ServerPort": "Sunucu portu", + + "DisplayName:Abp.Ldap.BaseDc": "Temel alan bileşeni", + "Description:Abp.Ldap.BaseDc": "Temel alan bileşeni", + + "DisplayName:Abp.Ldap.UserName": "Kullanıcı adı", + "Description:Abp.Ldap.UserName": "Kullanıcı adı", + + "DisplayName:Abp.Ldap.Password": "Parola", + "Description:Abp.Ldap.Password": "Parola" + } +} diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/zh-Hans.json b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/zh-Hans.json new file mode 100644 index 0000000000..7efddf2682 --- /dev/null +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/zh-Hans.json @@ -0,0 +1,19 @@ +{ + "culture": "zh-Hans", + "texts": { + "DisplayName:Abp.Ldap.ServerHost": "服务器主机", + "Description:Abp.Ldap.ServerHost": "服务器主机", + + "DisplayName:Abp.Ldap.ServerPort": "服务器端口", + "Description:Abp.Ldap.ServerPort": "服务器端口", + + "DisplayName:Abp.Ldap.BaseDc": "基域组件", + "Description:Abp.Ldap.BaseDc": "基域组件", + + "DisplayName:Abp.Ldap.UserName": "用户名", + "Description:Abp.Ldap.UserName": "用户名", + + "DisplayName:Abp.Ldap.Password": "密码", + "Description:Abp.Ldap.Password": "密码" + } +} diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/zh-Hant.json b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/zh-Hant.json new file mode 100644 index 0000000000..d8e30ea362 --- /dev/null +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/zh-Hant.json @@ -0,0 +1,19 @@ +{ + "culture": "zh-Hant", + "texts": { + "DisplayName:Abp.Ldap.ServerHost": "服務器主機", + "Description:Abp.Ldap.ServerHost": "服務器主機", + + "DisplayName:Abp.Ldap.ServerPort": "服務器端口", + "Description:Abp.Ldap.ServerPort": "服務器端口", + + "DisplayName:Abp.Ldap.BaseDc": "基域組件", + "Description:Abp.Ldap.BaseDc": "基域組件", + + "DisplayName:Abp.Ldap.UserName": "用戶名", + "Description:Abp.Ldap.UserName": "用戶名", + + "DisplayName:Abp.Ldap.Password": "密碼", + "Description:Abp.Ldap.Password": "密碼" + } +} diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/ILdapEntry.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/ILdapEntry.cs deleted file mode 100644 index f15775fc06..0000000000 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/ILdapEntry.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Volo.Abp.Ldap.Modeling -{ - public interface ILdapEntry - { - string ObjectCategory { get; set; } - string[] ObjectClass { get; set; } - string Name { get; set; } - string DistinguishedName { get; set; } - string CommonName { get; set; } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/ILdapOrganization.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/ILdapOrganization.cs deleted file mode 100644 index 07c11000e8..0000000000 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/ILdapOrganization.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Volo.Abp.Ldap.Modeling -{ - public interface ILdapOrganization : ILdapEntry - { - string OrganizationUnit { get; set; } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/ILdapUser.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/ILdapUser.cs deleted file mode 100644 index 8ef68f6607..0000000000 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/ILdapUser.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Volo.Abp.Ldap.Modeling -{ - public interface ILdapUser : ILdapEntry - { - string SamAccountName { get; set; } - string UserPrincipalName { get; set; } - string DisplayName { get; set; } - string Email { get; set; } - string Phone { get; set; } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/LdapEntryBase.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/LdapEntryBase.cs deleted file mode 100644 index 6658a983d0..0000000000 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/LdapEntryBase.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Novell.Directory.Ldap; - -namespace Volo.Abp.Ldap.Modeling -{ - public abstract class LdapEntryBase : ILdapEntry - { - public string ObjectCategory { get; set; } - public string[] ObjectClass { get; set; } - public string Name { get; set; } - public string CommonName { get; set; } - public string DistinguishedName { get; set; } - - protected LdapEntryBase() { } - - protected LdapEntryBase(LdapAttributeSet attributeSet) - { - ObjectCategory = attributeSet.getAttribute("objectCategory")?.StringValue; - ObjectClass = attributeSet.getAttribute("objectClass")?.StringValueArray; - Name = attributeSet.getAttribute("name")?.StringValue; - CommonName = attributeSet.getAttribute("cn")?.StringValue; - DistinguishedName = attributeSet.getAttribute("distinguishedName")?.StringValue; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/LdapOrganization.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/LdapOrganization.cs deleted file mode 100644 index e401a2514d..0000000000 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/LdapOrganization.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Novell.Directory.Ldap; - -namespace Volo.Abp.Ldap.Modeling -{ - public class LdapOrganization : LdapEntryBase, ILdapOrganization - { - public string OrganizationUnit { get; set; } - - public LdapOrganization() { } - - public LdapOrganization(LdapAttributeSet attributeSet) - : base(attributeSet) - { - OrganizationUnit = attributeSet.getAttribute("ou")?.StringValue; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/LdapUser.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/LdapUser.cs deleted file mode 100644 index d9f554d512..0000000000 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Modeling/LdapUser.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Novell.Directory.Ldap; - -namespace Volo.Abp.Ldap.Modeling -{ - public class LdapUser : LdapEntryBase, ILdapUser - { - public string SamAccountName { get; set; } - public string UserPrincipalName { get; set; } - public string DisplayName { get; set; } - public string Email { get; set; } - public string Phone { get; set; } - - public LdapUser() { } - - public LdapUser( LdapAttributeSet attributeSet) - : base(attributeSet) - { - SamAccountName = attributeSet.getAttribute("sAMAccountName")?.StringValue; - UserPrincipalName = attributeSet.getAttribute("userPrincipalName")?.StringValue; - DisplayName = attributeSet.getAttribute("displayName")?.StringValue; - Email = attributeSet.getAttribute("mail")?.StringValue; - Phone = attributeSet.getAttribute("telephoneNumber")?.StringValue; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ldap/readme.md b/framework/src/Volo.Abp.Ldap/readme.md deleted file mode 100644 index dd9b84087d..0000000000 --- a/framework/src/Volo.Abp.Ldap/readme.md +++ /dev/null @@ -1,171 +0,0 @@ -# Volo.Abp.Ldap - -# Only Authenticate(not read/write AD) - -## Configure - -add section in `appsettings.json` - -### use SSL - -```json -"LDAP": { - "ServerHost": "192.168.101.54", - "ServerPort": 636, - "UseSsl": true -} -``` - -### not use SSL - -```json -"LDAP": { - "ServerHost": "192.168.101.54", - "ServerPort": 389, - "UseSsl": false -} -``` - -## Authenticate - - Injecting `ILdapManager` into a class. For example: - -```csharp -public class TaxAppService : ApplicationService -{ - private readonly ILdapManager _ldapManager; - - public TaxAppService(ILdapManager ldapManager) - { - _ldapManager = ldapManager; - } - - public void Authenticate(string userName, string password) - { - var result = _ldapManager.Authenticate(userName, password); - } -} -``` - -- `userName` must be full domain name. E.g abc@abc.com - -# Read/Write AD - -## Configure - -### use SSL - -```json -"LDAP": { - "ServerHost": "192.168.101.54", - "ServerPort": 636, - "UseSsl": true, - "Credentials": { - "DomainUserName": "administrator@yourdomain.com.cn", - "Password": "yH.20190528" - }, - "SearchBase": "DC=yourdomain,DC=com,DC=cn", - "DomainName": "yourdomain.com.cn", - "DomainDistinguishedName": "DC=yourdomain,DC=com,DC=cn" -} -``` - -### not use SSL - -```json -"LDAP": { - "ServerHost": "192.168.101.54", - "ServerPort": 389, - "UseSsl": false, - "Credentials": { - "DomainUserName": "administrator@yourdomain.com.cn", - "Password": "yH.20190528" - }, - "SearchBase": "DC=yourdomain,DC=com,DC=cn", - "DomainName": "yourdomain.com.cn", - "DomainDistinguishedName": "DC=yourdomain,DC=com,DC=cn" -} -``` - -- `Credentials:DomainUserName` a administrator of AD. - -- `Credentials:Password` the password for the administrator. -- `SearchBase`: where search from AD. -- `DomainName`: name of you domain. no need `www`. -- `DomainDistinguishedName`: distinguished name of root domain. - -## Query Organizations - -```cs -// query all organizations -// filter: (&(objectClass=organizationalUnit)) -_ldapManager.GetOrganizations(); - -// query organizations by name -// filter: (&(name=abc)(objectClass=organizationalUnit)) -_ldapManager.GetOrganizations("abc"); - -``` - -## Query Organization - -```csharp -// query organization by distinguished name -// filter: (&(distinguishedName=abc)(objectClass=organizationalUnit)) -_ldapManager.GetOrganization("abc"); - -``` - -## Add Organization - -```csharp -// use LdapOrganization -_ldapManager.AddSubOrganization("nameA", parentOrganization); - -// or use OrganizationDistinguishedName -_ldapManager.AddSubOrganization("nameA", "OU=Domain Controllers,DC=yourdomain,DC=com,DC=cn"); -``` - -## Query Users - -```cs -// query all users -// filter: (&(objectCategory=person)(objectClass=user)) -_ldapManager.GetUsers(); - -// query organizations by name -// filter: (&(name=abc)(objectCategory=person)(objectClass=user)) -_ldapManager.GetUsers(name : "abc"); - -// query organizations by displayName -// filter: (&(displayName=abc)(objectCategory=person)(objectClass=user)) -_ldapManager.GetUsers(displayName : "abc"); - -// query organization by commonName -// filter: (&(cn=abc)(objectCategory=person)(objectClass=user)) -_ldapManager.GetUsers(commonName : "abc"); - -``` - -## Query User - -```csharp -// query a user by distinguished name -// filter: (&(distinguishedName=abc)(objectCategory=person)(objectClass=user)) -_ldapManager.GetUser("abc"); - -``` - -## Add User - -```csharp -// use LdapOrganization -_ldapManager.AddUserToOrganization("nameA", "passwordA", parentOrganization); - -// or use OrganizationDistinguishedName -_ldapManager.AddUserToOrganization("nameA", "passwordA", "OU=Domain Controllers,DC=yourdomain,DC=com,DC=cn"); -``` - -# More - -See [unit test](../../test/Volo.Abp.Ldap.Tests) \ No newline at end of file diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationOptionsExtensions.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationOptionsExtensions.cs index 36355027db..410e4684cc 100644 --- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationOptionsExtensions.cs +++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationOptionsExtensions.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Globalization; using System.Linq; namespace Volo.Abp.Localization @@ -24,6 +25,11 @@ namespace Volo.Abp.Localization : language; } + public static string GetCurrentUICultureLanguagesMap(this AbpLocalizationOptions localizationOptions, string packageName) + { + return GetLanguagesMap(localizationOptions, packageName, CultureInfo.CurrentUICulture.Name); + } + public static AbpLocalizationOptions AddLanguageFilesMapOrUpdate(this AbpLocalizationOptions localizationOptions, string packageName, params NameValue[] maps) { @@ -43,6 +49,11 @@ namespace Volo.Abp.Localization : language; } + public static string GetCurrentUICultureLanguageFilesMap(this AbpLocalizationOptions localizationOptions, string packageName) + { + return GetLanguageFilesMap(localizationOptions, packageName, CultureInfo.CurrentUICulture.Name); + } + private static void AddOrUpdate(IDictionary> maps, string packageName, NameValue value) { if (maps.TryGetValue(packageName, out var existMaps)) diff --git a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs index 1dc38bf5ea..fdf66b08a6 100644 --- a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs +++ b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Dynamic.Core; using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; @@ -25,21 +26,21 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb public virtual IMemoryDatabase Database => DatabaseProvider.GetDatabase(); protected IMemoryDatabaseProvider DatabaseProvider { get; } - + public ILocalEventBus LocalEventBus { get; set; } public IDistributedEventBus DistributedEventBus { get; set; } public IEntityChangeEventHelper EntityChangeEventHelper { get; set; } - + public IAuditPropertySetter AuditPropertySetter { get; set; } - + public IGuidGenerator GuidGenerator { get; set; } public MemoryDbRepository(IMemoryDatabaseProvider databaseProvider) { DatabaseProvider = databaseProvider; - + LocalEventBus = NullLocalEventBus.Instance; DistributedEventBus = NullDistributedEventBus.Instance; EntityChangeEventHelper = NullEntityChangeEventHelper.Instance; @@ -49,7 +50,7 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb { return ApplyDataFilters(Collection.AsQueryable()); } - + protected virtual async Task TriggerDomainEventsAsync(object entity) { var generatesDomainEventsEntity = entity as IGeneratesDomainEvents; @@ -80,7 +81,7 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb generatesDomainEventsEntity.ClearDistributedEvents(); } } - + protected virtual bool IsHardDeleted(TEntity entity) { if (!(UnitOfWorkManager?.Current?.Items.GetOrDefault(UnitOfWorkItemNames.HardDeletedEntities) is HashSet hardDeletedEntities)) @@ -90,7 +91,7 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb return hardDeletedEntities.Contains(entity); } - + protected virtual void CheckAndSetId(TEntity entity) { if (entity is IEntity entityWithGuidId) @@ -117,12 +118,12 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb { AuditPropertySetter.SetCreationProperties(entity); } - + protected virtual void SetModificationAuditProperties(TEntity entity) { AuditPropertySetter.SetModificationProperties(entity); } - + protected virtual void SetDeletionAuditProperties(TEntity entity) { AuditPropertySetter.SetDeletionProperties(entity); @@ -139,7 +140,7 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb await EntityChangeEventHelper.TriggerEntityUpdatedEventOnUowCompletedAsync(entity); await EntityChangeEventHelper.TriggerEntityUpdatingEventAsync(entity); } - + protected virtual async Task TriggerEntityDeleteEventsAsync(TEntity entity) { await EntityChangeEventHelper.TriggerEntityDeletedEventOnUowCompletedAsync(entity); @@ -153,7 +154,7 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb await TriggerEntityCreateEvents(entity); await TriggerDomainEventsAsync(entity); } - + protected virtual async Task ApplyAbpConceptsForDeletedEntityAsync(TEntity entity) { SetDeletionAuditProperties(entity); @@ -170,8 +171,8 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb } public override async Task DeleteAsync( - Expression> predicate, - bool autoSave = false, + Expression> predicate, + bool autoSave = false, CancellationToken cancellationToken = default) { var entities = GetQueryable().Where(predicate).ToList(); @@ -189,10 +190,10 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb await ApplyAbpConceptsForAddedEntityAsync(entity); Collection.Add(entity); - + return entity; } - + public override async Task UpdateAsync( TEntity entity, bool autoSave = false, @@ -216,7 +217,7 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb return entity; } - + public override async Task DeleteAsync( TEntity entity, bool autoSave = false, @@ -244,6 +245,19 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb { return Task.FromResult(GetQueryable().LongCount()); } + + public override Task> GetPagedListAsync( + int skipCount, + int maxResultCount, + string sorting, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + return Task.FromResult(GetQueryable() + .OrderBy(sorting) + .PageBy(skipCount, maxResultCount) + .ToList()); + } } public class MemoryDbRepository : MemoryDbRepository, IMemoryDbRepository @@ -296,4 +310,4 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb await DeleteAsync(x => x.Id.Equals(id), autoSave, cancellationToken); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs index 6abcc70f0d..8676ac0f34 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs @@ -3,6 +3,7 @@ using MongoDB.Driver.Linq; using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Dynamic.Core; using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; @@ -49,6 +50,7 @@ namespace Volo.Abp.Domain.Repositories.MongoDB LocalEventBus = NullLocalEventBus.Instance; DistributedEventBus = NullDistributedEventBus.Instance; EntityChangeEventHelper = NullEntityChangeEventHelper.Instance; + GuidGenerator = SimpleGuidGenerator.Instance; } public override async Task InsertAsync( @@ -147,6 +149,20 @@ namespace Volo.Abp.Domain.Repositories.MongoDB return await GetMongoQueryable().LongCountAsync(GetCancellationToken(cancellationToken)); } + public override async Task> GetPagedListAsync( + int skipCount, + int maxResultCount, + string sorting, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + return await GetMongoQueryable() + .OrderBy(sorting) + .As>() + .PageBy>(skipCount, maxResultCount) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + public override async Task DeleteAsync( Expression> predicate, bool autoSave = false, diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/AbpSecurityLogOptions.cs b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/AbpSecurityLogOptions.cs new file mode 100644 index 0000000000..a1cc6406f4 --- /dev/null +++ b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/AbpSecurityLogOptions.cs @@ -0,0 +1,21 @@ +namespace Volo.Abp.SecurityLog +{ + public class AbpSecurityLogOptions + { + /// + /// Default: true. + /// + public bool IsEnabled { get; set; } + + /// + /// The name of the application or service writing security log. + /// Default: null. + /// + public string ApplicationName { get; set; } + + public AbpSecurityLogOptions() + { + IsEnabled = true; + } + } +} diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/DefaultSecurityLogManager.cs b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/DefaultSecurityLogManager.cs new file mode 100644 index 0000000000..f628958bc4 --- /dev/null +++ b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/DefaultSecurityLogManager.cs @@ -0,0 +1,42 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.SecurityLog +{ + public class DefaultSecurityLogManager : ISecurityLogManager, ITransientDependency + { + protected AbpSecurityLogOptions SecurityLogOptions { get; } + + protected ISecurityLogStore SecurityLogStore { get; } + + public DefaultSecurityLogManager( + IOptions securityLogOptions, + ISecurityLogStore securityLogStore) + { + SecurityLogStore = securityLogStore; + SecurityLogOptions = securityLogOptions.Value; + } + + public async Task SaveAsync(Action saveAction = null) + { + if (!SecurityLogOptions.IsEnabled) + { + return; + } + + var securityLogInfo = await CreateAsync(); + saveAction?.Invoke(securityLogInfo); + await SecurityLogStore.SaveAsync(securityLogInfo); + } + + protected virtual Task CreateAsync() + { + return Task.FromResult(new SecurityLogInfo + { + ApplicationName = SecurityLogOptions.ApplicationName + }); + } + } +} diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/ISecurityLogManager.cs b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/ISecurityLogManager.cs new file mode 100644 index 0000000000..f368489fb0 --- /dev/null +++ b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/ISecurityLogManager.cs @@ -0,0 +1,10 @@ +using System; +using System.Threading.Tasks; + +namespace Volo.Abp.SecurityLog +{ + public interface ISecurityLogManager + { + Task SaveAsync(Action saveAction = null); + } +} diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/ISecurityLogStore.cs b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/ISecurityLogStore.cs new file mode 100644 index 0000000000..df2496f307 --- /dev/null +++ b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/ISecurityLogStore.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.SecurityLog +{ + public interface ISecurityLogStore + { + Task SaveAsync(SecurityLogInfo securityLogInfo); + } +} diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/SecurityLogInfo.cs b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/SecurityLogInfo.cs new file mode 100644 index 0000000000..b187b5bfa2 --- /dev/null +++ b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/SecurityLogInfo.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.SecurityLog +{ + [Serializable] + public class SecurityLogInfo + { + public string ApplicationName { get; set; } + + public string Identity { get; set; } + + public string Action { get; set; } + + public Dictionary ExtraProperties { get; } + + public Guid? UserId { get; set; } + + public string UserName { get; set; } + + public Guid? TenantId { get; set; } + + public string TenantName { get; set; } + + public string ClientId { get; set; } + + public string CorrelationId { get; set; } + + public string ClientIpAddress { get; set; } + + public string BrowserInfo { get; set; } + + public DateTime CreationTime { get; set; } + + public SecurityLogInfo() + { + ExtraProperties = new Dictionary(); + } + + public override string ToString() + { + return $"SECURITY LOG: [{ApplicationName} - {Identity} - {Action}]"; + } + } +} diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/SimpleSecurityLogStore.cs b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/SimpleSecurityLogStore.cs new file mode 100644 index 0000000000..fe6496ef40 --- /dev/null +++ b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/SimpleSecurityLogStore.cs @@ -0,0 +1,30 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.SecurityLog +{ + public class SimpleSecurityLogStore : ISecurityLogStore, ITransientDependency + { + public ILogger Logger { get; set; } + protected AbpSecurityLogOptions SecurityLogOptions { get; } + + public SimpleSecurityLogStore(ILogger logger, IOptions securityLogOptions) + { + Logger = logger; + SecurityLogOptions = securityLogOptions.Value; + } + + public Task SaveAsync(SecurityLogInfo securityLogInfo) + { + if (!SecurityLogOptions.IsEnabled) + { + return Task.CompletedTask; + } + + Logger.LogInformation(securityLogInfo.ToString()); + return Task.CompletedTask; + } + } +} diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/ar.json b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/ar.json new file mode 100644 index 0000000000..7b8ddf6919 --- /dev/null +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/ar.json @@ -0,0 +1,7 @@ +{ + "culture": "ar", + "texts": { + "DisplayName:Abp.Timing.Timezone": "المنطقة زمنية", + "Description:Abp.Timing.Timezone": "المنطقة الزمنية للتطبيق" + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TZConvertTimezoneProvider.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TZConvertTimezoneProvider.cs index 551b26e488..9713ea0f5c 100644 --- a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TZConvertTimezoneProvider.cs +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TZConvertTimezoneProvider.cs @@ -10,7 +10,7 @@ namespace Volo.Abp.Timing { public virtual List GetWindowsTimezones() { - return TZConvert.KnownIanaTimeZoneNames.OrderBy(x => x).Select(x => new NameValue(x, x)).ToList(); + return TZConvert.KnownWindowsTimeZoneIds.OrderBy(x => x).Select(x => new NameValue(x, x)).ToList(); } public virtual List GetIanaTimezones() diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json index ea35628dc7..ec51f85510 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/ar.json @@ -1,4 +1,4 @@ -{ +{ "culture": "ar", "texts": { "InternalServerErrorMessage": "حدث خطأ داخلي أثناء طلبك!", @@ -47,6 +47,8 @@ "PagerInfoEmpty": "عرض 0 الي 0 من 0 إدخالات", "PagerInfoFiltered": "(تمت تصفيته من _MAX_ مجموع الإدخالات)", "NoDataAvailableInDatatable": "لا توجد بيانات متاحة في الجدول", + "Total": "مجموع", + "Selected": "مختارة", "PagerShowMenuEntries": "عرض _MENU_ إدخالات", "DatatableActionDropdownDefaultText": "أجراءات", "ChangePassword": "تغيير كلمة السر", @@ -61,4 +63,4 @@ "GoBack": "عد", "Search": "بحث" } -} +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json index 2e4095ceae..b42cc1ebe3 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/cs.json @@ -47,6 +47,8 @@ "PagerInfoEmpty": "Zobrazeno od 0 do 0 z celkem 0 záznamů", "PagerInfoFiltered": "(filtrováno ze všech _MAX_ záznamů)", "NoDataAvailableInDatatable": "Nejsou žádná data", + "Total": "celkem", + "Selected": "vybráno", "PagerShowMenuEntries": "Zobrazit _MENU_ záznamů", "DatatableActionDropdownDefaultText": "Akce", "ChangePassword": "Změnit heslo", diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json index a663b772be..9727a11823 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/de.json @@ -1,18 +1,18 @@ { "culture": "de", "texts": { - "InternalServerErrorMessage": "Während Ihrer Anfrage ist ein interner Fehler aufgetreten!", - "ValidationErrorMessage": "Ihre Anfrage ist nicht gültig!", + "InternalServerErrorMessage": "Während Ihrer Anfrage ist ein interner Fehler aufgetreten!", + "ValidationErrorMessage": "Ihre Anfrage ist nicht gültig!", "ValidationNarrativeErrorMessageTitle": "Die folgenden Fehler wurden bei der Validierung entdeckt.", "DefaultErrorMessage": "Ein Fehler ist aufgetreten!", "DefaultErrorMessageDetail": "Es wurden keine Fehlerdetails vom Server gesendet.", "DefaultErrorMessage401": "Sie sind nicht authentifiziert.", - "DefaultErrorMessage401Detail": "Sie sollten sich anmelden, um diese Operation durchzuführen.", + "DefaultErrorMessage401Detail": "Sie sollten sich anmelden, um diese Operation durchzuführen.", "DefaultErrorMessage403": "Sie sind nicht autorisiert!", - "DefaultErrorMessage403Detail": "Es ist Ihnen nicht erlaubt, diese Operation durchzuführen!", + "DefaultErrorMessage403Detail": "Es ist Ihnen nicht erlaubt, diese Operation durchzuführen!", "DefaultErrorMessage404": "Ressource nicht gefunden!", "DefaultErrorMessage404Detail": "Die angeforderte Ressource konnte auf dem Server nicht gefunden werden!", - "EntityNotFoundErrorMessage": "Es gibt keine Entität {0} mit id = {1}!", + "EntityNotFoundErrorMessage": "Es gibt keine Entität {0} mit id = {1}!", "Languages": "Sprachen", "Error": "Fehler", "AreYouSure": "Sind Sie sicher?", @@ -20,11 +20,11 @@ "Yes": "Ja", "No": "Nein", "Ok": "Ok", - "Close": "Schließen", + "Close": "Schließen", "Save": "Speichern", "SavingWithThreeDot": "Speichere...", "Actions": "Aktionen", - "Delete": "Löschen", + "Delete": "Löschen", "Edit": "Bearbeiten", "Refresh": "Aktualisieren", "Language": "Sprache", @@ -36,28 +36,28 @@ "Register": "Registrieren", "Logout": "Abmelden", "Submit": "Absenden", - "Back": "Zurück", + "Back": "Zurück", "PagerSearch": "Suchen", - "PagerNext": "Nächste", + "PagerNext": "Nächste", "PagerPrevious": "Vorherige", "PagerFirst": "Erste", "PagerLast": "Letzte", - "PagerInfo": "Zeige _START_ bis _END_ von _TOTAL_ Einträgen", - "PagerInfo{0}{1}{2}": "Zeige {0} bis {1} von {2} Einträgen", - "PagerInfoEmpty": "Zeige 0 bis 0 von 0 Einträgen", - "PagerInfoFiltered": "(gefiltert von _MAX_ Einträgen insgesamt)", - "NoDataAvailableInDatatable": "Keine Daten verfügbar", - "PagerShowMenuEntries": "Zeige _MENU_ Einträge", + "PagerInfo": "Zeige _START_ bis _END_ von _TOTAL_ Einträgen", + "PagerInfo{0}{1}{2}": "Zeige {0} bis {1} von {2} Einträgen", + "PagerInfoEmpty": "Zeige 0 bis 0 von 0 Einträgen", + "PagerInfoFiltered": "(gefiltert von _MAX_ Einträgen insgesamt)", + "NoDataAvailableInDatatable": "Keine Daten verfügbar", + "PagerShowMenuEntries": "Zeige _MENU_ Einträge", "DatatableActionDropdownDefaultText": "Aktionen", - "ChangePassword": "Passwort ändern", + "ChangePassword": "Passwort ändern", "PersonalInfo": "Mein Profil", - "AreYouSureYouWantToCancelEditingWarningMessage": "Sie haben ungespeicherte Änderungen.", + "AreYouSureYouWantToCancelEditingWarningMessage": "Sie haben ungespeicherte Änderungen.", "UnhandledException": "Unerwartete Ausnahme!", "401Message": "Unauthorisiert", "403Message": "Verboten", "404Message": "Seite nicht gefunden", - "500Message": "Internet Server Fehler", + "500Message": "Interner Server Fehler", "GoHomePage": "Zur Startseite", - "GoBack": "Zurück" + "GoBack": "Zurück" } } diff --git a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs index b42b745fb4..b4158229fe 100644 --- a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs +++ b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; @@ -316,4 +316,4 @@ namespace Volo.Abp.Uow return $"[UnitOfWork {Id}]"; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkExtensions.cs b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkExtensions.cs index 5c01f54a99..c183a70390 100644 --- a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkExtensions.cs +++ b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkExtensions.cs @@ -1,4 +1,7 @@ -using JetBrains.Annotations; +using System; +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; namespace Volo.Abp.Uow { @@ -10,5 +13,43 @@ namespace Volo.Abp.Uow return unitOfWork.IsReserved && unitOfWork.ReservationName == reservationName; } + + public static void AddItem([NotNull] this IUnitOfWork unitOfWork, string key, TValue value) + where TValue : class + { + Check.NotNull(unitOfWork, nameof(unitOfWork)); + + if (!unitOfWork.Items.ContainsKey(key)) + { + unitOfWork.Items[key] = value; + } + else + { + unitOfWork.Items.Add(key, value); + } + } + + public static TValue GetItemOrDefault([NotNull] this IUnitOfWork unitOfWork, string key) + where TValue : class + { + Check.NotNull(unitOfWork, nameof(unitOfWork)); + + return unitOfWork.Items.FirstOrDefault(x => x.Key == key).As(); + } + + public static TValue GetOrAddItem([NotNull] this IUnitOfWork unitOfWork, string key, Func factory) + where TValue : class + { + Check.NotNull(unitOfWork, nameof(unitOfWork)); + + return unitOfWork.Items.GetOrAdd(key, factory).As(); + } + + public static void RemoveItem([NotNull] this IUnitOfWork unitOfWork, string key) + { + Check.NotNull(unitOfWork, nameof(unitOfWork)); + + unitOfWork.Items.RemoveAll(x => x.Key == key); + } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Validation.Abstractions/Volo/Abp/Validation/AbpValidationException.cs b/framework/src/Volo.Abp.Validation.Abstractions/Volo/Abp/Validation/AbpValidationException.cs index fb3be504c5..5781eaecca 100644 --- a/framework/src/Volo.Abp.Validation.Abstractions/Volo/Abp/Validation/AbpValidationException.cs +++ b/framework/src/Volo.Abp.Validation.Abstractions/Volo/Abp/Validation/AbpValidationException.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Runtime.Serialization; +using System.Text; using Microsoft.Extensions.Logging; using Volo.Abp.Logging; @@ -12,9 +13,9 @@ namespace Volo.Abp.Validation /// This exception type is used to throws validation exceptions. /// [Serializable] - public class AbpValidationException : AbpException, - IHasLogLevel, - IHasValidationErrors, + public class AbpValidationException : AbpException, + IHasLogLevel, + IHasValidationErrors, IExceptionWithSelfLogging { /// @@ -99,7 +100,8 @@ namespace Volo.Abp.Validation return; } - logger.LogWithLevel(LogLevel, "There are " + ValidationErrors.Count + " validation errors:"); + var validationErrors = new StringBuilder(); + validationErrors.AppendLine("There are " + ValidationErrors.Count + " validation errors:"); foreach (var validationResult in ValidationErrors) { var memberNames = ""; @@ -108,8 +110,10 @@ namespace Volo.Abp.Validation memberNames = " (" + string.Join(", ", validationResult.MemberNames) + ")"; } - logger.LogWithLevel(LogLevel, validationResult.ErrorMessage + memberNames); + validationErrors.AppendLine(validationResult.ErrorMessage + memberNames); } + + logger.LogWithLevel(LogLevel, validationErrors.ToString()); } } } diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ar.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ar.json index 1ef7e01b34..2cdb6999b5 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ar.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/ar.json @@ -1,34 +1,34 @@ { "culture": "ar", "texts": { - "'{0}' and '{1}' do not match.": "'{0}' and '{1}' do not match.", - "The {0} field is not a valid credit card number.": "The {0} field is not a valid credit card number.", - "{0} is not valid.": "{0} is not valid.", - "The {0} field is not a valid e-mail address.": "The {0} field is not a valid e-mail address.", - "The {0} field only accepts files with the following extensions: {1}": "The {0} field only accepts files with the following extensions: {1}", - "The field {0} must be a string or array type with a maximum length of '{1}'.": "The field {0} must be a string or array type with a maximum length of '{1}'.", - "The field {0} must be a string or array type with a minimum length of '{1}'.": "The field {0} must be a string or array type with a minimum length of '{1}'.", - "The {0} field is not a valid phone number.": "The {0} field is not a valid phone number.", - "The field {0} must be between {1} and {2}.": "The field {0} must be between {1} and {2}.", - "The field {0} must match the regular expression '{1}'.": "The field {0} must match the regular expression '{1}'.", - "The {0} field is required.": "The {0} field is required.", - "The field {0} must be a string with a maximum length of {1}.": "The field {0} must be a string with a maximum length of {1}.", - "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.", - "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "The {0} field is not a valid fully-qualified http, https, or ftp URL.", - "The field {0} is invalid.": "The field {0} is invalid.", - "ThisFieldIsNotAValidCreditCardNumber.": "This field is not a valid credit card number.", - "ThisFieldIsNotValid.": "This field is not valid.", - "ThisFieldIsNotAValidEmailAddress.": "This field is not a valid e-mail address.", - "ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "This field only accepts files with the following extensions: {0}", - "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "This field must be a string or array type with a maximum length of '{0}'.", - "ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "This field must be a string or array type with a minimum length of '{0}'.", - "ThisFieldIsNotAValidPhoneNumber.": "This field is not a valid phone number.", - "ThisFieldMustBeBetween{0}And{1}": "This field must be between {0} and {1}.", - "ThisFieldMustMatchTheRegularExpression{0}": "This field must match the regular expression '{0}'.", - "ThisFieldIsRequired.": "This field is required.", - "ThisFieldMustBeAStringWithAMaximumLengthOf{0}": "This field must be a string with a maximum length of {0}.", - "ThisFieldMustBeAStringWithAMinimumLengthOf{1}AndAMaximumLengthOf{0}": "This field must be a string with a minimum length of {1} and a maximum length of {0}.", - "ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl": "This field is not a valid fully-qualified http, https, or ftp URL.", - "ThisFieldIsInvalid.": "This field is invalid." + "'{0}' and '{1}' do not match.": "'{0}' و '{1}' غير متطابقان", + "The {0} field is not a valid credit card number.": "الحقل {0} لا يمثل رقم بطاقة ائتمان صالح.", + "{0} is not valid.": "{0} غير صالح.", + "The {0} field is not a valid e-mail address.": "الحقل {0} لا يمثل عنوان بريد إلكتروني صالح.", + "The {0} field only accepts files with the following extensions: {1}": "الحقل {0} يقبل الملفات ذات الامتدادات التالية فقط: {1}", + "The field {0} must be a string or array type with a maximum length of '{1}'.": "يجب أن يكون الحقل {0} عبارة عن سلسلة أو صفيف طوله '{1}' كحد أقصى.", + "The field {0} must be a string or array type with a minimum length of '{1}'.": "يجب أن يكون الحقل {0} عبارة عن سلسلة أو صفيف طوله كحد أدنى '{1}'.", + "The {0} field is not a valid phone number.": "الحقل {0} لا يمثل رقم هاتف صالح.", + "The field {0} must be between {1} and {2}.": "يجب أن يكون الحقل {0} بين {1} و {2}.", + "The field {0} must match the regular expression '{1}'.": "يجب أن يتطابق الحقل {0} مع التعبير النمطي '{1}'.", + "The {0} field is required.": "الحقل {0} إجباري.", + "The field {0} must be a string with a maximum length of {1}.": "يجب أن يكون الحقل {0} سلسلة أحرف طولها كحد أقصى {1}.", + "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "يجب أن يكون الحقل {0} سلسلة أحرف طولها {1} كحد أدنى و {2} كحد أقصى.", + "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "الحقل {0} ليس عنوانا URL صالحًا مؤهلاً بالكامل سواء كان عنوان http أو https أو ftp", + "The field {0} is invalid.": "الحقل {0} غير صالح.", + "ThisFieldIsNotAValidCreditCardNumber.": "هذا الحقل لا يمثل رقم بطاقة ائتمان صالح.", + "ThisFieldIsNotValid.": "هذا الحقل غير صالح.", + "ThisFieldIsNotAValidEmailAddress.": "هذا الحقل لا يمثل عنوان بريد إلكتروني صالح.", + "ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "هذا الحقل يقبل الملفات ذات الامتدادات التالية فقط: {0}", + "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "هذا الحقل يجب أن يكون سلسلة أحرف أو صفيف لا يتعدى طوله '{0}'.", + "ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": " هذا الحقل يجب أن يكون عبارة عن سلسلة أو صفيف طوله كحد أدنى '{0}'.", + "ThisFieldIsNotAValidPhoneNumber.": "هذا الحقل لا يمثل رقم هاتف صالح.", + "ThisFieldMustBeBetween{0}And{1}": "هذا الحقل يجب أن يكون بين {0} و {1}.", + "ThisFieldMustMatchTheRegularExpression{0}": "هذا الحقل يجب أن يتطابق مع التعبير النمطي '{0}'.", + "ThisFieldIsRequired.": "هذا الحقل إجباري.", + "ThisFieldMustBeAStringWithAMaximumLengthOf{0}": "هذا الحقل يجب أن يكون سلسلة أحرف طولها كحد أقصى {0}.", + "ThisFieldMustBeAStringWithAMinimumLengthOf{1}AndAMaximumLengthOf{0}": "هذا الحقل يجب أن يكون سلسلة أحرف طولها {0} كحد أدنى و {1} كحد أقصى.", + "ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl": "هذا الحقل ليس عنوانا URL صالحًا مؤهلاً بالكامل سواء كان عنوان http أو https أو ftp", + "ThisFieldIsInvalid.": "هذا الحقل غير صالح." } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/de.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/de.json index 1e0e10716a..6c48a6c1fa 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/de.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/de.json @@ -1,34 +1,34 @@ { "culture": "de", "texts": { - "'{0}' and '{1}' do not match.": "'{0}' und '{1}' stimmen nicht überein.", - "The {0} field is not a valid credit card number.": "Das Feld {0} ist keine gültige Kreditkartennummer.", - "{0} is not valid.": "{0} ist nicht gültig.", - "The {0} field is not a valid e-mail address.": "Das Feld {0} ist keine gültige E-Mail-Adresse.", + "'{0}' and '{1}' do not match.": "'{0}' und '{1}' stimmen nicht überein.", + "The {0} field is not a valid credit card number.": "Das Feld {0} ist keine gültige Kreditkartennummer.", + "{0} is not valid.": "{0} ist nicht gültig.", + "The {0} field is not a valid e-mail address.": "Das Feld {0} ist keine gültige E-Mail-Adresse.", "The {0} field only accepts files with the following extensions: {1}": "Das Feld {0} akzeptiert nur Dateien mit den folgenden Erweiterungen: {1}", - "The field {0} must be a string or array type with a maximum length of '{1}'.": "Das Feld {0} muss eine Zeichenfolge oder Auflistung mit einer maximalen Länge von '{1}' sein.", - "The field {0} must be a string or array type with a minimum length of '{1}'.": "Das Feld {0} muss eine Zeichenfolge oder Auflistung mit einer Mindestlänge von '{1}' sein.", - "The {0} field is not a valid phone number.": "Das Feld {0} ist keine gültige Telefonnummer.", + "The field {0} must be a string or array type with a maximum length of '{1}'.": "Das Feld {0} muss eine Zeichenfolge oder Auflistung mit einer maximalen Länge von '{1}' sein.", + "The field {0} must be a string or array type with a minimum length of '{1}'.": "Das Feld {0} muss eine Zeichenfolge oder Auflistung mit einer Mindestlänge von '{1}' sein.", + "The {0} field is not a valid phone number.": "Das Feld {0} ist keine gültige Telefonnummer.", "The field {0} must be between {1} and {2}.": "Das Feld {0} muss zwischen {1} und {2} liegen.", - "The field {0} must match the regular expression '{1}'.": "Das Feld {0} muss dem regulären Ausdruck '{1}' entsprechen.", + "The field {0} must match the regular expression '{1}'.": "Das Feld {0} muss dem regulären Ausdruck '{1}' entsprechen.", "The {0} field is required.": "Das Feld {0} ist erforderlich.", - "The field {0} must be a string with a maximum length of {1}.": "Das Feld {0} muss eine Zeichenfolge mit einer maximalen Länge von {1} sein.", - "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Das Feld {0} muss eine Zeichenfolge mit einer minimalen Länge von {2} und einer maximalen Länge von {1} sein.", - "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "Das {0}-Feld ist keine gültige vollqualifizierte http-, https- oder ftp-URL.", - "The field {0} is invalid.": "Das Feld {0} ist ungültig.", - "ThisFieldIsNotAValidCreditCardNumber.": "Dieses Feld ist keine gültige Kreditkartennummer.", - "ThisFieldIsNotValid.": "Dieses Feld ist nicht gültig.", - "ThisFieldIsNotAValidEmailAddress.": "Dieses Feld ist keine gültige E-Mail-Adresse.", + "The field {0} must be a string with a maximum length of {1}.": "Das Feld {0} muss eine Zeichenfolge mit einer maximalen Länge von {1} sein.", + "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Das Feld {0} muss eine Zeichenfolge mit einer minimalen Länge von {2} und einer maximalen Länge von {1} sein.", + "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "Das {0}-Feld ist keine gültige vollqualifizierte http-, https- oder ftp-URL.", + "The field {0} is invalid.": "Das Feld {0} ist ungültig.", + "ThisFieldIsNotAValidCreditCardNumber.": "Dieses Feld ist keine gültige Kreditkartennummer.", + "ThisFieldIsNotValid.": "Dieses Feld ist nicht gültig.", + "ThisFieldIsNotAValidEmailAddress.": "Dieses Feld ist keine gültige E-Mail-Adresse.", "ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "Dieses Feld akzeptiert nur Dateien mit den folgenden Erweiterungen: {0}", - "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "Dieses Feld muss eine Zeichenfolge oder Auflistung mit einer maximalen Länge von '{0}' sein.", - "ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "Dieses Feld muss eine Zeichenfolge oder Auflistung mit einer Mindestlänge von '{0}' sein.", - "ThisFieldIsNotAValidPhoneNumber.": "Dieses Feld ist keine gültige Telefonnummer.", + "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "Dieses Feld muss eine Zeichenfolge oder Auflistung mit einer maximalen Länge von '{0}' sein.", + "ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "Dieses Feld muss eine Zeichenfolge oder Auflistung mit einer Mindestlänge von '{0}' sein.", + "ThisFieldIsNotAValidPhoneNumber.": "Dieses Feld ist keine gültige Telefonnummer.", "ThisFieldMustBeBetween{0}And{1}": "Dieses Feld muss zwischen {0} und {1} liegen.", - "ThisFieldMustMatchTheRegularExpression{0}": "Dieses Feld muss dem regulären Ausdruck '{0}' entsprechen.", + "ThisFieldMustMatchTheRegularExpression{0}": "Dieses Feld muss dem regulären Ausdruck '{0}' entsprechen.", "ThisFieldIsRequired.": "Dieses Feld ist erforderlich.", - "ThisFieldMustBeAStringWithAMaximumLengthOf{0}": "Dieses Feld muss eine Zeichenfolge mit einer maximalen Länge von {0} sein.", - "ThisFieldMustBeAStringWithAMinimumLengthOf{1}AndAMaximumLengthOf{0}": "Dieses Feld muss eine Zeichenfolge mit einer Mindestlänge von '{0}' sein.", - "ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl": "Dieses Feld ist keine gültige vollqualifizierte http-, https- oder ftp-URL.", - "ThisFieldIsInvalid.": "Dieses Feld ist ungültig." + "ThisFieldMustBeAStringWithAMaximumLengthOf{0}": "Dieses Feld muss eine Zeichenfolge mit einer maximalen Länge von {0} sein.", + "ThisFieldMustBeAStringWithAMinimumLengthOf{1}AndAMaximumLengthOf{0}": "Dieses Feld muss eine Zeichenfolge mit einer Mindestlänge von '{0}' sein.", + "ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl": "Dieses Feld ist keine gültige vollqualifizierte http-, https- oder ftp-URL.", + "ThisFieldIsInvalid.": "Dieses Feld ist ungültig." } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.VirtualFileSystem/Volo.Abp.VirtualFileSystem.csproj b/framework/src/Volo.Abp.VirtualFileSystem/Volo.Abp.VirtualFileSystem.csproj index b4aa55b1c0..09af40a2c7 100644 --- a/framework/src/Volo.Abp.VirtualFileSystem/Volo.Abp.VirtualFileSystem.csproj +++ b/framework/src/Volo.Abp.VirtualFileSystem/Volo.Abp.VirtualFileSystem.csproj @@ -15,9 +15,9 @@ - - - + + + diff --git a/framework/test/SimpleConsoleDemo/SimpleConsoleDemo.csproj b/framework/test/SimpleConsoleDemo/SimpleConsoleDemo.csproj index 49c80c56db..4edbb748b9 100644 --- a/framework/test/SimpleConsoleDemo/SimpleConsoleDemo.csproj +++ b/framework/test/SimpleConsoleDemo/SimpleConsoleDemo.csproj @@ -18,7 +18,7 @@ - + diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/de.json b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/de.json index e5b59a1915..f5c2e651b5 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/de.json +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/de.json @@ -2,6 +2,6 @@ "culture": "de", "texts": { "BirthDate": "Geburtsdatum", - "Value1": "Wert Eins" + "Value1": "Wert Eins" } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/PeopleAppService_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/PeopleAppService_Tests.cs index 96a79d8971..aae7345908 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/PeopleAppService_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/PeopleAppService_Tests.cs @@ -83,7 +83,6 @@ namespace Volo.Abp.AspNetCore.Mvc (await _personRepository.FindAsync(resultDto.Id)).ShouldNotBeNull(); } - [Fact] public async Task Update_Test() { @@ -170,4 +169,4 @@ namespace Volo.Abp.AspNetCore.Mvc douglas.Phones.Any(p => p.Number == firstPhone.Number).ShouldBeFalse(); } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/FormElements.cshtml b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/FormElements.cshtml index 889e798c8a..6fa9cabb37 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/FormElements.cshtml +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/FormElements.cshtml @@ -108,7 +108,7 @@ public class FormElementsModel : PageModel { public SampleModel MyModel { get; set; } - + public List<SelectListItem> CityList { get; set; } = new List<SelectListItem> { new SelectListItem { Value = "NY", Text = "New York"}, @@ -260,7 +260,7 @@ public class SampleModel { public string SampleInput0 { get; set; } - + public string SampleInput1 { get; set; } public string SampleInput2 { get; set; } @@ -354,7 +354,7 @@ public class FormElementsModel : PageModel { public SampleModel MyModel { get; set; } - + public List"SelectListItem" CityList { get; set; } = new List"SelectListItem" { new SelectListItem { Value = "NY", Text = "New York"}, @@ -458,6 +458,51 @@ <option value="3">Coupe</option> </select> </div> + + + + + + +

    Suppress Label Generation

    + +
    +
    + +
    +
    + + +
    
    + public class FormElementsModel : PageModel
    + {
    +     public SampleModel MyModel { get; set; }
    +
    +     public void OnGet()
    +     {
    +         MyModel = new SampleModel();
    +     }
    +
    +     public class SampleModel
    +     {
    +         [Required]
    +         public string Name { get; set; }
    +     }
    + }
    +
    +
    +
    + +
    
    +<abp-input asp-for="@@Model.MyModel.Name" suppress-label="true"/>
    +                
    +
    + +
    
    +<div class="form-group">
    +    <input type="text" id="MyModel_Name" name="MyModel.Name" value="" class="form-control ">
    +    <span class="text-danger field-validation-valid" data-valmsg-for="MyModel.Name" data-valmsg-replace="true"></span>
    +</div>
     
    diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Pages/Components/Dropdowns/Index.cshtml b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Pages/Components/Dropdowns/Index.cshtml index 2b8c7f387e..9bdf7abe38 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Pages/Components/Dropdowns/Index.cshtml +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/Pages/Components/Dropdowns/Index.cshtml @@ -4,10 +4,10 @@ @model Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo.Pages.Components.Dropdowns.IndexModel @inject IPageLayout PageLayout @{ - PageLayout.Content.Title = "DrowDowns"; + PageLayout.Content.Title = "Dropdowns"; } -

    Cards

    -

    Based on Bootstrap button.

    +

    Dropdowns

    +

    Based on Bootstrap Dropdown.

    Check the ABP Documentation.

    @await Component.InvokeAsync(typeof(DropdownsDemoViewComponent)) \ No newline at end of file diff --git a/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo.Abp.BlobStoring.Aliyun.Tests.csproj b/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo.Abp.BlobStoring.Aliyun.Tests.csproj new file mode 100644 index 0000000000..7d61dcecae --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo.Abp.BlobStoring.Aliyun.Tests.csproj @@ -0,0 +1,24 @@ + + + + + + netcoreapp3.1 + + 9f0d2c00-80c1-435b-bfab-2c39c8249091 + + + + + + + + + + + + + + + + diff --git a/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunTestBase.cs b/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunTestBase.cs new file mode 100644 index 0000000000..bc696ad877 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunTestBase.cs @@ -0,0 +1,19 @@ +using Volo.Abp.Testing; + +namespace Volo.Abp.BlobStoring.Aliyun +{ + public class AbpBlobStoringAliyunTestCommonBase : AbpIntegratedTest + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + } + public class AbpBlobStoringAliyunTestBase : AbpIntegratedTest + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + } +} diff --git a/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunTestModule.cs b/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunTestModule.cs new file mode 100644 index 0000000000..b08737eba5 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AbpBlobStoringAliyunTestModule.cs @@ -0,0 +1,86 @@ +using Aliyun.OSS; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Linq; +using Volo.Abp.Modularity; + +namespace Volo.Abp.BlobStoring.Aliyun +{ + [DependsOn( + typeof(AbpBlobStoringAliyunModule), + typeof(AbpBlobStoringTestModule) + )] + public class AbpBlobStoringAliyunTestCommonModule : AbpModule + { + + } + + [DependsOn( + typeof(AbpBlobStoringAliyunTestCommonModule) + )] + public class AbpBlobStoringAliyunTestModule : AbpModule + { + private const string UserSecretsId = "9f0d2c00-80c1-435b-bfab-2c39c8249091"; + + private AliyunBlobProviderConfiguration _configuration; + private readonly string _randomContainerName = "abp-aliyun-test-container-" + Guid.NewGuid().ToString("N"); + + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.ReplaceConfiguration(ConfigurationHelper.BuildConfiguration(builderAction: builder => + { + builder.AddUserSecrets(UserSecretsId); + })); + + var configuration = context.Services.GetConfiguration(); + var accessKeyId = configuration["Aliyun:AccessKeyId"]; + var accessKeySecret = configuration["Aliyun:AccessKeySecret"]; + var endpoint = configuration["Aliyun:Endpoint"]; + var regionId = configuration["Aliyun:RegionId"]; + var roleArn = configuration["Aliyun:RoleArn"]; + + Configure(options => + { + options.Containers.ConfigureAll((containerName, containerConfiguration) => + { + containerConfiguration.UseAliyun(aliyun => + { + aliyun.AccessKeyId = accessKeyId; + aliyun.AccessKeySecret = accessKeySecret; + aliyun.Endpoint = endpoint; + //STS + aliyun.UseSecurityTokenService = true; + aliyun.RegionId = regionId; + aliyun.RoleArn = roleArn; + aliyun.RoleSessionName = Guid.NewGuid().ToString("N"); + aliyun.DurationSeconds = 900; + aliyun.Policy = String.Empty; + //Other + aliyun.CreateContainerIfNotExists = true; + aliyun.ContainerName = _randomContainerName; + aliyun.TemporaryCredentialsCacheKey = "297A96094D7048DBB2C28C3FDB20839A"; + _configuration = aliyun; + }); + }); + }); + } + + public override void OnApplicationShutdown(ApplicationShutdownContext context) + { + var ossClientFactory = context.ServiceProvider.GetService(); + var ossClient = ossClientFactory.Create(_configuration); + if(ossClient.DoesBucketExist(_randomContainerName)) + { + var objects = ossClient.ListObjects(_randomContainerName); + if (objects.ObjectSummaries.Any()) + { + ossClient.DeleteObjects(new DeleteObjectsRequest(_randomContainerName, + objects.ObjectSummaries.Select(o => o.Key).ToList())); + } + ossClient.DeleteBucket(_randomContainerName); + } + } + + } +} diff --git a/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AliyunBlobContainer_Tests.cs b/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AliyunBlobContainer_Tests.cs new file mode 100644 index 0000000000..29f84091df --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AliyunBlobContainer_Tests.cs @@ -0,0 +1,17 @@ +using Shouldly; +using System.IO; +using System.Text; +using Xunit; + +namespace Volo.Abp.BlobStoring.Aliyun +{ + /* + //Please set the correct connection string in secrets.json and continue the test. + public class AliyunBlobContainer_Tests : BlobContainer_Tests + { + public AliyunBlobContainer_Tests() + { + } + } + */ +} diff --git a/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AliyunBlobNameCalculator_Tests.cs b/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AliyunBlobNameCalculator_Tests.cs new file mode 100644 index 0000000000..d7ea806b16 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/AliyunBlobNameCalculator_Tests.cs @@ -0,0 +1,55 @@ +using Shouldly; +using System; +using Volo.Abp.MultiTenancy; +using Xunit; + +namespace Volo.Abp.BlobStoring.Aliyun +{ + public class AliyunBlobNameCalculator_Tests: AbpBlobStoringAliyunTestCommonBase + { + private readonly IAliyunBlobNameCalculator _calculator; + private readonly ICurrentTenant _currentTenant; + + private const string AliyunSeparator = "/"; + + public AliyunBlobNameCalculator_Tests() + { + _calculator = GetRequiredService(); + _currentTenant = GetRequiredService(); + } + + [Fact] + public void Default_Settings() + { + _calculator.Calculate( + GetArgs("my-container", "my-blob") + ).ShouldBe($"host{AliyunSeparator}my-blob"); + } + + [Fact] + public void Default_Settings_With_TenantId() + { + var tenantId = Guid.NewGuid(); + + using (_currentTenant.Change(tenantId)) + { + _calculator.Calculate( + GetArgs("my-container", "my-blob") + ).ShouldBe($"tenants{AliyunSeparator}{tenantId:D}{AliyunSeparator}my-blob"); + } + } + + private static BlobProviderArgs GetArgs( + string containerName, + string blobName) + { + return new BlobProviderGetArgs( + containerName, + new BlobContainerConfiguration().UseAliyun(x => + { + }), + blobName + ); + } + } +} diff --git a/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/DefaultAliyunBlobNamingNormalizerProvider_Tests.cs b/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/DefaultAliyunBlobNamingNormalizerProvider_Tests.cs new file mode 100644 index 0000000000..895c87cc91 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Aliyun.Tests/Volo/Abp/BlobStoring/Aliyun/DefaultAliyunBlobNamingNormalizerProvider_Tests.cs @@ -0,0 +1,58 @@ +using Shouldly; +using Xunit; + +namespace Volo.Abp.BlobStoring.Aliyun +{ + public class DefaultAliyunBlobNamingNormalizerProvider_Tests: AbpBlobStoringAliyunTestCommonBase + { + private readonly IBlobNamingNormalizer _blobNamingNormalizer; + + public DefaultAliyunBlobNamingNormalizerProvider_Tests() + { + _blobNamingNormalizer = GetRequiredService(); + } + + [Fact] + public void NormalizeContainerName_Lowercase() + { + var filename = "ThisIsMyContainerName"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.ShouldBe("thisismycontainername"); + } + + [Fact] + public void NormalizeContainerName_Only_Letters_Numbers_Dash() + { + var filename = ",./this-i,./s-my-c,./ont,./ai+*/.=!@#$n^&*er-name.+/"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.ShouldBe("this-is-my-container-name"); + } + + [Fact] + public void NormalizeContainerName_Dash() + { + var filename = "-this--is----my-container----name-"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.ShouldBe("this-is-my-container-name"); + } + + + [Fact] + public void NormalizeContainerName_Min_Length() + { + var filename = "a"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.Length.ShouldBeGreaterThanOrEqualTo(3); + } + + + [Fact] + public void NormalizeContainerName_Max_Length() + { + var filename = "abpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabp"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.Length.ShouldBeLessThanOrEqualTo(63); + } + + } +} diff --git a/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo.Abp.BlobStoring.Aws.Tests.csproj b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo.Abp.BlobStoring.Aws.Tests.csproj new file mode 100644 index 0000000000..2a4910e32c --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo.Abp.BlobStoring.Aws.Tests.csproj @@ -0,0 +1,19 @@ + + + + + + netcoreapp3.1 + + 9f0d2c00-80c1-435b-bfab-2c39c8249091 + + + + + + + + + + + diff --git a/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAwsTestBase.cs b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAwsTestBase.cs new file mode 100644 index 0000000000..eef12cb0a1 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAwsTestBase.cs @@ -0,0 +1,20 @@ +using Volo.Abp.Testing; + +namespace Volo.Abp.BlobStoring.Aws +{ + public class AbpBlobStoringAwsTestCommonBase : AbpIntegratedTest + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + } + + public class AbpBlobStoringAwsTestBase : AbpIntegratedTest + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + } +} diff --git a/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAwsTestModule.cs b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAwsTestModule.cs new file mode 100644 index 0000000000..d176a864e7 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AbpBlobStoringAwsTestModule.cs @@ -0,0 +1,92 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Amazon.S3.Model; +using Amazon.S3.Util; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; +using Volo.Abp.Threading; + +namespace Volo.Abp.BlobStoring.Aws +{ + /// + /// This module will not try to connect to aws. + /// + [DependsOn( + typeof(AbpBlobStoringAwsModule), + typeof(AbpBlobStoringTestModule) + )] + public class AbpBlobStoringAwsTestCommonModule : AbpModule + { + } + + [DependsOn( + typeof(AbpBlobStoringAwsTestCommonModule) + )] + public class AbpBlobStoringAwsTestModule : AbpModule + { + private const string UserSecretsId = "9f0d2c00-80c1-435b-bfab-2c39c8249091"; + + private readonly string _randomContainerName = "abp-aws-test-container-" + Guid.NewGuid().ToString("N"); + + private AwsBlobProviderConfiguration _configuration; + + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.ReplaceConfiguration(ConfigurationHelper.BuildConfiguration(builderAction: builder => + { + builder.AddUserSecrets(UserSecretsId); + })); + + var configuration = context.Services.GetConfiguration(); + var accessKeyId = configuration["Aws:AccessKeyId"]; + var secretAccessKey = configuration["Aws:SecretAccessKey"]; + var region = configuration["Aws:Region"]; + + Configure(options => + { + options.Containers.ConfigureAll((containerName, containerConfiguration) => + { + containerConfiguration.UseAws(aws => + { + aws.AccessKeyId = accessKeyId; + aws.SecretAccessKey = secretAccessKey; + aws.Region = region; + aws.CreateContainerIfNotExists = true; + aws.ContainerName = _randomContainerName; + + _configuration = aws; + }); + }); + }); + } + + public override void OnApplicationShutdown(ApplicationShutdownContext context) + { + AsyncHelper.RunSync(() => DeleteBucketAsync(context)); + } + + private async Task DeleteBucketAsync(ApplicationShutdownContext context) + { + var amazonS3Client = await context.ServiceProvider.GetService() + .GetAmazonS3Client(_configuration); + + if (await AmazonS3Util.DoesS3BucketExistV2Async(amazonS3Client, _randomContainerName)) + { + var blobs = await amazonS3Client.ListObjectsAsync(_randomContainerName); + + if (blobs.S3Objects.Any()) + { + await amazonS3Client.DeleteObjectsAsync(new DeleteObjectsRequest + { + BucketName = _randomContainerName, + Objects = blobs.S3Objects.Select(o => new KeyVersion {Key = o.Key}).ToList() + }); + } + + await amazonS3Client.DeleteBucketAsync(_randomContainerName); + } + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AwsBlobContainer_Tests.cs b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AwsBlobContainer_Tests.cs new file mode 100644 index 0000000000..662949e7e3 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AwsBlobContainer_Tests.cs @@ -0,0 +1,16 @@ +using Xunit; + +namespace Volo.Abp.BlobStoring.Aws +{ + /* + //Please set the correct connection string in secrets.json and continue the test. + + public class AwsBlobContainer_Tests : BlobContainer_Tests + { + public AwsBlobContainer_Tests() + { + + } + } + */ +} diff --git a/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AwsBlobNameCalculator_Tests.cs b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AwsBlobNameCalculator_Tests.cs new file mode 100644 index 0000000000..e4fbe714f8 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/AwsBlobNameCalculator_Tests.cs @@ -0,0 +1,57 @@ +using System; +using Shouldly; +using Volo.Abp.MultiTenancy; +using Xunit; + +namespace Volo.Abp.BlobStoring.Aws +{ + public class AwsBlobNameCalculatorTests : AbpBlobStoringAwsTestCommonBase + { + private readonly IAwsBlobNameCalculator _calculator; + private readonly ICurrentTenant _currentTenant; + + private const string AwsContainerName = "/"; + private const string AwsSeparator = "/"; + + public AwsBlobNameCalculatorTests() + { + _calculator = GetRequiredService(); + _currentTenant = GetRequiredService(); + } + + [Fact] + public void Default_Settings() + { + _calculator.Calculate( + GetArgs("my-container", "my-blob") + ).ShouldBe($"host{AwsSeparator}my-blob"); + } + + [Fact] + public void Default_Settings_With_TenantId() + { + var tenantId = Guid.NewGuid(); + + using (_currentTenant.Change(tenantId)) + { + _calculator.Calculate( + GetArgs("my-container", "my-blob") + ).ShouldBe($"tenants{AwsSeparator}{tenantId:D}{AwsSeparator}my-blob"); + } + } + + private static BlobProviderArgs GetArgs( + string containerName, + string blobName) + { + return new BlobProviderGetArgs( + containerName, + new BlobContainerConfiguration().UseAws(x => + { + x.ContainerName = containerName; + }), + blobName + ); + } + } +} diff --git a/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/DefaultAwsBlobNamingNormalizerProvider_Tests.cs b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/DefaultAwsBlobNamingNormalizerProvider_Tests.cs new file mode 100644 index 0000000000..7798c52419 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Aws.Tests/Volo/Abp/BlobStoring/Aws/DefaultAwsBlobNamingNormalizerProvider_Tests.cs @@ -0,0 +1,57 @@ +using Shouldly; +using Xunit; + +namespace Volo.Abp.BlobStoring.Aws +{ + public class DefaultAwsBlobNamingNormalizerProviderTests : AbpBlobStoringAwsTestCommonBase + { + private readonly IBlobNamingNormalizer _blobNamingNormalizer; + + public DefaultAwsBlobNamingNormalizerProviderTests() + { + _blobNamingNormalizer = GetRequiredService(); + } + + [Fact] + public void NormalizeContainerName_Lowercase() + { + var filename = "ThisIsMyContainerName"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.ShouldBe("thisismycontainername"); + } + + [Fact] + public void NormalizeContainerName_Only_Letters_Numbers_Dash() + { + var filename = ",./this-i,./s-my-c,./ont,./ai+*/.=!@#$n^&*er-name.+/"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.ShouldBe("this-is-my-container-name"); + } + + [Fact] + public void NormalizeContainerName_Dash() + { + var filename = "-this--is----my-container----name-"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.ShouldBe("this-is-my-container-name"); + } + + + [Fact] + public void NormalizeContainerName_Min_Length() + { + var filename = "a"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.Length.ShouldBeGreaterThanOrEqualTo(3); + } + + + [Fact] + public void NormalizeContainerName_Max_Length() + { + var filename = "abpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabp"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.Length.ShouldBeLessThanOrEqualTo(63); + } + } +} diff --git a/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo.Abp.BlobStoring.Minio.Tests.csproj b/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo.Abp.BlobStoring.Minio.Tests.csproj new file mode 100644 index 0000000000..388efc88a2 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo.Abp.BlobStoring.Minio.Tests.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp3.1 + + 9f0d2c00-80c1-435b-bfab-2c39c8249091 + + + + + + + + + + + diff --git a/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioTestBase.cs b/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioTestBase.cs new file mode 100644 index 0000000000..c15f093e58 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioTestBase.cs @@ -0,0 +1,20 @@ +using Volo.Abp.Testing; + +namespace Volo.Abp.BlobStoring.Minio +{ + public class AbpBlobStoringMinioTestCommonBase : AbpIntegratedTest + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + } + + public class AbpBlobStoringMinioTestBase : AbpIntegratedTest + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + } +} diff --git a/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioTestModule.cs b/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioTestModule.cs new file mode 100644 index 0000000000..71d780e880 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/AbpBlobStoringMinioTestModule.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Reactive.Linq; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Minio; +using Volo.Abp.Modularity; + +namespace Volo.Abp.BlobStoring.Minio +{ + + [DependsOn( + typeof(AbpBlobStoringMinioModule), + typeof(AbpBlobStoringTestModule) + )] + public class AbpBlobStoringMinioTestCommonModule : AbpModule + { + + } + + [DependsOn( + typeof(AbpBlobStoringMinioTestCommonModule) + )] + public class AbpBlobStoringMinioTestModule : AbpModule + { + private const string UserSecretsId = "9f0d2c00-80c1-435b-bfab-2c39c8249091"; + + private string _endPoint; + private string _accessKey; + private string _secretKey; + + private readonly string _randomContainerName = "abp-minio-test-container-" + Guid.NewGuid().ToString("N"); + + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.ReplaceConfiguration(ConfigurationHelper.BuildConfiguration(builderAction: builder => + { + builder.AddUserSecrets(UserSecretsId); + })); + + var configuration = context.Services.GetConfiguration(); + _endPoint = configuration["Minio:EndPoint"]; + _accessKey = configuration["Minio:AccessKey"]; + _secretKey = configuration["Minio:SecretKey"]; + + Configure(options => + { + options.Containers.ConfigureAll((containerName, containerConfiguration) => + { + containerConfiguration.UseMinio(minio => + { + minio.EndPoint = _endPoint; + minio.AccessKey = _accessKey; + minio.SecretKey = _secretKey; + minio.WithSSL = false; + minio.BucketName = _randomContainerName; + minio.CreateBucketIfNotExists = true; + }); + }); + }); + } + + public override async void OnApplicationShutdown(ApplicationShutdownContext context) + { + var minioClient = new MinioClient(_endPoint, _accessKey, _secretKey); + if (await minioClient.BucketExistsAsync(_randomContainerName)) + { + var objects = await minioClient.ListObjectsAsync(_randomContainerName, null, true).ToList(); + + foreach (var item in objects) + { + await minioClient.RemoveObjectAsync(_randomContainerName, item.Key); + } + + await minioClient.RemoveBucketAsync(_randomContainerName); + } + + } + } + +} diff --git a/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/DefaultMinioBlobNamingNormalizerProvider_Tests.cs b/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/DefaultMinioBlobNamingNormalizerProvider_Tests.cs new file mode 100644 index 0000000000..ae7c4fab75 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/DefaultMinioBlobNamingNormalizerProvider_Tests.cs @@ -0,0 +1,57 @@ +using Shouldly; +using Xunit; + +namespace Volo.Abp.BlobStoring.Minio +{ + public class DefaultMinioBlobNamingNormalizerProvider_Tests : AbpBlobStoringMinioTestCommonBase + { + private readonly IBlobNamingNormalizer _blobNamingNormalizer; + + public DefaultMinioBlobNamingNormalizerProvider_Tests() + { + _blobNamingNormalizer = GetRequiredService(); + } + + [Fact] + public void NormalizeContainerName_Lowercase() + { + var filename = "ThisIsMyContainerName"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.ShouldBe("thisismycontainername"); + } + + [Fact] + public void NormalizeContainerName_Only_Letters_Numbers_Dash() + { + var filename = ",./this-i,./s-my-c,./ont,./ai+*/.=!@#$n^&*er-name.+/"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.ShouldBe("this-is-my-container-name"); + } + + [Fact] + public void NormalizeContainerName_Dash() + { + var filename = "-this--is----my-container----name-"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.ShouldBe("this-is-my-container-name"); + } + + + [Fact] + public void NormalizeContainerName_Min_Length() + { + var filename = "a"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.Length.ShouldBeGreaterThanOrEqualTo(3); + } + + + [Fact] + public void NormalizeContainerName_Max_Length() + { + var filename = "abpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabp"; + filename = _blobNamingNormalizer.NormalizeContainerName(filename); + filename.Length.ShouldBeLessThanOrEqualTo(63); + } + } +} diff --git a/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/MinioBlobContainer_Tests.cs b/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/MinioBlobContainer_Tests.cs new file mode 100644 index 0000000000..1da4b6b9d7 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/MinioBlobContainer_Tests.cs @@ -0,0 +1,16 @@ +using Xunit; + +namespace Volo.Abp.BlobStoring.Minio +{ + /* + //Please set the correct connection string in secrets.json and continue the test. + public class MinioBlobContainer_Tests : BlobContainer_Tests + { + public MinioBlobContainer_Tests() + { + + + } + } + */ +} diff --git a/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/MinioBlobNameCalculator_Tests.cs b/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/MinioBlobNameCalculator_Tests.cs new file mode 100644 index 0000000000..9d1954efc3 --- /dev/null +++ b/framework/test/Volo.Abp.BlobStoring.Minio.Tests/Volo/Abp/BlobStoring/Minio/MinioBlobNameCalculator_Tests.cs @@ -0,0 +1,56 @@ +using System; +using Shouldly; +using Volo.Abp.MultiTenancy; +using Xunit; + +namespace Volo.Abp.BlobStoring.Minio +{ + public class MinioBlobNameCalculator_Tests : AbpBlobStoringMinioTestCommonBase + { + private readonly IMinioBlobNameCalculator _calculator; + private readonly ICurrentTenant _currentTenant; + + private const string MinioSeparator = "/"; + + public MinioBlobNameCalculator_Tests() + { + _calculator = GetRequiredService(); + _currentTenant = GetRequiredService(); + } + + [Fact] + public void Default_Settings() + { + _calculator.Calculate( + GetArgs("my-container", "my-blob") + ).ShouldBe($"host{MinioSeparator}my-blob"); + } + + [Fact] + public void Default_Settings_With_TenantId() + { + var tenantId = Guid.NewGuid(); + + using (_currentTenant.Change(tenantId)) + { + _calculator.Calculate( + GetArgs("my-container", "my-blob") + ).ShouldBe($"tenants{MinioSeparator}{tenantId:D}{MinioSeparator}my-blob"); + } + } + + private static BlobProviderArgs GetArgs( + string containerName, + string blobName) + { + return new BlobProviderGetArgs( + containerName, + new BlobContainerConfiguration().UseMinio(x => + { + x.BucketName = containerName; + }), + blobName + ); + } + } +} diff --git a/framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/BlobContainer_Tests.cs b/framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/BlobContainer_Tests.cs index 091c1b7c95..a965f2852b 100644 --- a/framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/BlobContainer_Tests.cs +++ b/framework/test/Volo.Abp.BlobStoring.Tests/Volo/Abp/BlobStoring/BlobContainer_Tests.cs @@ -3,7 +3,9 @@ using System.Linq; using System.Threading.Tasks; using Shouldly; using Volo.Abp.BlobStoring.TestObjects; +using Volo.Abp.Clients; using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; using Volo.Abp.Testing; using Xunit; @@ -14,11 +16,14 @@ namespace Volo.Abp.BlobStoring { protected IBlobContainer Container { get; } + protected ICurrentTenant CurrentTenant { get; } + protected BlobContainer_Tests() { Container = GetRequiredService>(); + CurrentTenant = GetRequiredService(); } - + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) { options.UseAutofac(); @@ -37,35 +42,68 @@ namespace Volo.Abp.BlobStoring result.SequenceEqual(testContent).ShouldBeTrue(); } + [Fact] + public async Task Should_Save_And_Get_Blobs_In_Different_Tenant() + { + var blobName = "test-blob-1"; + var testContent = "test content".GetBytes(); + + using (CurrentTenant.Change(Guid.NewGuid())) + { + await Container.SaveAsync(blobName, testContent); + (await Container.GetAllBytesAsync(blobName)).SequenceEqual(testContent).ShouldBeTrue(); + } + + using (CurrentTenant.Change(Guid.NewGuid())) + { + await Container.SaveAsync(blobName, testContent); + (await Container.GetAllBytesAsync(blobName)).SequenceEqual(testContent).ShouldBeTrue(); + + using (CurrentTenant.Change(null)) + { + // Could not found the requested BLOB... + await Assert.ThrowsAsync(async () => + await Container.GetAllBytesAsync(blobName) + ); + } + } + + using (CurrentTenant.Change(null)) + { + await Container.SaveAsync(blobName, testContent); + (await Container.GetAllBytesAsync(blobName)).SequenceEqual(testContent).ShouldBeTrue(); + } + } + [Fact] public async Task Should_Overwrite_Pre_Saved_Blob_If_Requested() { var blobName = "test-blob-1"; - + var testContent = "test content".GetBytes(); await Container.SaveAsync(blobName, testContent); - + var testContentOverwritten = "test content overwritten".GetBytes(); await Container.SaveAsync(blobName, testContentOverwritten, true); - + var result = await Container.GetAllBytesAsync(blobName); result.SequenceEqual(testContentOverwritten).ShouldBeTrue(); } - + [Fact] public async Task Should_Not_Allow_To_Overwrite_Pre_Saved_Blob_By_Default() { var blobName = "test-blob-1"; - + var testContent = "test content".GetBytes(); await Container.SaveAsync(blobName, testContent); - + var testContentOverwritten = "test content overwritten".GetBytes(); await Assert.ThrowsAsync(() => Container.SaveAsync(blobName, testContentOverwritten) ); } - + [Theory] [InlineData("test-blob-1")] [InlineData("test-blob-1.txt")] @@ -91,7 +129,7 @@ namespace Volo.Abp.BlobStoring await Container.DeleteAsync(blobName); (await Container.ExistsAsync(blobName)).ShouldBeFalse(); } - + [Theory] [InlineData("test-blob-1")] [InlineData("test-blob-1.txt")] @@ -102,4 +140,4 @@ namespace Volo.Abp.BlobStoring (await Container.ExistsAsync(blobName)).ShouldBeFalse(); } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs index be003431f2..9da6725fa5 100644 --- a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs @@ -1,5 +1,7 @@ using Microsoft.Extensions.Caching.Distributed; using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Volo.Abp.Modularity; namespace Volo.Abp.Caching @@ -26,6 +28,8 @@ namespace Volo.Abp.Caching option.GlobalCacheEntryOptions.SetSlidingExpiration(TimeSpan.FromMinutes(20)); }); + + context.Services.Replace(ServiceDescriptor.Singleton()); } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs index 533362c851..32876c08fe 100644 --- a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/DistributedCache_Tests.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Shouldly; using Volo.Abp.Testing; +using Volo.Abp.Uow; using Xunit; namespace Volo.Abp.Caching @@ -30,7 +32,7 @@ namespace Volo.Abp.Caching cacheItem.ShouldNotBeNull(); cacheItem.Name.ShouldBe(personName); - //Remove + //Remove await personCache.RemoveAsync(cacheKey); //Get (not exists since removed) @@ -111,7 +113,7 @@ namespace Volo.Abp.Caching cacheItem1.ShouldNotBeNull(); cacheItem1.Name.ShouldBe(personName); - //Remove + //Remove await personCache.RemoveAsync(cacheKey); @@ -145,7 +147,7 @@ namespace Volo.Abp.Caching cacheItem.ShouldNotBeNull(); cacheItem.Name.ShouldBe(personName); - //Remove + //Remove await personCache.RemoveAsync(cacheKey); //Get (not exists since removed) @@ -180,10 +182,10 @@ namespace Volo.Abp.Caching factoryExecuted = false; cacheItem = await personCache.GetOrAddAsync(cacheKey, - async () => + () => { factoryExecuted = true; - return new PersonCacheItem(personName); + return Task.FromResult(new PersonCacheItem(personName)); }); factoryExecuted.ShouldBeFalse(); @@ -227,7 +229,7 @@ namespace Volo.Abp.Caching cacheItem1.ShouldNotBeNull(); cacheItem1.Name.ShouldBe(personName); - //Remove + //Remove await personCache.RemoveAsync(cacheKey); @@ -261,7 +263,7 @@ namespace Volo.Abp.Caching cacheItem.ShouldNotBeNull(); cacheItem.Name.ShouldBe(personName); - //Remove + //Remove await personCache.RemoveAsync(cacheKey); //Get (not exists since removed) @@ -299,7 +301,7 @@ namespace Volo.Abp.Caching cacheItem2.ShouldNotBeNull(); cacheItem2.Name.ShouldBe(personName); - //Remove + //Remove await personCache.RemoveAsync(cache1Key); await personCache.RemoveAsync(cache2Key); @@ -310,6 +312,377 @@ namespace Volo.Abp.Caching cacheItem2.ShouldBeNull(); } + [Fact] + public async Task Cache_Should_Only_Available_In_Uow_For_GetAsync() + { + const string key = "testkey"; + + using (var uow = GetRequiredService().Begin()) + { + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldBeNull(); + + await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + uow.OnCompleted(async () => + { + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + }); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Cache_Should_Rollback_With_Uow_For_GetAsync() + { + const string key = "testkey"; + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + using (var uow = GetRequiredService().Begin()) + { + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldBeNull(); + + await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + [Fact] + public async Task Cache_Should_Only_Available_In_Uow_For_GetOrAddAsync() + { + const string key = "testkey"; + + using (var uow = GetRequiredService().Begin()) + { + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetOrAddAsync(key, () => Task.FromResult(new PersonCacheItem("john")), considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + uow.OnCompleted(async () => + { + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + }); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Cache_Should_Rollback_With_Uow_For_GetOrAddAsync() + { + const string key = "testkey"; + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + using (var uow = GetRequiredService().Begin()) + { + cacheValue = await personCache.GetOrAddAsync(key, () => Task.FromResult(new PersonCacheItem("john")), considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + [Fact] + public async Task Cache_Should_Only_Available_In_Uow_For_SetAsync() + { + const string key = "testkey"; + + using (var uow = GetRequiredService().Begin()) + { + var personCache = GetRequiredService>(); + + await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); + + var cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + uow.OnCompleted(async () => + { + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + }); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Cache_Should_Rollback_With_Uow_For_SetAsync() + { + const string key = "testkey"; + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + using (var uow = GetRequiredService().Begin()) + { + await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + [Fact] + public async Task Cache_Should_Only_Available_In_Uow_For_RemoveAsync() + { + const string key = "testkey"; + + using (var uow = GetRequiredService().Begin()) + { + var personCache = GetRequiredService>(); + + await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); + + var cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + await personCache.RemoveAsync(key, considerUow: true); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldBeNull(); + + uow.OnCompleted(async () => + { + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + }); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Cache_Should_Rollback_With_Uow_For_RemoveAsync() + { + const string key = "testkey"; + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + + using (var uow = GetRequiredService().Begin()) + { + await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldNotBeNull(); + cacheValue.Name.ShouldBe("john"); + + await personCache.RemoveAsync(key, considerUow: true); + + cacheValue = await personCache.GetAsync(key, considerUow: true); + cacheValue.ShouldBeNull(); + } + + cacheValue = await personCache.GetAsync(key, considerUow: false); + cacheValue.ShouldBeNull(); + } + + [Fact] + public async Task Cache_Should_Only_Available_In_Uow_For_GetManyAsync() + { + var testkey = "testkey"; + var testkey2 = "testkey2"; + var testKeys = new[] {testkey, testkey2}; + + using (var uow = GetRequiredService().Begin()) + { + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetManyAsync(testKeys, considerUow: true); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + + await personCache.SetManyAsync(new List> + { + new KeyValuePair(testkey, new PersonCacheItem("john")), + new KeyValuePair(testkey2, new PersonCacheItem("jack")) + }, considerUow: true); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: true); + cacheValue.Where(x => x.Value != null).ShouldNotBeEmpty(); + cacheValue.ShouldContain(x => x.Value.Name == "john" || x.Value.Name == "jack"); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + + uow.OnCompleted(async () => + { + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.ShouldNotBeEmpty(); + cacheValue.ShouldContain(x => x.Value.Name == "john" || x.Value.Name == "jack"); + }); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Cache_Should_Rollback_With_Uow_For_GetManyAsync() + { + var testkey = "testkey"; + var testkey2 = "testkey2"; + var testKeys = new[] {testkey, testkey2}; + + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + + using (var uow = GetRequiredService().Begin()) + { + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: true); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + + await personCache.SetManyAsync(new List> + { + new KeyValuePair(testkey, new PersonCacheItem("john")), + new KeyValuePair(testkey2, new PersonCacheItem("jack")) + }, considerUow: true); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: true); + cacheValue.Where(x => x.Value != null).ShouldNotBeEmpty(); + cacheValue.ShouldContain(x => x.Value.Name == "john" || x.Value.Name == "jack"); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + } + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + } + + + [Fact] + public async Task Cache_Should_Only_Available_In_Uow_For_SetManyAsync() + { + var testkey = "testkey"; + var testkey2 = "testkey2"; + var testKeys = new[] {testkey, testkey2}; + + using (var uow = GetRequiredService().Begin()) + { + var personCache = GetRequiredService>(); + + await personCache.SetManyAsync(new List> + { + new KeyValuePair(testkey, new PersonCacheItem("john")), + new KeyValuePair(testkey, new PersonCacheItem("jack")) + }, considerUow: true); + + var cacheValue = await personCache.GetManyAsync(testKeys, considerUow: true); + cacheValue.Where(x => x.Value != null).ShouldNotBeEmpty(); + cacheValue.ShouldContain(x => x.Value.Name == "john" || x.Value.Name == "jack"); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + + uow.OnCompleted(async () => + { + var cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldNotBeEmpty(); + cacheValue.ShouldContain(x => x.Value.Name == "john" || x.Value.Name == "jack"); + }); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Cache_Should_Rollback_With_Uow_For_SetManyAsync() + { + var testkey = "testkey"; + var testkey2 = "testkey2"; + var testKeys = new[] {testkey, testkey2}; + + var personCache = GetRequiredService>(); + + var cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + + using (var uow = GetRequiredService().Begin()) + { + await personCache.SetManyAsync(new List> + { + new KeyValuePair(testkey, new PersonCacheItem("john")), + new KeyValuePair(testkey, new PersonCacheItem("jack")) + }, considerUow: true); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: true); + cacheValue.Where(x => x.Value != null).ShouldNotBeEmpty(); + cacheValue.ShouldContain(x => x.Value.Name == "john" || x.Value.Name == "jack"); + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + } + + cacheValue = await personCache.GetManyAsync(testKeys, considerUow: false); + cacheValue.Where(x => x.Value != null).ShouldBeEmpty(); + } + + [Fact] public async Task Should_Set_And_Get_Multiple_Items_Async() { @@ -317,7 +690,7 @@ namespace Volo.Abp.Caching await personCache.SetManyAsync(new[] { - new KeyValuePair("john", new PersonCacheItem("John Nash")), + new KeyValuePair("john", new PersonCacheItem("John Nash")), new KeyValuePair("thomas", new PersonCacheItem("Thomas Moore")) }); @@ -327,7 +700,7 @@ namespace Volo.Abp.Caching "thomas", "baris" //doesn't exist }); - + cacheItems.Length.ShouldBe(3); cacheItems[0].Key.ShouldBe("john"); cacheItems[0].Value.Name.ShouldBe("John Nash"); @@ -335,9 +708,9 @@ namespace Volo.Abp.Caching cacheItems[1].Value.Name.ShouldBe("Thomas Moore"); cacheItems[2].Key.ShouldBe("baris"); cacheItems[2].Value.ShouldBeNull(); - + (await personCache.GetAsync("john")).Name.ShouldBe("John Nash"); (await personCache.GetAsync("baris")).ShouldBeNull(); } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/TestMemoryDistributedCache.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/TestMemoryDistributedCache.cs new file mode 100644 index 0000000000..69589d97cd --- /dev/null +++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/TestMemoryDistributedCache.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Caching +{ + [DisableConventionalRegistration] + public class TestMemoryDistributedCache : MemoryDistributedCache, ICacheSupportsMultipleItems + { + public TestMemoryDistributedCache(IOptions optionsAccessor) + : base(optionsAccessor) + { + } + + public TestMemoryDistributedCache(IOptions optionsAccessor, ILoggerFactory loggerFactory) + : base(optionsAccessor, loggerFactory) + { + } + + public byte[][] GetMany(IEnumerable keys) + { + var values = new List(); + foreach (var key in keys) + { + values.Add(Get(key)); + } + return values.ToArray(); + } + + public async Task GetManyAsync(IEnumerable keys, CancellationToken token = default) + { + var values = new List(); + foreach (var key in keys) + { + values.Add(await GetAsync(key, token)); + } + return values.ToArray(); + } + + public void SetMany(IEnumerable> items, DistributedCacheEntryOptions options) + { + foreach (var item in items) + { + Set(item.Key, item.Value, options); + } + } + + public async Task SetManyAsync(IEnumerable> items, DistributedCacheEntryOptions options, CancellationToken token = default) + { + foreach (var item in items) + { + await SetAsync(item.Key, item.Value, options, token); + } + } + } +} diff --git a/framework/test/Volo.Abp.Core.Tests/System/Collections/Generic/AbpListExtensions_Tests.cs b/framework/test/Volo.Abp.Core.Tests/System/Collections/Generic/AbpListExtensions_Tests.cs index 9c8926f38a..618b8769dc 100644 --- a/framework/test/Volo.Abp.Core.Tests/System/Collections/Generic/AbpListExtensions_Tests.cs +++ b/framework/test/Volo.Abp.Core.Tests/System/Collections/Generic/AbpListExtensions_Tests.cs @@ -7,6 +7,20 @@ namespace System.Collections.Generic { public class AbpListExtensions_Tests { + [Fact] + public void InsertRange() + { + var list = Enumerable.Range(1, 3).ToList(); + list.InsertRange(1, new[] {7, 8, 9}); + + list[0].ShouldBe(1); + list[1].ShouldBe(7); + list[2].ShouldBe(8); + list[3].ShouldBe(9); + list[4].ShouldBe(2); + list[5].ShouldBe(3); + } + [Fact] public void InsertAfter() { @@ -191,7 +205,7 @@ namespace System.Collections.Generic { var list = RandomHelper .GenerateRandomizedList(new char[] {'A', 'B', 'C', 'D', 'E', 'F', 'G'}); - + list = list.SortByDependencies(c => dependencies[c]); foreach (var dependency in dependencies) @@ -204,4 +218,4 @@ namespace System.Collections.Generic } } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.Core.Tests/System/StringExtensions_Tests.cs b/framework/test/Volo.Abp.Core.Tests/System/StringExtensions_Tests.cs index 23d961dfa7..69b02d07c1 100644 --- a/framework/test/Volo.Abp.Core.Tests/System/StringExtensions_Tests.cs +++ b/framework/test/Volo.Abp.Core.Tests/System/StringExtensions_Tests.cs @@ -83,6 +83,16 @@ namespace System "ThisIsSampleText".ToKebabCase().ShouldBe("this-is-sample-text"); } + [Fact] + public void ToSnakeCase_Test() + { + (null as string).ToSnakeCase().ShouldBe(null); + "helloMoon".ToSnakeCase().ShouldBe("hello_moon"); + "HelloWorld".ToSnakeCase().ShouldBe("hello_world"); + "HelloIsparta".ToSnakeCase().ShouldBe("hello_isparta"); + "ThisIsSampleText".ToSnakeCase().ShouldBe("this_is_sample_text"); + } + [Fact] public void ToSentenceCase_Test() { @@ -247,4 +257,4 @@ namespace System _cultureScope.Dispose(); } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs b/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs index 067f4b6a01..57b8054784 100644 --- a/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs +++ b/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs @@ -280,6 +280,12 @@ namespace Volo.Abp.Domain.Repositories { throw new NotImplementedException(); } + + public override Task> GetPagedListAsync(int skipCount, int maxResultCount, string sorting, bool includeDetails = false, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } } public class MyTestDefaultRepository : MyTestDefaultRepository, IRepository diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/ExtraProperties_Tests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/ExtraProperties_Tests.cs index 557116d158..f8edd1f5f1 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/ExtraProperties_Tests.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/ExtraProperties_Tests.cs @@ -1,6 +1,10 @@ -using System.Threading.Tasks; +using System; +using System.Linq; +using System.Threading.Tasks; using Shouldly; using Volo.Abp.Data; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.TestApp.Domain; using Volo.Abp.TestApp.Testing; using Xunit; @@ -22,11 +26,30 @@ namespace Volo.Abp.EntityFrameworkCore.Domain var london = await CityRepository.FindByNameAsync("London"); london.GetProperty("PhoneCode").ShouldBe("42"); - london.ExtraProperties["PhoneCode"] = "53"; + london.ExtraProperties["PhoneCode"] = 123456; + london.ExtraProperties["Rank"] = "88"; + london.ExtraProperties["ZipCode"] = null; await CityRepository.UpdateAsync(london); var london2 = await CityRepository.FindByNameAsync("London"); - london2.GetProperty("PhoneCode").ShouldBe("53"); + london2.GetProperty("PhoneCode").ShouldBe("123456"); + london2.GetProperty("Rank").ShouldBe(88); + london2.GetProperty("ZipCode").ShouldBe(null); + } + + + [Fact] + public async Task An_Extra_Property_Configured_As_Extension2() + { + await WithUnitOfWorkAsync(() => + { + var entityEntry = CityRepository.GetDbContext().Attach(new City(Guid.NewGuid(), "NewYork")); + var indexes = entityEntry.Metadata.GetIndexes().ToList(); + indexes.ShouldNotBeEmpty(); + indexes.ShouldContain(x => x.IsUnique); + return Task.CompletedTask; + }); + } } } diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/TestEntityExtensionConfigurator.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/TestEntityExtensionConfigurator.cs index 3d415d79e6..dd723ffb55 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/TestEntityExtensionConfigurator.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/TestEntityExtensionConfigurator.cs @@ -15,7 +15,15 @@ namespace Volo.Abp.EntityFrameworkCore.Domain ObjectExtensionManager.Instance .MapEfCoreProperty( "PhoneCode", - p => p.HasMaxLength(8) + (e, p) => + { + e.HasIndex(p.Metadata.Name).IsUnique(); + p.HasMaxLength(8); + } + ).MapEfCoreProperty( + "ZipCode" + ).MapEfCoreProperty( + "Rank" ); }); } diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/Repository_Basic_Tests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/Repository_Basic_Tests.cs index bfa6b8532d..3d5d7b656b 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/Repository_Basic_Tests.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/Repository_Basic_Tests.cs @@ -1,9 +1,31 @@ -using Volo.Abp.TestApp.Testing; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Shouldly; +using Volo.Abp.TestApp.Testing; +using Xunit; namespace Volo.Abp.EntityFrameworkCore.Repositories { public class Repository_Basic_Tests : Repository_Basic_Tests { + [Fact] + public async Task EFCore_QueryableExtension_ToListAsync() + { + await WithUnitOfWorkAsync(async () => + { + var persons = await PersonRepository.ToListAsync(); + persons.Count.ShouldBeGreaterThan(0); + }); + } + [Fact] + public async Task EFCore_QueryableExtension_CountAsync() + { + await WithUnitOfWorkAsync(async () => + { + var count = await PersonRepository.CountAsync(); + count.ShouldBeGreaterThan(0); + }); + } } } diff --git a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus_Test.cs b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus_Test.cs index 47649ad3c3..d3371f383a 100644 --- a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus_Test.cs +++ b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus_Test.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.MultiTenancy; using Xunit; namespace Volo.Abp.EventBus.Distributed @@ -17,5 +20,29 @@ namespace Volo.Abp.EventBus.Distributed Assert.Equal(3, MySimpleDistributedTransientEventHandler.HandleCount); Assert.Equal(3, MySimpleDistributedTransientEventHandler.DisposeCount); } + + [Fact] + public async Task Should_Change_TenantId_If_EventData_Is_MultiTenant() + { + var tenantId = Guid.NewGuid(); + + DistributedEventBus.Subscribe(GetRequiredService()); + + await DistributedEventBus.PublishAsync(new MySimpleEventData(3, tenantId)); + + Assert.Equal(tenantId, MySimpleDistributedSingleInstanceEventHandler.TenantId); + } + + [Fact] + public async Task Should_Change_TenantId_If_Generic_EventData_Is_MultiTenant() + { + var tenantId = Guid.NewGuid(); + + DistributedEventBus.Subscribe>(GetRequiredService()); + + await DistributedEventBus.PublishAsync(new MySimpleEventData(3, tenantId)); + + Assert.Equal(tenantId, MySimpleDistributedSingleInstanceEventHandler.TenantId); + } } } diff --git a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/MySimpleDistributedSingleInstanceEventHandler.cs b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/MySimpleDistributedSingleInstanceEventHandler.cs new file mode 100644 index 0000000000..53fe8e347b --- /dev/null +++ b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/MySimpleDistributedSingleInstanceEventHandler.cs @@ -0,0 +1,32 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.EventBus.Distributed +{ + public class MySimpleDistributedSingleInstanceEventHandler : IDistributedEventHandler, IDistributedEventHandler>, ITransientDependency + { + private readonly ICurrentTenant _currentTenant; + + public MySimpleDistributedSingleInstanceEventHandler(ICurrentTenant currentTenant) + { + _currentTenant = currentTenant; + } + + public static Guid? TenantId { get; set; } + + public Task HandleEventAsync(MySimpleEventData eventData) + { + TenantId = _currentTenant.Id; + return Task.CompletedTask; + } + + public Task HandleEventAsync(EntityCreatedEto eventData) + { + TenantId = _currentTenant.Id; + return Task.CompletedTask; + } + } +} diff --git a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/EventBus_MultiTenancy_Test.cs b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/EventBus_MultiTenancy_Test.cs new file mode 100644 index 0000000000..62dcb33b42 --- /dev/null +++ b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/EventBus_MultiTenancy_Test.cs @@ -0,0 +1,64 @@ +using System; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Domain.Entities.Events; +using Volo.Abp.MultiTenancy; +using Xunit; + +namespace Volo.Abp.EventBus.Local +{ + public class EventBus_MultiTenancy_Test : EventBusTestBase + { + [Fact] + public async Task Should_Change_TenantId_If_EventData_Is_MultiTenant() + { + var tenantId = Guid.NewGuid(); + var handler = new MyEventHandler(GetRequiredService()); + + LocalEventBus.Subscribe>(handler); + + await LocalEventBus.PublishAsync(new EntityCreatedEventData(new MyEntity(tenantId))); + + handler.TenantId.ShouldBe(tenantId); + } + + public class MyEntity : Entity, IMultiTenant + { + public override object[] GetKeys() + { + return new object[0]; + } + + public MyEntity() + { + + } + + public MyEntity(Guid? tenantId) + { + TenantId = tenantId; + } + + public Guid? TenantId { get; } + } + + public class MyEventHandler : ILocalEventHandler> + { + private readonly ICurrentTenant _currentTenant; + + public MyEventHandler(ICurrentTenant currentTenant) + { + _currentTenant = currentTenant; + } + + public Guid? TenantId { get; set; } + + public Task HandleEventAsync(EntityChangedEventData eventData) + { + TenantId = _currentTenant.Id; + return Task.CompletedTask; + } + } + } +} diff --git a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/MySimpleEventData.cs b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/MySimpleEventData.cs index 08142b3355..45a3e7f237 100644 --- a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/MySimpleEventData.cs +++ b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/MySimpleEventData.cs @@ -1,12 +1,18 @@ +using System; +using Volo.Abp.MultiTenancy; + namespace Volo.Abp.EventBus { - public class MySimpleEventData + public class MySimpleEventData : IMultiTenant { public int Value { get; set; } - public MySimpleEventData(int value) + public Guid? TenantId { get; } + + public MySimpleEventData(int value, Guid? tenantId = null) { Value = value; + TenantId = tenantId; } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs index 5fa3d4ab66..e53e3cabbf 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; @@ -40,12 +41,29 @@ namespace Volo.Abp.Http.DynamicProxying [Fact] public async Task GetList() { - var people = await _peopleAppService.GetListAsync(new PagedAndSortedResultRequestDto()) - ; + var people = await _peopleAppService.GetListAsync(new PagedAndSortedResultRequestDto()); people.TotalCount.ShouldBeGreaterThan(0); people.Items.Count.ShouldBe((int) people.TotalCount); } + [Fact] + public async Task GetParams() + { + var id1 = Guid.NewGuid(); + var id2 = Guid.NewGuid(); + + var @params = await _peopleAppService.GetParams(new List + { + id1, + id2 + }, new[] {"name1", "name2"}); + + @params.ShouldContain(id1.ToString("N")); + @params.ShouldContain(id2.ToString("N")); + @params.ShouldContain("name1"); + @params.ShouldContain("name2"); + } + [Fact] public async Task Delete() { @@ -151,4 +169,4 @@ namespace Volo.Abp.Http.DynamicProxying result.Inner1.Inner2.Value3.ShouldBe("value three"); } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.Ldap.Tests/Volo.Abp.Ldap.Tests.csproj b/framework/test/Volo.Abp.Ldap.Tests/Volo.Abp.Ldap.Tests.csproj index 3c1f9c96db..d2db584347 100644 --- a/framework/test/Volo.Abp.Ldap.Tests/Volo.Abp.Ldap.Tests.csproj +++ b/framework/test/Volo.Abp.Ldap.Tests/Volo.Abp.Ldap.Tests.csproj @@ -9,10 +9,8 @@ - - - + diff --git a/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/AbpLdapTestModule.cs b/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/AbpLdapTestModule.cs new file mode 100644 index 0000000000..8297655891 --- /dev/null +++ b/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/AbpLdapTestModule.cs @@ -0,0 +1,25 @@ +using Volo.Abp.Autofac; +using Volo.Abp.Modularity; + +namespace Volo.Abp.Ldap +{ + [DependsOn( + typeof(AbpAutofacModule), + typeof(AbpLdapModule), + typeof(AbpTestBaseModule) + )] + public class AbpLdapTestModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.ServerHost = "192.168.0.3"; + options.ServerPort = 389; + options.BaseDc = "dc=abp,dc=io"; + options.UserName = "admin"; + options.Password = "123qwe"; + }); + } + } +} diff --git a/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/Authenticate_Tests.cs b/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/Authenticate_Tests.cs deleted file mode 100644 index bf11262a99..0000000000 --- a/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/Authenticate_Tests.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using Shouldly; -using Volo.Abp.Modularity; -using Volo.Abp.Testing; -using Xunit; - -namespace Volo.Abp.Ldap -{ - - public class Authenticate_Tests : AbpIntegratedTest - { - protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) - { - options.UseAutofac(); - } - - private readonly ILdapManager _ldapManager; - private readonly LdapTestData _testData; - - public Authenticate_Tests() - { - // ReSharper disable once VirtualMemberCallInConstructor - _testData = GetRequiredService(); - _ldapManager = GetRequiredService(); - } - - [Fact(Skip = "need environment AD ")] - public void Authenticate() - { - var result = _ldapManager.Authenticate(_testData.AdministratorDomainName, _testData.AdministratorPassword); - - result.ShouldBeTrue(); - } - - [Fact(Skip = "need environment AD ")] - public void Authenticate_With_Wrong_Password() - { - var result = _ldapManager.Authenticate("NonExistentNameA", "PasswordA"); - - result.ShouldBeFalse(); - } - - [DependsOn(typeof(AbpLdapModule))] - public class TestModule : AbpModule - { - public override void ConfigureServices(ServiceConfigurationContext context) - { - // not use ssl - // "LDAP": { - // "ServerHost": "192.168.101.54", - // "ServerPort": 389, - // "UseSSL": false - // } - - // use ssl - // "LDAP": { - // "ServerHost": "192.168.101.54", - // "ServerPort": 636, - // "UseSSL": true - // } - Configure(settings => - { - settings.ServerHost = "192.168.101.54"; - settings.ServerPort = 636; - settings.UseSsl = true; - }); - } - } - } - -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapHelps_Tests.cs b/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapHelps_Tests.cs deleted file mode 100644 index 06d6834992..0000000000 --- a/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapHelps_Tests.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System.Collections.Generic; -using Shouldly; -using Xunit; - -namespace Volo.Abp.Ldap -{ - public class LdapHelps_Tests - { - - [Fact] - public void BuildCondition_With_Value() - { - // act - var res = LdapHelps.BuildCondition("objectClass", "testNameA"); - - // assert - res.ShouldBe("(objectClass=testNameA)"); - } - - [Fact] - public void BuildCondition_With_Null_Value() - { - // act - var res = LdapHelps.BuildCondition("objectClass", null); - - // assert - res.ShouldBeEmpty(); - } - - [Fact] - public void BuildCondition_With_Empty_Value() - { - // act - var res = LdapHelps.BuildCondition("objectClass", ""); - - // assert - res.ShouldBeEmpty(); - } - - [Fact] - public void BuildCondition_With_WhiteSpace_Value() - { - // act - var res = LdapHelps.BuildCondition("objectClass", " "); - - // assert - res.ShouldBeEmpty(); - } - - [Fact] - public void BuildFilter_With_Null_Condition() - { - // act - var res = LdapHelps.BuildFilter(null); - - // assert - res.ShouldBe("(&(objectClass=*))"); - } - - [Fact] - public void BuildFilter_With_Empty_Condition() - { - // act - var res = LdapHelps.BuildFilter(new Dictionary()); - - // assert - res.ShouldBe("(&(objectClass=*))"); - } - - [Fact] - public void BuildFilter_With_Condition() - { - // act - var conditions = new Dictionary - { - {"objectClass", "testClassA"}, {"objectCategory", "testCategoryA"}, {"name", null} - }; - var res = LdapHelps.BuildFilter(conditions); - - // assert - res.ShouldBe("(&(objectClass=testClassA)(objectCategory=testCategoryA))"); - } - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapManager_Tests.cs b/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapManager_Tests.cs index 3e83d26543..8da9a9303d 100644 --- a/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapManager_Tests.cs +++ b/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapManager_Tests.cs @@ -1,246 +1,28 @@ -using System; -using Shouldly; -using Volo.Abp.Modularity; +using Shouldly; using Volo.Abp.Testing; using Xunit; namespace Volo.Abp.Ldap { - - public class LdapManager_Tests : AbpIntegratedTest + public class LdapManager_Tests : AbpIntegratedTest { - protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) - { - options.UseAutofac(); - } - private readonly ILdapManager _ldapManager; - private readonly LdapTestData _testData; public LdapManager_Tests() { - // ReSharper disable once VirtualMemberCallInConstructor - _testData = GetRequiredService(); _ldapManager = GetRequiredService(); } - [Fact(Skip = "need environment AD ")] - public void GetOrganizations_With_Empty_Condition() - { - var result = _ldapManager.GetOrganizations(); - - result.ShouldNotBeNull(); - result.ShouldContain(e => e.Name == _testData.DomainControllersName); - result.ShouldContain(e => e.DistinguishedName == _testData.DomainControllersDistinguishedName); - } - - [Fact(Skip = "need environment AD ")] - public void GetOrganizations_With_Name() - { - var result = _ldapManager.GetOrganizations(_testData.DomainControllersName); - - result.ShouldNotBeNull(); - result.ShouldHaveSingleItem(); - result.ShouldContain(e => e.Name == _testData.DomainControllersName); - result.ShouldContain(e => e.DistinguishedName == _testData.DomainControllersDistinguishedName); - } - - [Fact(Skip = "need environment AD ")] - public void GetOrganizations_With_Non_Existent_Name() - { - var result = _ldapManager.GetOrganizations("NonExistentNameA"); - - result.ShouldNotBeNull(); - result.ShouldBeEmpty(); - } - - [Fact(Skip = "need environment AD ")] - public void GetOrganization() - { - var result = _ldapManager.GetOrganization(_testData.DomainControllersDistinguishedName); - - result.ShouldNotBeNull(); - result.Name.ShouldBe(_testData.DomainControllersName); - } - - [Fact(Skip = "need environment AD ")] - public void GetOrganization_With_Non_Existent_DistinguishedName() - { - var result = _ldapManager.GetOrganization("NonExistentNameA"); - - result.ShouldBeNull(); - } - - [Fact(Skip = "need environment AD ")] - public void GetUsers_With_Empty_Condition() - { - var result = _ldapManager.GetUsers(); - - result.ShouldNotBeNull(); - result.ShouldContain(e => e.Name == _testData.AdministratorName); - } - - [Fact(Skip = "need environment AD ")] - public void GetUsers_With_Name() - { - var result = _ldapManager.GetUsers(name: _testData.AdministratorName); - - result.ShouldNotBeNull(); - result.ShouldContain(e => e.Name == _testData.AdministratorName); - } - - [Fact(Skip = "need environment AD ")] - public void GetUsers_With_Non_Existent_Name() - { - var result = _ldapManager.GetUsers(name: "NonExistentNameA"); - - result.ShouldNotBeNull(); - result.ShouldBeEmpty(); - } - - [Fact(Skip = "need environment AD ")] - public void GetUsers_With_CommonName() - { - var result = _ldapManager.GetUsers(commonName: _testData.AdministratorName); - - result.ShouldNotBeNull(); - result.ShouldContain(e => e.Name == _testData.AdministratorName); - } - - [Fact(Skip = "need environment AD ")] - public void GetUsers_With_Non_Existent_CommonName() - { - var result = _ldapManager.GetUsers(commonName: "NonExistentNameA"); - - result.ShouldNotBeNull(); - result.ShouldBeEmpty(); - } - - [Fact(Skip = "need environment AD ")] - public void GetUsers_With_DisplayName() - { - var result = _ldapManager.GetUsers(displayName: _testData.AdministratorName); - - result.ShouldNotBeNull(); - // the administrator in AD. not have display name by default. - result.ShouldBeEmpty(); - } - - [Fact(Skip = "need environment AD ")] - public void GetUser() - { - var result = _ldapManager.GetUser(_testData.AdministratorDistinguishedName); - - result.ShouldNotBeNull(); - result.Name.ShouldBe(_testData.AdministratorName); - } - - [Fact(Skip = "need environment AD ")] - public void GetUser_With_Non_Existent_DistinguishedName() + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) { - var result = _ldapManager.GetOrganization("NonExistentNameA"); - - result.ShouldBeNull(); + options.UseAutofac(); } - [Fact(Skip = "need environment AD ")] + [Fact(Skip = "Required Ldap environment")] public void Authenticate() { - var result = _ldapManager.Authenticate(_testData.AdministratorDomainName, _testData.AdministratorPassword); - - result.ShouldBeTrue(); - } - - [Fact(Skip = "need environment AD ")] - public void Authenticate_With_Wrong_Password() - { - var result = _ldapManager.Authenticate("NonExistentNameA", "PasswordA"); - - result.ShouldBeFalse(); - } - - [Fact(Skip = "need environment AD ")] - public void AddSubOrganization() - { - var parentOrganization = _ldapManager.GetOrganization(_testData.DomainControllersDistinguishedName); - var randomName = $"Test_{DateTime.Now.Ticks}"; - - _ldapManager.AddSubOrganization(randomName, parentOrganization); - - var result = _ldapManager.GetOrganizations(randomName); - result.ShouldNotBeNull(); - result.ShouldContain(e => e.Name == randomName); - } - - [Fact(Skip = "need environment AD ")] - public void AddSubOrganization_With_DistinguishedName() - { - var randomName = $"Test_{DateTime.Now.Ticks}"; - - _ldapManager.AddSubOrganization(randomName, _testData.DomainControllersDistinguishedName); - - var result = _ldapManager.GetOrganizations(randomName); - result.ShouldNotBeNull(); - result.ShouldContain(e => e.Name == randomName); - } - - [Fact(Skip = "need environment AD ")] - public void AddOrganizationUser() - { - var parentOrganization = _ldapManager.GetOrganization(_testData.DomainControllersDistinguishedName); - var randomName = $"Test_{DateTime.Now:yyMMddHHmmss}"; - _ldapManager.AddUserToOrganization(randomName, _testData.AdministratorPassword, parentOrganization); - - var result = _ldapManager.GetUsers(randomName); - result.ShouldNotBeNull(); - result.ShouldContain(e=>e.Name == randomName); - } - - [DependsOn(typeof(AbpLdapModule))] - public class TestModule : AbpModule - { - public override void ConfigureServices(ServiceConfigurationContext context) - { - // not use ssl - // "LDAP": { - // "ServerHost": "192.168.101.54", - // "ServerPort": 389, - // "UseSSL": false, - // "Credentials": { - // "DomainUserName": "administrator@yourdomain.com.cn", - // "Password": "yH.20190528" - // }, - // "SearchBase": "CN=Users,DC=yourdomain,DC=com,DC=cn", - // "DomainName": "yourdomain.com.cn", - // "DomainDistinguishedName": "DC=yourdomain,DC=com,DC=cn" - // } - - // use ssl - // "LDAP": { - // "ServerHost": "192.168.101.54", - // "ServerPort": 636, - // "UseSSL": true, - // "Credentials": { - // "DomainUserName": "administrator@yourdomain.com.cn", - // "Password": "yH.20190528" - // }, - // "SearchBase": "CN=Users,DC=yourdomain,DC=com,DC=cn", - // "DomainName": "yourdomain.com.cn", - // "DomainDistinguishedName": "DC=yourdomain,DC=com,DC=cn" - // } - Configure(settings => - { - settings.ServerHost = "192.168.101.54"; - settings.ServerPort = 636; - settings.UseSsl = true; - settings.Credentials.DomainUserName = "administrator@yourdomain.com.cn"; - settings.Credentials.Password = "yH.20190528"; - settings.SearchBase = "DC=yourdomain,DC=com,DC=cn"; - settings.DomainName = "yourdomain.com.cn"; - settings.DomainDistinguishedName = "DC=yourdomain,DC=com,DC=cn"; - }); - } + _ldapManager.Authenticate("cn=abp,dc=abp,dc=io", "123qwe").ShouldBe(true); + _ldapManager.Authenticate("NoExists", "123qwe").ShouldBe(false); } } - -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapOptions_Tests.cs b/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapOptions_Tests.cs new file mode 100644 index 0000000000..8ec7ef94c6 --- /dev/null +++ b/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapOptions_Tests.cs @@ -0,0 +1,21 @@ +using Microsoft.Extensions.Options; +using Shouldly; +using Volo.Abp.Testing; +using Xunit; + +namespace Volo.Abp.Ldap +{ + public class LdapOptions_Tests : AbpIntegratedTest + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + + [Fact] + public void Should_Resolve_AbpAbpLdapOptionsFactory() + { + GetRequiredService>().ShouldBeOfType(typeof(AbpAbpLdapOptionsFactory)); + } + } +} diff --git a/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapTestData.cs b/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapTestData.cs deleted file mode 100644 index 8224686daa..0000000000 --- a/framework/test/Volo.Abp.Ldap.Tests/Volo/Abp/Ldap/LdapTestData.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Ldap -{ - public class LdapTestData : ISingletonDependency - { - public string AdministratorName { get; } = "Administrator"; - public string AdministratorPassword { get; } = "yH.20190528"; - public string AdministratorDistinguishedName { get; } = "CN=Administrator,CN=Users,DC=yourdomain,DC=com,DC=cn"; - public string AdministratorDomainName { get; } = "Administrator@yourdomain.com.cn"; - - public string DomainControllersName = "Domain Controllers"; - public string DomainControllersDistinguishedName = "OU=Domain Controllers,DC=yourdomain,DC=com,DC=cn"; - - public string RootDistinguishedName { get; } = "DC=yourdomain,DC=com,DC=cn"; - - public string Organization001Name { get; } = "Test_A"; - - public string Test001Name { get; } = "test001"; - public string Test001Password { get; } = "yH.20190528"; - public string Test001Email { get; } = "test001@yourdomain.com.cn"; - - public string Test002Name { get; } = "test002"; - public string Test002Password { get; } = "yH.20190528"; - public string Test002WrongPassword { get; } = "yH.20190529"; - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs index 71c2fafe2e..475629b4f0 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs @@ -148,7 +148,10 @@ namespace Volo.Abp.Localization _localizer["MaxLenghtErrorMessage", 42].Value.ShouldBe("El campo puede tener un máximo de '42' caracteres"); //Overriden in Source/es.json } - + using (CultureHelper.Use("de")) + { + _localizer["MaxLenghtErrorMessage", 42].Value.ShouldBe("Die Länge dieses Feldes kann maximal '42'-Zeichen betragen"); //Overriden in Source/es.json + } } [Fact] diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/nl.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/nl.json index 849d1b0df6..3bf13dda13 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/nl.json +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/nl.json @@ -1,4 +1,4 @@ -{ +{ "culture": "nl", "texts": { "USA": "Verenigde Staten van Amerika", diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/nl.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/nl.json index 5875cf7a12..825c54ffec 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/nl.json +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/nl.json @@ -1,4 +1,4 @@ -{ +{ "culture": "nl", "texts": { "ThisFieldIsRequired": "Dit veld is verplicht", diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/nl.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/nl.json index 0e0ba67ef6..804eaf76c9 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/nl.json +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/nl.json @@ -1,4 +1,4 @@ -{ +{ "culture": "nl", "texts": { "Hello {0}.": "Hallo {0}.", diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/nl.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/nl.json index 7e7354d11b..4072732d62 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/nl.json +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/nl.json @@ -1,4 +1,4 @@ -{ +{ "culture": "nl", "texts": { "SeeYou": "Tot ziens" diff --git a/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/AbpSecurityTestModule.cs b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/AbpSecurityTestModule.cs index b71a0a214a..a1ce5c56aa 100644 --- a/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/AbpSecurityTestModule.cs +++ b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/AbpSecurityTestModule.cs @@ -1,4 +1,5 @@ using Volo.Abp.Modularity; +using Volo.Abp.SecurityLog; namespace Volo.Abp.Security { @@ -8,6 +9,12 @@ namespace Volo.Abp.Security )] public class AbpSecurityTestModule : AbpModule { - + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(x => + { + x.ApplicationName = "AbpSecurityTest"; + }); + } } } diff --git a/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/SecurityLog/SecurityLogManager_Tests.cs b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/SecurityLog/SecurityLogManager_Tests.cs new file mode 100644 index 0000000000..94fb6bccda --- /dev/null +++ b/framework/test/Volo.Abp.Security.Tests/Volo/Abp/Security/SecurityLog/SecurityLogManager_Tests.cs @@ -0,0 +1,45 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; +using Volo.Abp.SecurityLog; +using Volo.Abp.Testing; +using Xunit; + +namespace Volo.Abp.Security.SecurityLog +{ + + public class SecurityLogManager_Tests : AbpIntegratedTest + { + private readonly ISecurityLogManager _securityLogManager; + + private ISecurityLogStore _auditingStore; + + public SecurityLogManager_Tests() + { + _securityLogManager = GetRequiredService(); + } + + protected override void AfterAddApplication(IServiceCollection services) + { + _auditingStore = Substitute.For(); + services.AddSingleton(_auditingStore); + } + + [Fact] + public async Task SaveAsync() + { + await _securityLogManager.SaveAsync(securityLog => + { + securityLog.Identity = "Test"; + securityLog.Action = "Test-Action"; + securityLog.UserName = "Test-User"; + }); + + await _auditingStore.Received().SaveAsync(Arg.Is(log => + log.ApplicationName == "AbpSecurityTest" && + log.Identity == "Test" && + log.Action == "Test-Action" && + log.UserName == "Test-User")); + } + } +} diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs index 38b0e87787..a34cd19ebb 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; @@ -10,6 +11,8 @@ namespace Volo.Abp.TestApp.Application { Task> GetPhones(Guid id, GetPersonPhonesFilter filter); + Task> GetParams(IEnumerable ids, string[] names); + Task AddPhone(Guid id, PhoneDto phoneDto); Task RemovePhone(Guid id, string number); @@ -18,4 +21,4 @@ namespace Volo.Abp.TestApp.Application Task GetWithComplexType(GetWithComplexTypeInput input); } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs index 522f8d9794..cff72c7869 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs @@ -18,18 +18,25 @@ namespace Volo.Abp.TestApp.Application { } - + public async Task> GetPhones(Guid id, GetPersonPhonesFilter filter) { var phones = (await GetEntityByIdAsync(id)).Phones .WhereIf(filter.Type.HasValue, p => p.Type == filter.Type) .ToList(); - + return new ListResultDto( ObjectMapper.Map, List>(phones) ); } + public Task> GetParams(IEnumerable ids, string[] names) + { + var @params = ids.Select(id => id.ToString("N")).ToList(); + @params.AddRange(names); + return Task.FromResult(@params.ToList()); + } + public async Task AddPhone(Guid id, PhoneDto phoneDto) { var person = await GetEntityByIdAsync(id); diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs index 312125d541..4b5f390d85 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs @@ -46,7 +46,15 @@ namespace Volo.Abp.TestApp await _cityRepository.InsertAsync(new City(Guid.NewGuid(), "Tokyo")); await _cityRepository.InsertAsync(new City(Guid.NewGuid(), "Madrid")); - await _cityRepository.InsertAsync(new City(LondonCityId, "London") { ExtraProperties = { { "Population", 10_470_000 }, { "PhoneCode", "42" } } }); + await _cityRepository.InsertAsync(new City(LondonCityId, "London") + { + ExtraProperties = + { + { "Population", 10_470_000 }, + { "PhoneCode", "42" }, + { "ZipCode", "1000" } + } + }); await _cityRepository.InsertAsync(istanbul); await _cityRepository.InsertAsync(new City(Guid.NewGuid(), "Paris")); await _cityRepository.InsertAsync(new City(Guid.NewGuid(), "Washington")); @@ -79,4 +87,4 @@ namespace Volo.Abp.TestApp await _entityWithIntPksRepository.InsertAsync(new EntityWithIntPk("Entity1")); } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Basic_Tests.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Basic_Tests.cs index 28c94b02fc..a93123c1ca 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Basic_Tests.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Basic_Tests.cs @@ -8,7 +8,7 @@ using Xunit; namespace Volo.Abp.TestApp.Testing { - public abstract class Repository_Basic_Tests : TestAppTestBase + public abstract class Repository_Basic_Tests : TestAppTestBase where TStartupModule : IAbpModule { protected readonly IRepository PersonRepository; @@ -28,6 +28,27 @@ namespace Volo.Abp.TestApp.Testing person.Phones.Count.ShouldBe(2); } + [Fact] + public async Task GetListAsync() + { + var persons = await PersonRepository.GetListAsync(); + persons.Count.ShouldBeGreaterThan(0); + } + + [Fact] + public async Task GetPagedListAsync() + { + var persons = await PersonRepository.GetPagedListAsync(0, 10, "name"); + persons.Count.ShouldBeGreaterThan(0); + } + + [Fact] + public async Task GetPagedListAsync_Should_Return_Empty() + { + var persons = await PersonRepository.GetPagedListAsync(1, 10, "name"); + persons.Count.ShouldBe(0); + } + [Fact] public async Task GetAsync_With_Predicate() { @@ -84,5 +105,15 @@ namespace Volo.Abp.TestApp.Testing var person = await PersonRepository.FindAsync(personId); person.ShouldNotBeNull(); } + + [Fact] + public async Task Insert_Should_Set_Guid_Id() + { + var person = new Person(Guid.Empty, "New Person", 35); + + await PersonRepository.InsertAsync(person); + + person.Id.ShouldNotBe(Guid.Empty); + } } } diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/ar.json b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/ar.json new file mode 100644 index 0000000000..31a75bad8b --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/ar.json @@ -0,0 +1,7 @@ +{ + "culture": "ar", + "texts": { + "HelloText": "مرحبا {0}", + "HowAreYou": "كيف حالكم ؟" + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo.Abp.VirtualFileSystem.Tests.csproj b/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo.Abp.VirtualFileSystem.Tests.csproj index bcb9dcbf74..122f54f075 100644 --- a/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo.Abp.VirtualFileSystem.Tests.csproj +++ b/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo.Abp.VirtualFileSystem.Tests.csproj @@ -16,7 +16,7 @@ - + - + diff --git a/modules/account/.prettierrc b/modules/account/.prettierrc new file mode 100644 index 0000000000..56af76bd94 --- /dev/null +++ b/modules/account/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "useTabs": false, + "tabWidth": 4 +} diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo.Abp.Account.Application.Contracts.csproj b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo.Abp.Account.Application.Contracts.csproj index 58b2266340..722a75a22c 100644 --- a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo.Abp.Account.Application.Contracts.csproj +++ b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo.Abp.Account.Application.Contracts.csproj @@ -7,6 +7,7 @@ netstandard2.0 Volo.Abp.Account.Application.Contracts Volo.Abp.Account.Application.Contracts + true @@ -19,4 +20,8 @@ + + + + diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/cs.json b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/cs.json index 120060c4f4..ba7979dc65 100644 --- a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/cs.json +++ b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/cs.json @@ -40,6 +40,9 @@ "DisplayName:Abp.Account.IsSelfRegistrationEnabled": "Je povolena automatická registrace", "Description:Abp.Account.IsSelfRegistrationEnabled": "Zda si uživatel může účet zaregistrovat sám.", "DisplayName:Abp.Account.EnableLocalLogin": "Ověření pomocí místního účtu", - "Description:Abp.Account.EnableLocalLogin": "Označuje, zda server umožní uživatelům ověřovat se pomocí místního účtu." + "Description:Abp.Account.EnableLocalLogin": "Označuje, zda server umožní uživatelům ověřovat se pomocí místního účtu.", + "LoggedOutTitle": "Odhlášeno", + "LoggedOutText": "Byli jste odhlášeni a brzy budete přesměrováni.", + "ReturnToText": "Kliknutím sem přejdete na {0}" } } diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/de.json b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/de.json index 935fff56a6..82c9ae3ba8 100644 --- a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/de.json +++ b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/de.json @@ -8,22 +8,22 @@ "RememberMe": "Angemeldet bleiben", "UseAnotherServiceToLogin": "Einen anderen Dienst zum Anmelden verwenden", "UserLockedOutMessage": "Das Benutzerkonto wurde aufgrund fehlgeschlagener Anmeldeversuche gesperrt. Bitte warten Sie eine Weile und versuchen Sie es erneut.", - "InvalidUserNameOrPassword": "Ungültiger Benutzername oder Passwort!", - "LoginIsNotAllowed": "Sie dürfen sich nicht anmelden! Sie müssen Ihre E-Mail/Telefonnummer bestätigen.", - "SelfRegistrationDisabledMessage": "Die Selbstregistrierung ist für diese Anwendung deaktiviert. Bitte wenden Sie sich an den Anwendungsadministrator, um einen neuen Benutzer zu registrieren.", - "LocalLoginDisabledMessage": "Die lokale Anmeldung ist für diese Anwendung deaktiviert.", + "InvalidUserNameOrPassword": "Ungültiger Benutzername oder Passwort!", + "LoginIsNotAllowed": "Sie dürfen sich nicht anmelden! Sie müssen Ihre E-Mail/Telefonnummer bestätigen.", + "SelfRegistrationDisabledMessage": "Die Selbstregistrierung ist für diese Anwendung deaktiviert. Bitte wenden Sie sich an den Anwendungsadministrator, um einen neuen Benutzer zu registrieren.", + "LocalLoginDisabledMessage": "Die lokale Anmeldung ist für diese Anwendung deaktiviert.", "Login": "Anmelden", "Cancel": "Abbrechen", "Register": "Registrieren", "AreYouANewUser": "Neuer Benutzer?", "AlreadyRegistered": "Bereits registriert?", - "InvalidLoginRequest": "Ungültige Login-Anfrage", - "ThereAreNoLoginSchemesConfiguredForThisClient": "Es sind keine Anmeldeschemata für diesen Client konfiguriert.", + "InvalidLoginRequest": "Ungültige Login-Anfrage", + "ThereAreNoLoginSchemesConfiguredForThisClient": "Es sind keine Anmeldeschemata für diesen Client konfiguriert.", "LogInUsingYourProviderAccount": "Melden Sie sich mit Ihrem {0}-Konto an", "DisplayName:CurrentPassword": "Aktuelles Passwort", "DisplayName:NewPassword": "Neues Passwort", - "DisplayName:NewPasswordConfirm": "Neues Passwort bestätigen", - "PasswordChangedMessage": "Ihr Passwort wurde erfolgreich geändert.", + "DisplayName:NewPasswordConfirm": "Neues Passwort bestätigen", + "PasswordChangedMessage": "Ihr Passwort wurde erfolgreich geändert.", "DisplayName:UserName": "Benutzername", "DisplayName:Email": "E-Mail", "DisplayName:Name": "Name", @@ -31,12 +31,12 @@ "DisplayName:Password": "Passwort", "DisplayName:EmailAddress": "E-Mail-Adresse", "DisplayName:PhoneNumber": "Telefonnummer", - "PersonalSettings": "Persönliche Einstellungen", - "PersonalSettingsSaved": "Persönliche Einstellungen gespeichert", - "PasswordChanged": "Passwort geändert", - "NewPasswordConfirmFailed": "Bitte bestätigen Sie das neue Passwort.", + "PersonalSettings": "Persönliche Einstellungen", + "PersonalSettingsSaved": "Persönliche Einstellungen gespeichert", + "PasswordChanged": "Passwort geändert", + "NewPasswordConfirmFailed": "Bitte bestätigen Sie das neue Passwort.", "Manage": "Verwalten", - "ManageYourProfile": "Ihr profil verwalten", + "ManageYourProfile": "Ihr Profil verwalten", "DisplayName:Abp.Account.IsSelfRegistrationEnabled": "Ist die Selbstregistrierung aktiviert", "Description:Abp.Account.IsSelfRegistrationEnabled": "Gibt an, ob ein Benutzer das Konto selbst registrieren kann.", "DisplayName:Abp.Account.EnableLocalLogin": "Authentifizierung mit einem lokalen Konto", diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json index f0dbdaadb6..2940fff63b 100644 --- a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json +++ b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json @@ -43,6 +43,7 @@ "Description:Abp.Account.EnableLocalLogin": "Indicates if the server will allow users to authenticate with a local account.", "LoggedOutTitle": "Signed Out", "LoggedOutText": "You have been signed out and you will be redirected soon.", - "ReturnToText": "Click here to redirect to {0}" + "ReturnToText": "Click here to redirect to {0}", + "OrLoginWith": "Or login with;" } } diff --git a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/zh-Hans.json b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/zh-Hans.json index c02ff8669b..49a4604372 100644 --- a/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/zh-Hans.json +++ b/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/zh-Hans.json @@ -40,6 +40,9 @@ "DisplayName:Abp.Account.IsSelfRegistrationEnabled": "启用自行注册", "Description:Abp.Account.IsSelfRegistrationEnabled": "是否允许用户自行注册帐户.", "DisplayName:Abp.Account.EnableLocalLogin": "使用本地帐户进行身份验证", - "Description:Abp.Account.EnableLocalLogin": "服务器是否将允许用户使用本地帐户进行身份验证。" + "Description:Abp.Account.EnableLocalLogin": "服务器是否将允许用户使用本地帐户进行身份验证。", + "LoggedOutTitle": "注销", + "LoggedOutText": "你已成功注销并将马上返回.", + "ReturnToText": "点击此处返回到 {0}" } } diff --git a/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.csproj b/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.csproj index a11fb89e1a..bdcf0c9e8d 100644 --- a/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.csproj +++ b/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.csproj @@ -7,6 +7,7 @@ netstandard2.0 Volo.Abp.Account.Application Volo.Abp.Account.Application + true @@ -17,4 +18,8 @@ + + + + diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/AbpAccountWebIdentityServerModule.cs b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/AbpAccountWebIdentityServerModule.cs index 5260554ab5..273659fd69 100644 --- a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/AbpAccountWebIdentityServerModule.cs +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/AbpAccountWebIdentityServerModule.cs @@ -30,7 +30,7 @@ namespace Volo.Abp.Account.Web { Configure(options => { - options.FileSets.AddEmbedded("Volo.Abp.Account.Web"); + options.FileSets.AddEmbedded(); }); //TODO: Try to reuse from AbpIdentityAspNetCoreModule 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 60f55497da..297e54cd69 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 @@ -14,7 +14,10 @@ using System.Security.Principal; using System.Threading.Tasks; using Volo.Abp.Account.Settings; using Volo.Abp.DependencyInjection; +using Volo.Abp.Identity; +using Volo.Abp.Identity.AspNetCore; using Volo.Abp.MultiTenancy; +using Volo.Abp.SecurityLog; using Volo.Abp.Settings; using Volo.Abp.Uow; @@ -50,6 +53,8 @@ namespace Volo.Abp.Account.Web.Pages.Account if (context != null) { + ShowCancelButton = true; + LoginInput.UserNameOrEmailAddress = context.LoginHint; //TODO: Reference AspNetCore MultiTenancy module and use options to get the tenant key! @@ -127,6 +132,13 @@ namespace Volo.Abp.Account.Web.Pages.Account true ); + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() + { + Identity = IdentitySecurityLogIdentityConsts.Identity, + Action = result.ToIdentitySecurityLogAction(), + UserName = LoginInput.UserNameOrEmailAddress + }); + if (result.RequiresTwoFactor) { return RedirectToPage("./SendSecurityCode", new diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLogoutModel.cs b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLogoutModel.cs index 43857cc111..bec2ede369 100644 --- a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLogoutModel.cs +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLogoutModel.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Volo.Abp.DependencyInjection; +using Volo.Abp.Identity; namespace Volo.Abp.Account.Web.Pages.Account { @@ -19,6 +20,12 @@ namespace Volo.Abp.Account.Web.Pages.Account public override async Task OnGetAsync() { + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() + { + Identity = IdentitySecurityLogIdentityConsts.Identity, + Action = IdentitySecurityLogActionConsts.Logout + }); + await SignInManager.SignOutAsync(); var logoutId = Request.Query["logoutId"].ToString(); diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj index ca27f60695..566d40b3f9 100644 --- a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj @@ -12,6 +12,7 @@ false false false + true Volo.Abp.Account.Web Library @@ -32,4 +33,8 @@ + + + + diff --git a/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs b/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs index 6df4e43c07..10886e4c17 100644 --- a/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs +++ b/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs @@ -37,7 +37,7 @@ namespace Volo.Abp.Account.Web { Configure(options => { - options.FileSets.AddEmbedded("Volo.Abp.Account.Web"); + options.FileSets.AddEmbedded(); }); Configure(options => diff --git a/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs b/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs index 64fbdd40d1..575853f410 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs @@ -7,6 +7,7 @@ using Volo.Abp.Account.Settings; using Volo.Abp.Account.Web.Areas.Account.Controllers.Models; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.Identity; +using Volo.Abp.Identity.AspNetCore; using Volo.Abp.Settings; using Volo.Abp.Validation; using SignInResult = Microsoft.AspNetCore.Identity.SignInResult; @@ -25,14 +26,20 @@ namespace Volo.Abp.Account.Web.Areas.Account.Controllers protected SignInManager SignInManager { get; } protected IdentityUserManager UserManager { get; } protected ISettingProvider SettingProvider { get; } + protected IdentitySecurityLogManager IdentitySecurityLogManager { get; } - public AccountController(SignInManager signInManager, IdentityUserManager userManager, ISettingProvider settingProvider) + public AccountController( + SignInManager signInManager, + IdentityUserManager userManager, + ISettingProvider settingProvider, + IdentitySecurityLogManager identitySecurityLogManager) { LocalizationResource = typeof(AccountResource); SignInManager = signInManager; UserManager = userManager; SettingProvider = settingProvider; + IdentitySecurityLogManager = identitySecurityLogManager; } [HttpPost] @@ -44,20 +51,34 @@ namespace Volo.Abp.Account.Web.Areas.Account.Controllers ValidateLoginInfo(login); await ReplaceEmailToUsernameOfInputIfNeeds(login); - - return GetAbpLoginResult(await SignInManager.PasswordSignInAsync( + var signInResult = await SignInManager.PasswordSignInAsync( login.UserNameOrEmailAddress, login.Password, login.RememberMe, true - )); + ); + + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() + { + Identity = IdentitySecurityLogIdentityConsts.Identity, + Action = signInResult.ToIdentitySecurityLogAction(), + UserName = login.UserNameOrEmailAddress + }); + + return GetAbpLoginResult(signInResult); } [HttpGet] [Route("logout")] public virtual async Task Logout() { - await SignInManager.SignOutAsync(); + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() + { + Identity = IdentitySecurityLogIdentityConsts.Identity, + Action = IdentitySecurityLogActionConsts.Logout + }); + + await SignInManager.SignOutAsync(); } [HttpPost] diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs index 41bdd93647..7e79442bd7 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Volo.Abp.Account.Localization; @@ -15,6 +14,7 @@ namespace Volo.Abp.Account.Web.Pages.Account { public SignInManager SignInManager { get; set; } public IdentityUserManager UserManager { get; set; } + public IdentitySecurityLogManager IdentitySecurityLogManager { get; set; } protected AccountPageModel() { diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/LoggedOut.js b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/LoggedOut.js index 950bf65719..164399e4ce 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/LoggedOut.js +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/LoggedOut.js @@ -1,5 +1,7 @@ -document.addEventListener("DOMContentLoaded", function (event) { - setTimeout(function () { - window.location = document.getElementById("redirectButton").getAttribute("href"); - }, 3000) -}); +document.addEventListener('DOMContentLoaded', function (event) { + setTimeout(function () { + window.location = document + .getElementById('redirectButton') + .getAttribute('href'); + }, 3000); +}); diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml index 889267d692..41986c3856 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml @@ -6,66 +6,67 @@ @model Volo.Abp.Account.Web.Pages.Account.LoginModel @inject IHtmlLocalizer L @inject Volo.Abp.Settings.ISettingProvider SettingProvider -@if (Model.EnableLocalLogin) -{ -
    -
    -

    @L["Login"]

    - @if (await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled)) - { - - @L["AreYouANewUser"] - @L["Register"] - - } +
    +
    +

    @L["Login"]

    + @if (await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled)) + { + + @L["AreYouANewUser"] + @L["Register"] + + } + @if (Model.EnableLocalLogin) + {
    - - + +
    - +
    - +
    @L["Login"] + @if (Model.ShowCancelButton) + { + @L["Cancel"] + }
    -
    + } - -
    -} + @if (Model.VisibleExternalProviders.Any()) + { +
    +
    @L["OrLoginWith"]
    +
    + + + @foreach (var provider in Model.VisibleExternalProviders) + { + + } +
    +
    + } -@if (Model.VisibleExternalProviders.Any()) -{ -
    -

    @L["UseAnotherServiceToLogIn"]

    -
    - - - @foreach (var provider in Model.VisibleExternalProviders) - { - - } -
    -
    -} + @if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any()) + { +
    + @L["InvalidLoginRequest"] + @L["ThereAreNoLoginSchemesConfiguredForThisClient"] +
    + } -@if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any()) -{ -
    - @L["InvalidLoginRequest"] - @L["ThereAreNoLoginSchemesConfiguredForThisClient"]
    -} +
    diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs index 5251cb12f1..ccd6e07a0f 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs @@ -13,11 +13,12 @@ using System.Threading.Tasks; using Volo.Abp.Account.Settings; using Volo.Abp.Auditing; using Volo.Abp.Identity; +using Volo.Abp.Identity.AspNetCore; using Volo.Abp.Security.Claims; using Volo.Abp.Settings; -using Volo.Abp.Uow; using Volo.Abp.Validation; using IdentityUser = Volo.Abp.Identity.IdentityUser; +using SignInResult = Microsoft.AspNetCore.Identity.SignInResult; namespace Volo.Abp.Account.Web.Pages.Account { @@ -51,6 +52,8 @@ namespace Volo.Abp.Account.Web.Pages.Account protected IAuthenticationSchemeProvider SchemeProvider { get; } protected AbpAccountOptions AccountOptions { get; } + public bool ShowCancelButton { get; set; } + public LoginModel( IAuthenticationSchemeProvider schemeProvider, IOptions accountOptions) @@ -83,7 +86,7 @@ namespace Volo.Abp.Account.Web.Pages.Account ValidateModel(); ExternalProviders = await GetExternalProviders(); - + EnableLocalLogin = await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin); await ReplaceEmailToUsernameOfInputIfNeeds(); @@ -95,6 +98,13 @@ namespace Volo.Abp.Account.Web.Pages.Account true ); + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() + { + Identity = IdentitySecurityLogIdentityConsts.Identity, + Action = result.ToIdentitySecurityLogAction(), + UserName = LoginInput.UserNameOrEmailAddress + }); + if (result.RequiresTwoFactor) { return RedirectToPage("./SendSecurityCode", new @@ -182,6 +192,15 @@ namespace Volo.Abp.Account.Web.Pages.Account bypassTwoFactor: true ); + if (!result.Succeeded) + { + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() + { + Identity = IdentitySecurityLogIdentityConsts.IdentityExternal, + Action = "Login" + result + }); + } + if (result.IsLockedOut) { throw new UserFriendlyException("Cannot proceed because user is locked out!"); @@ -195,18 +214,41 @@ namespace Volo.Abp.Account.Web.Pages.Account //TODO: Handle other cases for result! // Get the information about the user from the external login provider - var info = await SignInManager.GetExternalLoginInfoAsync(); - if (info == null) + var externalLoginInfo = await SignInManager.GetExternalLoginInfoAsync(); + if (externalLoginInfo == null) { throw new ApplicationException("Error loading external login information during confirmation."); } - var user = await CreateExternalUserAsync(info); + if (!IsEmailRetrievedFromExternalLogin(externalLoginInfo)) + { + return RedirectToPage("./Register", new + { + IsExternalLogin = true, + ExternalLoginAuthSchema = externalLoginInfo.LoginProvider, + ReturnUrl = returnUrl + }); + } + + var user = await CreateExternalUserAsync(externalLoginInfo); await SignInManager.SignInAsync(user, false); + + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() + { + Identity = IdentitySecurityLogIdentityConsts.IdentityExternal, + Action = result.ToIdentitySecurityLogAction(), + UserName = user.Name + }); + return RedirectSafely(returnUrl, returnUrlHash); } + private static bool IsEmailRetrievedFromExternalLogin(ExternalLoginInfo externalLoginInfo) + { + return externalLoginInfo.Principal.FindFirstValue(AbpClaimTypes.Email) != null; + } + protected virtual async Task CreateExternalUserAsync(ExternalLoginInfo info) { var emailAddress = info.Principal.FindFirstValue(AbpClaimTypes.Email); diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs index 8cb2c2bc1e..5923827a24 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Identity; namespace Volo.Abp.Account.Web.Pages.Account { @@ -15,6 +16,12 @@ namespace Volo.Abp.Account.Web.Pages.Account public virtual async Task OnGetAsync() { + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() + { + Identity = IdentitySecurityLogIdentityConsts.Identity, + Action = IdentitySecurityLogActionConsts.Logout + }); + await SignInManager.SignOutAsync(); if (ReturnUrl != null) { diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml index 28aa876bba..23350442b2 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml @@ -25,33 +25,41 @@ - -

    @L["ChangePassword"].Value


    - - - - -
    + @if (!Model.DisablePasswordChange) + { + +

    @L["ChangePassword"].Value


    +
    + @if (!Model.HideOldPasswordInput) + { + + } + + + + +
    + } -

    @L["PersonalSettings"].Value


    +

    @L["PersonalSettings"].Value


    - + - + - + - + - + - +
    diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs index 3c779dc870..db51f9890c 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.cshtml.cs @@ -12,6 +12,10 @@ namespace Volo.Abp.Account.Web.Pages.Account public PersonalSettingsInfoModel PersonalSettingsInfoModel { get; set; } + public bool DisablePasswordChange { get; set; } + + public bool HideOldPasswordInput { get; set; } + protected IProfileAppService ProfileAppService { get; } public ManageModel(IProfileAppService profileAppService) @@ -25,6 +29,9 @@ namespace Volo.Abp.Account.Web.Pages.Account PersonalSettingsInfoModel = ObjectMapper.Map(user); + DisablePasswordChange = user.IsExternal; + HideOldPasswordInput = !user.HasPassword; + return Page(); } @@ -54,7 +61,7 @@ namespace Volo.Abp.Account.Web.Pages.Account [DataType(DataType.Password)] public string NewPasswordConfirm { get; set; } } - + public class PersonalSettingsInfoModel { [Required] diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.js b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.js index 844151c71b..3c7c4148e8 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.js +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Manage.js @@ -1,50 +1,47 @@ (function ($) { - var l = abp.localization.getResource('AbpAccount'); var _profileService = volo.abp.identity.profile; - $("#ChangePasswordForm").submit(function (e) { + $('#ChangePasswordForm').submit(function (e) { e.preventDefault(); - if (!$("#ChangePasswordForm").valid()) { + if (!$('#ChangePasswordForm').valid()) { return false; } - var input = $("#ChangePasswordForm").serializeFormToObject().changePasswordInfoModel; + var input = $('#ChangePasswordForm').serializeFormToObject() + .changePasswordInfoModel; - if (input.newPassword != input.newPasswordConfirm || input.currentPassword == '') { - abp.message.error(l("NewPasswordConfirmFailed")); + if ( + input.newPassword != input.newPasswordConfirm || + input.newPassword == '' + ) { + abp.message.error(l('NewPasswordConfirmFailed')); return; } - if (input.currentPassword == '') { + if (input.currentPassword && input.currentPassword == ''){ return; } - _profileService.changePassword( - input - ).then(function (result) { - abp.message.success(l("PasswordChanged")); + _profileService.changePassword(input).then(function (result) { + abp.message.success(l('PasswordChanged')); }); - }); - $("#PersonalSettingsForm").submit(function (e) { + $('#PersonalSettingsForm').submit(function (e) { e.preventDefault(); - if (!$("#PersonalSettingsForm").valid()) { + if (!$('#PersonalSettingsForm').valid()) { return false; } - var input = $("#PersonalSettingsForm").serializeFormToObject().personalSettingsInfoModel; + var input = $('#PersonalSettingsForm').serializeFormToObject() + .personalSettingsInfoModel; - _profileService.update( - input - ).then(function (result) { - abp.notify.success(l("PersonalSettingsSaved")); + _profileService.update(input).then(function (result) { + abp.notify.success(l('PersonalSettingsSaved')); }); - }); - -})(jQuery); \ No newline at end of file +})(jQuery); diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml index 204540e7e7..42779e864c 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml @@ -4,18 +4,27 @@ @model Volo.Abp.Account.Web.Pages.Account.RegisterModel @inject IHtmlLocalizer L -
    -
    -

    @L["Register"]

    - - @L["AlreadyRegistered"] - @L["Login"] - -
    - - - - @L["Register"] - -
    +
    +
    +

    @L["Register"]

    + + @L["AlreadyRegistered"] + @L["Login"] + +
    + @if (!Model.IsExternalLogin) + { + + } + + + + @if (!Model.IsExternalLogin) + { + + } + + @L["Register"] +
    +
    diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs index f6d626dd63..a8ec942084 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs @@ -1,14 +1,16 @@ +using System; using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Security.Claims; +using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using Volo.Abp.Account.Settings; using Volo.Abp.Auditing; -using Volo.Abp.Application.Dtos; using Volo.Abp.Identity; using Volo.Abp.Settings; -using Volo.Abp.Uow; using Volo.Abp.Validation; using IdentityUser = Volo.Abp.Identity.IdentityUser; @@ -27,6 +29,12 @@ namespace Volo.Abp.Account.Web.Pages.Account [BindProperty] public PostInput Input { get; set; } + [BindProperty(SupportsGet = true)] + public bool IsExternalLogin { get; set; } + + [BindProperty(SupportsGet = true)] + public string ExternalLoginAuthSchema { get; set; } + public RegisterModel(IAccountAppService accountAppService) { AccountAppService = accountAppService; @@ -35,32 +43,108 @@ namespace Volo.Abp.Account.Web.Pages.Account public virtual async Task OnGetAsync() { await CheckSelfRegistrationAsync(); - + await TrySetEmailAsync(); return Page(); } + private async Task TrySetEmailAsync() + { + if (IsExternalLogin) + { + var externalLoginInfo = await SignInManager.GetExternalLoginInfoAsync(); + if (externalLoginInfo == null) + { + return; + } + + if (!externalLoginInfo.Principal.Identities.Any()) + { + return; + } + + var identity = externalLoginInfo.Principal.Identities.First(); + var emailClaim = identity.FindFirst(ClaimTypes.Email); + + if (emailClaim == null) + { + return; + } + + Input = new PostInput {EmailAddress = emailClaim.Value}; + } + } + public virtual async Task OnPostAsync() { - ValidateModel(); + try + { + await CheckSelfRegistrationAsync(); + + if (IsExternalLogin) + { + var externalLoginInfo = await SignInManager.GetExternalLoginInfoAsync(); + if (externalLoginInfo == null) + { + Logger.LogWarning("External login info is not available"); + return RedirectToPage("./Login"); + } + + await RegisterExternalUserAsync(externalLoginInfo, Input.EmailAddress); + } + else + { + await RegisterLocalUserAsync(); + } + + return Redirect(ReturnUrl ?? "~/"); //TODO: How to ensure safety? IdentityServer requires it however it should be checked somehow! + } + catch (BusinessException e) + { + Alerts.Danger(e.Message); + return Page(); + } + } - await CheckSelfRegistrationAsync(); + protected virtual async Task RegisterLocalUserAsync() + { + ValidateModel(); - var registerDto = new RegisterDto - { - AppName = "MVC", - EmailAddress = Input.EmailAddress, - Password = Input.Password, - UserName = Input.UserName - }; + var userDto = await AccountAppService.RegisterAsync( + new RegisterDto + { + AppName = "MVC", + EmailAddress = Input.EmailAddress, + Password = Input.Password, + UserName = Input.UserName + } + ); - var userDto = await AccountAppService.RegisterAsync(registerDto); var user = await UserManager.GetByIdAsync(userDto.Id); + await SignInManager.SignInAsync(user, isPersistent: true); + } + + protected virtual async Task RegisterExternalUserAsync(ExternalLoginInfo externalLoginInfo, string emailAddress) + { + var user = new IdentityUser(GuidGenerator.Create(), emailAddress, emailAddress, CurrentTenant.Id); - await UserManager.SetEmailAsync(user, Input.EmailAddress); + (await UserManager.CreateAsync(user)).CheckErrors(); + (await UserManager.AddDefaultRolesAsync(user)).CheckErrors(); - await SignInManager.SignInAsync(user, isPersistent: false); + var userLoginAlreadyExists = user.Logins.Any(x => + x.TenantId == user.TenantId && + x.LoginProvider == externalLoginInfo.LoginProvider && + x.ProviderKey == externalLoginInfo.ProviderKey); + + if (!userLoginAlreadyExists) + { + (await UserManager.AddLoginAsync(user, new UserLoginInfo( + externalLoginInfo.LoginProvider, + externalLoginInfo.ProviderKey, + externalLoginInfo.ProviderDisplayName + ))).CheckErrors(); + } - return Redirect(ReturnUrl ?? "~/"); //TODO: How to ensure safety? IdentityServer requires it however it should be checked somehow! + await SignInManager.SignInAsync(user, isPersistent: true); } protected virtual async Task CheckSelfRegistrationAsync() diff --git a/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj b/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj index 42918e34d4..e4bbaf00ba 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj +++ b/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj @@ -11,6 +11,7 @@ false false false + true Volo.Abp.Account.Web true Library @@ -34,4 +35,8 @@ + + + + diff --git a/modules/account/test/Volo.Abp.Account.Application.Tests/Volo.Abp.Account.Application.Tests.csproj b/modules/account/test/Volo.Abp.Account.Application.Tests/Volo.Abp.Account.Application.Tests.csproj index 779c2d2d25..84e7de1746 100644 --- a/modules/account/test/Volo.Abp.Account.Application.Tests/Volo.Abp.Account.Application.Tests.csproj +++ b/modules/account/test/Volo.Abp.Account.Application.Tests/Volo.Abp.Account.Application.Tests.csproj @@ -7,7 +7,7 @@ - + diff --git a/modules/audit-logging/.prettierrc b/modules/audit-logging/.prettierrc new file mode 100644 index 0000000000..56af76bd94 --- /dev/null +++ b/modules/audit-logging/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "useTabs": false, + "tabWidth": 4 +} diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditingStore.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditingStore.cs index faaf01120e..f6c4343261 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditingStore.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditingStore.cs @@ -57,8 +57,8 @@ namespace Volo.Abp.AuditLogging using (var uow = UnitOfWorkManager.Begin(true)) { await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, auditInfo)); - await uow.SaveChangesAsync(); + await uow.CompleteAsync(); } } } -} +} \ No newline at end of file diff --git a/modules/audit-logging/test/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests.csproj b/modules/audit-logging/test/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests.csproj index 6287d732e2..0a85b56d87 100644 --- a/modules/audit-logging/test/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests.csproj +++ b/modules/audit-logging/test/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/modules/background-jobs/.prettierrc b/modules/background-jobs/.prettierrc new file mode 100644 index 0000000000..56af76bd94 --- /dev/null +++ b/modules/background-jobs/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "useTabs": false, + "tabWidth": 4 +} diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Volo.Abp.BackgroundJobs.DemoApp.Shared.csproj b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Volo.Abp.BackgroundJobs.DemoApp.Shared.csproj index cf5ed0ce08..df4f574663 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Volo.Abp.BackgroundJobs.DemoApp.Shared.csproj +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Volo.Abp.BackgroundJobs.DemoApp.Shared.csproj @@ -7,7 +7,7 @@ - + diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200624023633_BackgroundJobs_Initial.Designer.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200624023633_BackgroundJobs_Initial.Designer.cs deleted file mode 100644 index 40c0adcee7..0000000000 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200624023633_BackgroundJobs_Initial.Designer.cs +++ /dev/null @@ -1,86 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Volo.Abp.BackgroundJobs.DemoApp.Db; -using Volo.Abp.EntityFrameworkCore; - -namespace Volo.Abp.BackgroundJobs.DemoApp.Migrations -{ - [DbContext(typeof(DemoAppDbContext))] - [Migration("20200624023633_BackgroundJobs_Initial")] - partial class BackgroundJobs_Initial - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.5") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Volo.Abp.BackgroundJobs.BackgroundJobRecord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(40)") - .HasMaxLength(40); - - b.Property("CreationTime") - .HasColumnName("CreationTime") - .HasColumnType("datetime2"); - - b.Property("ExtraProperties") - .HasColumnName("ExtraProperties") - .HasColumnType("nvarchar(max)"); - - b.Property("IsAbandoned") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(false); - - b.Property("JobArgs") - .IsRequired() - .HasColumnType("nvarchar(max)") - .HasMaxLength(1048576); - - b.Property("JobName") - .IsRequired() - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("LastTryTime") - .HasColumnType("datetime2"); - - b.Property("NextTryTime") - .HasColumnType("datetime2"); - - b.Property("Priority") - .ValueGeneratedOnAdd() - .HasColumnType("tinyint") - .HasDefaultValue((byte)15); - - b.Property("TryCount") - .ValueGeneratedOnAdd() - .HasColumnType("smallint") - .HasDefaultValue((short)0); - - b.HasKey("Id"); - - b.HasIndex("IsAbandoned", "NextTryTime"); - - b.ToTable("AbpBackgroundJobs"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200624023633_BackgroundJobs_Initial.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200624023633_BackgroundJobs_Initial.cs deleted file mode 100644 index eebffa859a..0000000000 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200624023633_BackgroundJobs_Initial.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Volo.Abp.BackgroundJobs.DemoApp.Migrations -{ - public partial class BackgroundJobs_Initial : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "AbpBackgroundJobs", - columns: table => new - { - Id = table.Column(nullable: false), - ExtraProperties = table.Column(nullable: true), - ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), - JobName = table.Column(maxLength: 128, nullable: false), - JobArgs = table.Column(maxLength: 1048576, nullable: false), - TryCount = table.Column(nullable: false, defaultValue: (short)0), - CreationTime = table.Column(nullable: false), - NextTryTime = table.Column(nullable: false), - LastTryTime = table.Column(nullable: true), - IsAbandoned = table.Column(nullable: false, defaultValue: false), - Priority = table.Column(nullable: false, defaultValue: (byte)15) - }, - constraints: table => - { - table.PrimaryKey("PK_AbpBackgroundJobs", x => x.Id); - }); - - migrationBuilder.CreateIndex( - name: "IX_AbpBackgroundJobs_IsAbandoned_NextTryTime", - table: "AbpBackgroundJobs", - columns: new[] { "IsAbandoned", "NextTryTime" }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AbpBackgroundJobs"); - } - } -} diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200810022513_Initial.Designer.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200810022513_Initial.Designer.cs new file mode 100644 index 0000000000..cd91ca9fe9 --- /dev/null +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200810022513_Initial.Designer.cs @@ -0,0 +1,86 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Volo.Abp.BackgroundJobs.DemoApp.Db; +using Volo.Abp.EntityFrameworkCore; + +namespace Volo.Abp.BackgroundJobs.DemoApp.Migrations +{ + [DbContext(typeof(DemoAppDbContext))] + [Migration("20200810022513_Initial")] + partial class Initial + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) + .HasAnnotation("ProductVersion", "3.1.6") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Volo.Abp.BackgroundJobs.BackgroundJobRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsAbandoned") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("JobArgs") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasMaxLength(1048576); + + b.Property("JobName") + .IsRequired() + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("LastTryTime") + .HasColumnType("datetime2"); + + b.Property("NextTryTime") + .HasColumnType("datetime2"); + + b.Property("Priority") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint") + .HasDefaultValue((byte)15); + + b.Property("TryCount") + .ValueGeneratedOnAdd() + .HasColumnType("smallint") + .HasDefaultValue((short)0); + + b.HasKey("Id"); + + b.HasIndex("IsAbandoned", "NextTryTime"); + + b.ToTable("AbpBackgroundJobs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200810022513_Initial.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200810022513_Initial.cs new file mode 100644 index 0000000000..99c4a11d89 --- /dev/null +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/20200810022513_Initial.cs @@ -0,0 +1,43 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Volo.Abp.BackgroundJobs.DemoApp.Migrations +{ + public partial class Initial : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AbpBackgroundJobs", + columns: table => new + { + Id = table.Column(nullable: false), + ExtraProperties = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), + JobName = table.Column(maxLength: 128, nullable: false), + JobArgs = table.Column(maxLength: 1048576, nullable: false), + TryCount = table.Column(nullable: false, defaultValue: (short)0), + CreationTime = table.Column(nullable: false), + NextTryTime = table.Column(nullable: false), + LastTryTime = table.Column(nullable: true), + IsAbandoned = table.Column(nullable: false, defaultValue: false), + Priority = table.Column(nullable: false, defaultValue: (byte)15) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpBackgroundJobs", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_AbpBackgroundJobs_IsAbandoned_NextTryTime", + table: "AbpBackgroundJobs", + columns: new[] { "IsAbandoned", "NextTryTime" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AbpBackgroundJobs"); + } + } +} diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/DemoAppDbContextModelSnapshot.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/DemoAppDbContextModelSnapshot.cs index fa77fd75ca..4a5df70449 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/DemoAppDbContextModelSnapshot.cs +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Migrations/DemoAppDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace Volo.Abp.BackgroundJobs.DemoApp.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.5") + .HasAnnotation("ProductVersion", "3.1.6") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Volo.Abp.BackgroundJobs.DemoApp.csproj b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Volo.Abp.BackgroundJobs.DemoApp.csproj index 0d8388c610..0faa1370a8 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Volo.Abp.BackgroundJobs.DemoApp.csproj +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Volo.Abp.BackgroundJobs.DemoApp.csproj @@ -6,7 +6,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests.csproj b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests.csproj index 3246aefa4f..50c8e74edb 100644 --- a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests.csproj +++ b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/modules/blob-storing-database/.prettierrc b/modules/blob-storing-database/.prettierrc new file mode 100644 index 0000000000..56af76bd94 --- /dev/null +++ b/modules/blob-storing-database/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "useTabs": false, + "tabWidth": 4 +} diff --git a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/BlobStoring.Database.Host.ConsoleApp.ConsoleApp.csproj b/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/BlobStoring.Database.Host.ConsoleApp.ConsoleApp.csproj index 43455e655a..7e8d79ec68 100644 --- a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/BlobStoring.Database.Host.ConsoleApp.ConsoleApp.csproj +++ b/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/BlobStoring.Database.Host.ConsoleApp.ConsoleApp.csproj @@ -20,13 +20,13 @@ - + - - + + diff --git a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200624023744_Initial.Designer.cs b/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200624023744_Initial.Designer.cs deleted file mode 100644 index 5aa34b1eda..0000000000 --- a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200624023744_Initial.Designer.cs +++ /dev/null @@ -1,110 +0,0 @@ -// -using System; -using BlobStoring.Database.Host.ConsoleApp.ConsoleApp.EfCore; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Volo.Abp.EntityFrameworkCore; - -namespace BlobStoring.Database.Host.ConsoleApp.ConsoleApp.Migrations -{ - [DbContext(typeof(BlobStoringHostDbContext))] - [Migration("20200624023744_Initial")] - partial class Initial - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.5") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Volo.Abp.BlobStoring.Database.DatabaseBlob", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(40)") - .HasMaxLength(40); - - b.Property("ContainerId") - .HasColumnType("uniqueidentifier"); - - b.Property("Content") - .HasColumnType("varbinary(max)") - .HasMaxLength(2147483647); - - b.Property("ExtraProperties") - .HasColumnName("ExtraProperties") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("TenantId") - .HasColumnName("TenantId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.HasIndex("ContainerId"); - - b.HasIndex("TenantId", "ContainerId", "Name"); - - b.ToTable("AbpBlobs"); - }); - - modelBuilder.Entity("Volo.Abp.BlobStoring.Database.DatabaseBlobContainer", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(40)") - .HasMaxLength(40); - - b.Property("ExtraProperties") - .HasColumnName("ExtraProperties") - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); - - b.Property("TenantId") - .HasColumnName("TenantId") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.HasIndex("TenantId", "Name"); - - b.ToTable("AbpBlobContainers"); - }); - - modelBuilder.Entity("Volo.Abp.BlobStoring.Database.DatabaseBlob", b => - { - b.HasOne("Volo.Abp.BlobStoring.Database.DatabaseBlobContainer", null) - .WithMany() - .HasForeignKey("ContainerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200810022447_Initial.Designer.cs b/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200810022447_Initial.Designer.cs new file mode 100644 index 0000000000..6d2051908a --- /dev/null +++ b/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200810022447_Initial.Designer.cs @@ -0,0 +1,110 @@ +// +using System; +using BlobStoring.Database.Host.ConsoleApp.ConsoleApp.EfCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Volo.Abp.EntityFrameworkCore; + +namespace BlobStoring.Database.Host.ConsoleApp.ConsoleApp.Migrations +{ + [DbContext(typeof(BlobStoringHostDbContext))] + [Migration("20200810022447_Initial")] + partial class Initial + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) + .HasAnnotation("ProductVersion", "3.1.6") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Volo.Abp.BlobStoring.Database.DatabaseBlob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("ContainerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Content") + .HasColumnType("varbinary(max)") + .HasMaxLength(2147483647); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ContainerId"); + + b.HasIndex("TenantId", "ContainerId", "Name"); + + b.ToTable("AbpBlobs"); + }); + + modelBuilder.Entity("Volo.Abp.BlobStoring.Database.DatabaseBlobContainer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Name"); + + b.ToTable("AbpBlobContainers"); + }); + + modelBuilder.Entity("Volo.Abp.BlobStoring.Database.DatabaseBlob", b => + { + b.HasOne("Volo.Abp.BlobStoring.Database.DatabaseBlobContainer", null) + .WithMany() + .HasForeignKey("ContainerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200624023744_Initial.cs b/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200810022447_Initial.cs similarity index 100% rename from modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200624023744_Initial.cs rename to modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/20200810022447_Initial.cs diff --git a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/BlobStoringHostDbContextModelSnapshot.cs b/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/BlobStoringHostDbContextModelSnapshot.cs index 796c68b0ad..a32d6221d7 100644 --- a/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/BlobStoringHostDbContextModelSnapshot.cs +++ b/modules/blob-storing-database/host/BlobStoring.Database.Host.ConsoleApp/src/BlobStoring.Database.Host.ConsoleApp.ConsoleApp/Migrations/BlobStoringHostDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace BlobStoring.Database.Host.ConsoleApp.ConsoleApp.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("ProductVersion", "3.1.5") + .HasAnnotation("ProductVersion", "3.1.6") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); diff --git a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/DatabaseBlobProvider.cs b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/DatabaseBlobProvider.cs index fbccdb067e..7fec4592c6 100644 --- a/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/DatabaseBlobProvider.cs +++ b/modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Domain/Volo/Abp/BlobStoring/Database/DatabaseBlobProvider.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; using Volo.Abp.Guids; +using Volo.Abp.MultiTenancy; namespace Volo.Abp.BlobStoring.Database { @@ -11,15 +12,18 @@ namespace Volo.Abp.BlobStoring.Database protected IDatabaseBlobRepository DatabaseBlobRepository { get; } protected IDatabaseBlobContainerRepository DatabaseBlobContainerRepository { get; } protected IGuidGenerator GuidGenerator { get; } + protected ICurrentTenant CurrentTenant { get; } public DatabaseBlobProvider( IDatabaseBlobRepository databaseBlobRepository, IDatabaseBlobContainerRepository databaseBlobContainerRepository, - IGuidGenerator guidGenerator) + IGuidGenerator guidGenerator, + ICurrentTenant currentTenant) { DatabaseBlobRepository = databaseBlobRepository; DatabaseBlobContainerRepository = databaseBlobContainerRepository; GuidGenerator = guidGenerator; + CurrentTenant = currentTenant; } public override async Task SaveAsync(BlobProviderSaveArgs args) @@ -48,7 +52,7 @@ namespace Volo.Abp.BlobStoring.Database } else { - blob = new DatabaseBlob(GuidGenerator.Create(), container.Id, args.BlobName, content); + blob = new DatabaseBlob(GuidGenerator.Create(), container.Id, args.BlobName, content, CurrentTenant.Id); await DatabaseBlobRepository.InsertAsync(blob, autoSave: true); } } @@ -128,10 +132,10 @@ namespace Volo.Abp.BlobStoring.Database return container; } - container = new DatabaseBlobContainer(GuidGenerator.Create(), name); + container = new DatabaseBlobContainer(GuidGenerator.Create(), name, CurrentTenant.Id); await DatabaseBlobContainerRepository.InsertAsync(container, cancellationToken: cancellationToken); return container; } } -} \ No newline at end of file +} diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/EntityFrameworkCore/EfCoreDatabaseBlobContainer_Tests.cs b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/EntityFrameworkCore/EfCoreDatabaseBlobContainer_Tests.cs new file mode 100644 index 0000000000..094217f9e6 --- /dev/null +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/EntityFrameworkCore/EfCoreDatabaseBlobContainer_Tests.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.BlobStoring.Database.EntityFrameworkCore +{ + public class EfCoreDatabaseBlobContainer_Tests : DatabaseBlobContainer_Tests + { + + } +} diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests.csproj b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests.csproj index 04bdd5071a..fdaeecf17e 100644 --- a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests.csproj +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests.csproj @@ -7,9 +7,9 @@ - - - + + + diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/MongoDbDatabaseBlobContainer_Tests.cs b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/MongoDbDatabaseBlobContainer_Tests.cs new file mode 100644 index 0000000000..4d476d5e75 --- /dev/null +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/MongoDbDatabaseBlobContainer_Tests.cs @@ -0,0 +1,10 @@ +using Xunit; + +namespace Volo.Abp.BlobStoring.Database.MongoDB +{ + [Collection(MongoTestCollection.Name)] + public class MongoDbDatabaseBlobContainer_Tests : DatabaseBlobContainer_Tests + { + + } +} diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.TestBase/DatabaseBlobContainer_Tests.cs b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.TestBase/DatabaseBlobContainer_Tests.cs new file mode 100644 index 0000000000..542bb82113 --- /dev/null +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.TestBase/DatabaseBlobContainer_Tests.cs @@ -0,0 +1,10 @@ +using Volo.Abp.Modularity; + +namespace Volo.Abp.BlobStoring.Database +{ + public abstract class DatabaseBlobContainer_Tests : BlobContainer_Tests + where TStartupModule : IAbpModule + { + + } +} diff --git a/modules/blogging/.prettierrc b/modules/blogging/.prettierrc new file mode 100644 index 0000000000..56af76bd94 --- /dev/null +++ b/modules/blogging/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "useTabs": false, + "tabWidth": 4 +} diff --git a/modules/blogging/Volo.Blogging.sln b/modules/blogging/Volo.Blogging.sln index 607d2c3b1b..ad5c0fa895 100644 --- a/modules/blogging/Volo.Blogging.sln +++ b/modules/blogging/Volo.Blogging.sln @@ -59,6 +59,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Blogging.Admin.HttpApi EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Blogging.Admin.Web", "src\Volo.Blogging.Admin.Web\Volo.Blogging.Admin.Web.csproj", "{DB75CA32-96A5-4D10-8DD0-E62A3D0DDBCB}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{9FAD5B78-0577-4500-92D5-DC86E05F773C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Blogging.Application.Contracts.Shared", "src\Volo.Blogging.Application.Contracts.Shared\Volo.Blogging.Application.Contracts.Shared.csproj", "{E28EBBE0-8EB7-4FC1-9267-E6D30993EAE4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -153,6 +157,10 @@ Global {DB75CA32-96A5-4D10-8DD0-E62A3D0DDBCB}.Debug|Any CPU.Build.0 = Debug|Any CPU {DB75CA32-96A5-4D10-8DD0-E62A3D0DDBCB}.Release|Any CPU.ActiveCfg = Release|Any CPU {DB75CA32-96A5-4D10-8DD0-E62A3D0DDBCB}.Release|Any CPU.Build.0 = Release|Any CPU + {E28EBBE0-8EB7-4FC1-9267-E6D30993EAE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E28EBBE0-8EB7-4FC1-9267-E6D30993EAE4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E28EBBE0-8EB7-4FC1-9267-E6D30993EAE4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E28EBBE0-8EB7-4FC1-9267-E6D30993EAE4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -183,6 +191,8 @@ Global {59BBAF94-CC8E-4313-9143-F2F5C36A7C45} = {BE2A423C-271E-469A-AD90-5640DEBEE9C1} {58A63CC9-C886-448B-AB4E-068600294D86} = {BE2A423C-271E-469A-AD90-5640DEBEE9C1} {DB75CA32-96A5-4D10-8DD0-E62A3D0DDBCB} = {BE2A423C-271E-469A-AD90-5640DEBEE9C1} + {9FAD5B78-0577-4500-92D5-DC86E05F773C} = {42BF26EF-B8C7-42DC-9FFB-3653109B7776} + {E28EBBE0-8EB7-4FC1-9267-E6D30993EAE4} = {9FAD5B78-0577-4500-92D5-DC86E05F773C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F2BAE819-78D4-407A-9201-22473B2850B0} diff --git a/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200722033428_Added_SecurityLogs.Designer.cs b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200722033428_Added_SecurityLogs.Designer.cs new file mode 100644 index 0000000000..9faa725ce3 --- /dev/null +++ b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200722033428_Added_SecurityLogs.Designer.cs @@ -0,0 +1,1172 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Volo.Abp.EntityFrameworkCore; +using Volo.BloggingTestApp.EntityFrameworkCore; + +namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations +{ + [DbContext(typeof(BloggingTestAppDbContext))] + [Migration("20200722033428_Added_SecurityLogs")] + partial class Added_SecurityLogs + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) + .HasAnnotation("ProductVersion", "3.1.5") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityClaimType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("Description") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsStatic") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("Regex") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("RegexDescription") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Required") + .HasColumnType("bit"); + + b.Property("ValueType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("AbpClaimTypes"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDefault") + .HasColumnName("IsDefault") + .HasColumnType("bit"); + + b.Property("IsPublic") + .HasColumnName("IsPublic") + .HasColumnType("bit"); + + b.Property("IsStatic") + .HasColumnName("IsStatic") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedName") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName"); + + b.ToTable("AbpRoles"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("ClaimType") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AbpRoleClaims"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentitySecurityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("ApplicationName") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("BrowserInfo") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("ClientId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ClientIpAddress") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CorrelationId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Identity") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Action"); + + b.HasIndex("TenantId", "ApplicationName"); + + b.HasIndex("TenantId", "Identity"); + + b.HasIndex("TenantId", "UserId"); + + b.ToTable("AbpSecurityLogs"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccessFailedCount") + .ValueGeneratedOnAdd() + .HasColumnName("AccessFailedCount") + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasColumnName("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("EmailConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("LockoutEnabled") + .ValueGeneratedOnAdd() + .HasColumnName("LockoutEnabled") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("Name") + .HasColumnName("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("NormalizedEmail") + .IsRequired() + .HasColumnName("NormalizedEmail") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .IsRequired() + .HasColumnName("NormalizedUserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("PasswordHash") + .HasColumnName("PasswordHash") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("PhoneNumber") + .HasColumnName("PhoneNumber") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("PhoneNumberConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("PhoneNumberConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("SecurityStamp") + .IsRequired() + .HasColumnName("SecurityStamp") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("Surname") + .HasColumnName("Surname") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TwoFactorEnabled") + .ValueGeneratedOnAdd() + .HasColumnName("TwoFactorEnabled") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("UserName") + .IsRequired() + .HasColumnName("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("Email"); + + b.HasIndex("NormalizedEmail"); + + b.HasIndex("NormalizedUserName"); + + b.HasIndex("UserName"); + + b.ToTable("AbpUsers"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("ClaimType") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AbpUserClaims"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .IsRequired() + .HasColumnType("nvarchar(196)") + .HasMaxLength(196); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "LoginProvider"); + + b.HasIndex("LoginProvider", "ProviderKey"); + + b.ToTable("AbpUserLogins"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserOrganizationUnit", b => + { + b.Property("OrganizationUnitId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("OrganizationUnitId", "UserId"); + + b.HasIndex("UserId", "OrganizationUnitId"); + + b.ToTable("AbpUserOrganizationUnits"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId", "UserId"); + + b.ToTable("AbpUserRoles"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Name") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AbpUserTokens"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Code") + .IsRequired() + .HasColumnName("Code") + .HasColumnType("nvarchar(95)") + .HasMaxLength(95); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnName("DisplayName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("Code"); + + b.HasIndex("ParentId"); + + b.ToTable("AbpOrganizationUnits"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnitRole", b => + { + b.Property("OrganizationUnitId") + .HasColumnType("uniqueidentifier"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("OrganizationUnitId", "RoleId"); + + b.HasIndex("RoleId", "OrganizationUnitId"); + + b.ToTable("AbpOrganizationUnitRoles"); + }); + + modelBuilder.Entity("Volo.Abp.PermissionManagement.PermissionGrant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderName") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("Name", "ProviderName", "ProviderKey"); + + b.ToTable("AbpPermissionGrants"); + }); + + modelBuilder.Entity("Volo.Abp.SettingManagement.Setting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(2048)") + .HasMaxLength(2048); + + b.HasKey("Id"); + + b.HasIndex("Name", "ProviderName", "ProviderKey"); + + b.ToTable("AbpSettings"); + }); + + modelBuilder.Entity("Volo.Blogging.Blogs.Blog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnName("Description") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnName("Name") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ShortName") + .IsRequired() + .HasColumnName("ShortName") + .HasColumnType("nvarchar(32)") + .HasMaxLength(32); + + b.HasKey("Id"); + + b.ToTable("BlgBlogs"); + }); + + modelBuilder.Entity("Volo.Blogging.Comments.Comment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("PostId") + .HasColumnName("PostId") + .HasColumnType("uniqueidentifier"); + + b.Property("RepliedCommentId") + .HasColumnName("RepliedCommentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Text") + .IsRequired() + .HasColumnName("Text") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.HasKey("Id"); + + b.HasIndex("PostId"); + + b.HasIndex("RepliedCommentId"); + + b.ToTable("BlgComments"); + }); + + modelBuilder.Entity("Volo.Blogging.Posts.Post", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BlogId") + .HasColumnName("BlogId") + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("Content") + .HasColumnName("Content") + .HasColumnType("nvarchar(max)") + .HasMaxLength(1048576); + + b.Property("CoverImage") + .IsRequired() + .HasColumnName("CoverImage") + .HasColumnType("nvarchar(max)"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnName("Description") + .HasColumnType("nvarchar(1000)") + .HasMaxLength(1000); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("ReadCount") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasColumnName("Title") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("Url") + .IsRequired() + .HasColumnName("Url") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.HasKey("Id"); + + b.HasIndex("BlogId"); + + b.ToTable("BlgPosts"); + }); + + modelBuilder.Entity("Volo.Blogging.Posts.PostTag", b => + { + b.Property("PostId") + .HasColumnName("PostId") + .HasColumnType("uniqueidentifier"); + + b.Property("TagId") + .HasColumnName("TagId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("PostId", "TagId"); + + b.HasIndex("TagId"); + + b.ToTable("BlgPostTags"); + }); + + modelBuilder.Entity("Volo.Blogging.Tagging.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BlogId") + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnName("Description") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnName("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("UsageCount") + .HasColumnName("UsageCount") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("BlgTags"); + }); + + modelBuilder.Entity("Volo.Blogging.Users.BlogUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("Email") + .IsRequired() + .HasColumnName("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("EmailConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnName("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("PhoneNumber") + .HasColumnName("PhoneNumber") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("PhoneNumberConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("PhoneNumberConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("Surname") + .HasColumnName("Surname") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .IsRequired() + .HasColumnName("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.ToTable("BlgUsers"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => + { + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany("Claims") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Claims") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Logins") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserOrganizationUnit", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany() + .HasForeignKey("OrganizationUnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("OrganizationUnits") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => + { + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Roles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Tokens") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany() + .HasForeignKey("ParentId"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnitRole", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany("Roles") + .HasForeignKey("OrganizationUnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Blogging.Comments.Comment", b => + { + b.HasOne("Volo.Blogging.Posts.Post", null) + .WithMany() + .HasForeignKey("PostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Blogging.Comments.Comment", null) + .WithMany() + .HasForeignKey("RepliedCommentId"); + }); + + modelBuilder.Entity("Volo.Blogging.Posts.Post", b => + { + b.HasOne("Volo.Blogging.Blogs.Blog", null) + .WithMany() + .HasForeignKey("BlogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Blogging.Posts.PostTag", b => + { + b.HasOne("Volo.Blogging.Posts.Post", null) + .WithMany("Tags") + .HasForeignKey("PostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Blogging.Tagging.Tag", null) + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200722033428_Added_SecurityLogs.cs b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200722033428_Added_SecurityLogs.cs new file mode 100644 index 0000000000..7ac1cd66d2 --- /dev/null +++ b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200722033428_Added_SecurityLogs.cs @@ -0,0 +1,226 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations +{ + public partial class Added_SecurityLogs : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "BlgUsers", + maxLength: 40, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "BlgTags", + maxLength: 40, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "BlgPosts", + maxLength: 40, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "BlgComments", + maxLength: 40, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "BlgBlogs", + maxLength: 40, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "AbpUsers", + maxLength: 40, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "AbpRoles", + maxLength: 40, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(256)", + oldMaxLength: 256); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "AbpOrganizationUnits", + maxLength: 40, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "AbpClaimTypes", + maxLength: 40, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(256)", + oldMaxLength: 256); + + migrationBuilder.CreateTable( + name: "AbpSecurityLogs", + columns: table => new + { + Id = table.Column(nullable: false), + ExtraProperties = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), + TenantId = table.Column(nullable: true), + ApplicationName = table.Column(maxLength: 96, nullable: true), + Identity = table.Column(maxLength: 96, nullable: true), + Action = table.Column(maxLength: 96, nullable: true), + UserId = table.Column(nullable: true), + UserName = table.Column(maxLength: 256, nullable: true), + TenantName = table.Column(maxLength: 64, nullable: true), + ClientId = table.Column(maxLength: 64, nullable: true), + CorrelationId = table.Column(maxLength: 64, nullable: true), + ClientIpAddress = table.Column(maxLength: 64, nullable: true), + BrowserInfo = table.Column(maxLength: 512, nullable: true), + CreationTime = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpSecurityLogs", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_Action", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "Action" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_ApplicationName", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "ApplicationName" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_Identity", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "Identity" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpSecurityLogs_TenantId_UserId", + table: "AbpSecurityLogs", + columns: new[] { "TenantId", "UserId" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AbpSecurityLogs"); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "BlgUsers", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldMaxLength: 40, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "BlgTags", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldMaxLength: 40, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "BlgPosts", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldMaxLength: 40, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "BlgComments", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldMaxLength: 40, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "BlgBlogs", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldMaxLength: 40, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "AbpUsers", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldMaxLength: 40, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "AbpRoles", + type: "nvarchar(256)", + maxLength: 256, + nullable: false, + oldClrType: typeof(string), + oldMaxLength: 40, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "AbpOrganizationUnits", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldMaxLength: 40, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "AbpClaimTypes", + type: "nvarchar(256)", + maxLength: 256, + nullable: false, + oldClrType: typeof(string), + oldMaxLength: 40, + oldNullable: true); + } + } +} diff --git a/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200810022522_IdentityUserIsExternal.Designer.cs b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200810022522_IdentityUserIsExternal.Designer.cs new file mode 100644 index 0000000000..10ddf6bdf3 --- /dev/null +++ b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200810022522_IdentityUserIsExternal.Designer.cs @@ -0,0 +1,1178 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Volo.Abp.EntityFrameworkCore; +using Volo.BloggingTestApp.EntityFrameworkCore; + +namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations +{ + [DbContext(typeof(BloggingTestAppDbContext))] + [Migration("20200810022522_IdentityUserIsExternal")] + partial class IdentityUserIsExternal + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) + .HasAnnotation("ProductVersion", "3.1.6") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityClaimType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("Description") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsStatic") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("Regex") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("RegexDescription") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Required") + .HasColumnType("bit"); + + b.Property("ValueType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("AbpClaimTypes"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDefault") + .HasColumnName("IsDefault") + .HasColumnType("bit"); + + b.Property("IsPublic") + .HasColumnName("IsPublic") + .HasColumnType("bit"); + + b.Property("IsStatic") + .HasColumnName("IsStatic") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedName") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName"); + + b.ToTable("AbpRoles"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("ClaimType") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AbpRoleClaims"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentitySecurityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("ApplicationName") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("BrowserInfo") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("ClientId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ClientIpAddress") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CorrelationId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Identity") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Action"); + + b.HasIndex("TenantId", "ApplicationName"); + + b.HasIndex("TenantId", "Identity"); + + b.HasIndex("TenantId", "UserId"); + + b.ToTable("AbpSecurityLogs"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccessFailedCount") + .ValueGeneratedOnAdd() + .HasColumnName("AccessFailedCount") + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasColumnName("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("EmailConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("IsExternal") + .ValueGeneratedOnAdd() + .HasColumnName("IsExternal") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("LockoutEnabled") + .ValueGeneratedOnAdd() + .HasColumnName("LockoutEnabled") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("Name") + .HasColumnName("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("NormalizedEmail") + .IsRequired() + .HasColumnName("NormalizedEmail") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .IsRequired() + .HasColumnName("NormalizedUserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("PasswordHash") + .HasColumnName("PasswordHash") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("PhoneNumber") + .HasColumnName("PhoneNumber") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("PhoneNumberConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("PhoneNumberConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("SecurityStamp") + .IsRequired() + .HasColumnName("SecurityStamp") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("Surname") + .HasColumnName("Surname") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TwoFactorEnabled") + .ValueGeneratedOnAdd() + .HasColumnName("TwoFactorEnabled") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("UserName") + .IsRequired() + .HasColumnName("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("Email"); + + b.HasIndex("NormalizedEmail"); + + b.HasIndex("NormalizedUserName"); + + b.HasIndex("UserName"); + + b.ToTable("AbpUsers"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("ClaimType") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AbpUserClaims"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .IsRequired() + .HasColumnType("nvarchar(196)") + .HasMaxLength(196); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "LoginProvider"); + + b.HasIndex("LoginProvider", "ProviderKey"); + + b.ToTable("AbpUserLogins"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserOrganizationUnit", b => + { + b.Property("OrganizationUnitId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("OrganizationUnitId", "UserId"); + + b.HasIndex("UserId", "OrganizationUnitId"); + + b.ToTable("AbpUserOrganizationUnits"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId", "UserId"); + + b.ToTable("AbpUserRoles"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Name") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AbpUserTokens"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Code") + .IsRequired() + .HasColumnName("Code") + .HasColumnType("nvarchar(95)") + .HasMaxLength(95); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnName("DisplayName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("Code"); + + b.HasIndex("ParentId"); + + b.ToTable("AbpOrganizationUnits"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnitRole", b => + { + b.Property("OrganizationUnitId") + .HasColumnType("uniqueidentifier"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("OrganizationUnitId", "RoleId"); + + b.HasIndex("RoleId", "OrganizationUnitId"); + + b.ToTable("AbpOrganizationUnitRoles"); + }); + + modelBuilder.Entity("Volo.Abp.PermissionManagement.PermissionGrant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderName") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("Name", "ProviderName", "ProviderKey"); + + b.ToTable("AbpPermissionGrants"); + }); + + modelBuilder.Entity("Volo.Abp.SettingManagement.Setting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(2048)") + .HasMaxLength(2048); + + b.HasKey("Id"); + + b.HasIndex("Name", "ProviderName", "ProviderKey"); + + b.ToTable("AbpSettings"); + }); + + modelBuilder.Entity("Volo.Blogging.Blogs.Blog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnName("Description") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnName("Name") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ShortName") + .IsRequired() + .HasColumnName("ShortName") + .HasColumnType("nvarchar(32)") + .HasMaxLength(32); + + b.HasKey("Id"); + + b.ToTable("BlgBlogs"); + }); + + modelBuilder.Entity("Volo.Blogging.Comments.Comment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("PostId") + .HasColumnName("PostId") + .HasColumnType("uniqueidentifier"); + + b.Property("RepliedCommentId") + .HasColumnName("RepliedCommentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Text") + .IsRequired() + .HasColumnName("Text") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.HasKey("Id"); + + b.HasIndex("PostId"); + + b.HasIndex("RepliedCommentId"); + + b.ToTable("BlgComments"); + }); + + modelBuilder.Entity("Volo.Blogging.Posts.Post", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BlogId") + .HasColumnName("BlogId") + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("Content") + .HasColumnName("Content") + .HasColumnType("nvarchar(max)") + .HasMaxLength(1048576); + + b.Property("CoverImage") + .IsRequired() + .HasColumnName("CoverImage") + .HasColumnType("nvarchar(max)"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnName("Description") + .HasColumnType("nvarchar(1000)") + .HasMaxLength(1000); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("ReadCount") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasColumnName("Title") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("Url") + .IsRequired() + .HasColumnName("Url") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.HasKey("Id"); + + b.HasIndex("BlogId"); + + b.ToTable("BlgPosts"); + }); + + modelBuilder.Entity("Volo.Blogging.Posts.PostTag", b => + { + b.Property("PostId") + .HasColumnName("PostId") + .HasColumnType("uniqueidentifier"); + + b.Property("TagId") + .HasColumnName("TagId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("PostId", "TagId"); + + b.HasIndex("TagId"); + + b.ToTable("BlgPostTags"); + }); + + modelBuilder.Entity("Volo.Blogging.Tagging.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BlogId") + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnName("Description") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnName("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("UsageCount") + .HasColumnName("UsageCount") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("BlgTags"); + }); + + modelBuilder.Entity("Volo.Blogging.Users.BlogUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("Email") + .IsRequired() + .HasColumnName("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("EmailConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnName("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("PhoneNumber") + .HasColumnName("PhoneNumber") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("PhoneNumberConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("PhoneNumberConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("Surname") + .HasColumnName("Surname") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .IsRequired() + .HasColumnName("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.ToTable("BlgUsers"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => + { + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany("Claims") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Claims") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Logins") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserOrganizationUnit", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany() + .HasForeignKey("OrganizationUnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("OrganizationUnits") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => + { + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Roles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Tokens") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany() + .HasForeignKey("ParentId"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnitRole", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany("Roles") + .HasForeignKey("OrganizationUnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Blogging.Comments.Comment", b => + { + b.HasOne("Volo.Blogging.Posts.Post", null) + .WithMany() + .HasForeignKey("PostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Blogging.Comments.Comment", null) + .WithMany() + .HasForeignKey("RepliedCommentId"); + }); + + modelBuilder.Entity("Volo.Blogging.Posts.Post", b => + { + b.HasOne("Volo.Blogging.Blogs.Blog", null) + .WithMany() + .HasForeignKey("BlogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Blogging.Posts.PostTag", b => + { + b.HasOne("Volo.Blogging.Posts.Post", null) + .WithMany("Tags") + .HasForeignKey("PostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Blogging.Tagging.Tag", null) + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200810022522_IdentityUserIsExternal.cs b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200810022522_IdentityUserIsExternal.cs new file mode 100644 index 0000000000..ee2e259b8a --- /dev/null +++ b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200810022522_IdentityUserIsExternal.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations +{ + public partial class IdentityUserIsExternal : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsExternal", + table: "AbpUsers", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsExternal", + table: "AbpUsers"); + } + } +} diff --git a/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/BloggingTestAppDbContextModelSnapshot.cs b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/BloggingTestAppDbContextModelSnapshot.cs index 00e9389e8f..47647bb25e 100644 --- a/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/BloggingTestAppDbContextModelSnapshot.cs +++ b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/BloggingTestAppDbContextModelSnapshot.cs @@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Volo.Abp.EntityFrameworkCore; using Volo.BloggingTestApp.EntityFrameworkCore; namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations @@ -15,7 +16,8 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "3.1.2") + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) + .HasAnnotation("ProductVersion", "3.1.6") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); @@ -27,10 +29,9 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() - .IsRequired() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("Description") .HasColumnType("nvarchar(256)") @@ -75,10 +76,9 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() - .IsRequired() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("ExtraProperties") .HasColumnName("ExtraProperties") @@ -145,6 +145,81 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations b.ToTable("AbpRoleClaims"); }); + modelBuilder.Entity("Volo.Abp.Identity.IdentitySecurityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Action") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("ApplicationName") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("BrowserInfo") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("ClientId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ClientIpAddress") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CorrelationId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Identity") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "Action"); + + b.HasIndex("TenantId", "ApplicationName"); + + b.HasIndex("TenantId", "Identity"); + + b.HasIndex("TenantId", "UserId"); + + b.ToTable("AbpSecurityLogs"); + }); + modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => { b.Property("Id") @@ -160,7 +235,8 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("CreationTime") .HasColumnName("CreationTime") @@ -200,6 +276,12 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations .HasColumnType("bit") .HasDefaultValue(false); + b.Property("IsExternal") + .ValueGeneratedOnAdd() + .HasColumnName("IsExternal") + .HasColumnType("bit") + .HasDefaultValue(false); + b.Property("LastModificationTime") .HasColumnName("LastModificationTime") .HasColumnType("datetime2"); @@ -433,7 +515,8 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("CreationTime") .HasColumnName("CreationTime") @@ -590,7 +673,8 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("CreationTime") .HasColumnName("CreationTime") @@ -657,7 +741,8 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("CreationTime") .HasColumnName("CreationTime") @@ -729,7 +814,8 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("Content") .HasColumnName("Content") @@ -839,7 +925,8 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("CreationTime") .HasColumnName("CreationTime") @@ -904,7 +991,8 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations b.Property("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasColumnType("nvarchar(max)"); + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); b.Property("Email") .IsRequired() diff --git a/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Volo.BloggingTestApp.EntityFrameworkCore.csproj b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Volo.BloggingTestApp.EntityFrameworkCore.csproj index 341f5fdc14..5d3175a12a 100644 --- a/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Volo.BloggingTestApp.EntityFrameworkCore.csproj +++ b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Volo.BloggingTestApp.EntityFrameworkCore.csproj @@ -3,7 +3,7 @@ - netstandard2.0 + netcoreapp3.1 @@ -14,4 +14,9 @@ + + + + + diff --git a/modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs b/modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs index 3d37192145..0cbdeaf6fc 100644 --- a/modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs +++ b/modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs @@ -1,4 +1,4 @@ -//#define MONGODB +//#define MONGODB using System.Collections.Generic; using System.Globalization; @@ -14,6 +14,7 @@ using Microsoft.Extensions.Options; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.Swagger; using Volo.Abp; +using Volo.Abp.Account; using Volo.Abp.Account.Web; using Volo.Abp.AspNetCore.Mvc.UI; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap; @@ -51,6 +52,7 @@ namespace Volo.BloggingTestApp typeof(BloggingTestAppEntityFrameworkCoreModule), #endif typeof(AbpAccountWebModule), + typeof(AbpAccountApplicationModule), typeof(AbpIdentityWebModule), typeof(AbpIdentityApplicationModule), typeof(AbpPermissionManagementDomainIdentityModule), diff --git a/modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj b/modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj index c264c00f41..4fb4e461af 100644 --- a/modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj +++ b/modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj @@ -18,8 +18,6 @@ - - @@ -35,6 +33,7 @@ + diff --git a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo.Blogging.Admin.Application.Contracts.csproj b/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo.Blogging.Admin.Application.Contracts.csproj index e7f7b6a8b5..2af8db519c 100644 --- a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo.Blogging.Admin.Application.Contracts.csproj +++ b/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo.Blogging.Admin.Application.Contracts.csproj @@ -2,30 +2,17 @@ - + netstandard2.0 Volo.Blogging.Admin.Application.Contracts Volo.Blogging.Admin.Application.Contracts + true - - + - - - - - - - - - - - - - diff --git a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/BloggingAdminApplicationContractsModule.cs b/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/BloggingAdminApplicationContractsModule.cs index b2b6d000c0..5f255f3c01 100644 --- a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/BloggingAdminApplicationContractsModule.cs +++ b/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/BloggingAdminApplicationContractsModule.cs @@ -5,21 +5,12 @@ using Volo.Blogging.Localization; namespace Volo.Blogging.Admin { - [DependsOn(typeof(BloggingDomainSharedModule))] + [DependsOn(typeof(BloggingApplicationContractsSharedModule))] public class BloggingAdminApplicationContractsModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { - Configure(options => - { - options.FileSets.AddEmbedded(); - }); - Configure(options => - { - options.Resources - .Get() - .AddVirtualJson("Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts"); - }); + } } } diff --git a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/BloggingAdminPermissionDefinitionProvider.cs b/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/BloggingAdminPermissionDefinitionProvider.cs deleted file mode 100644 index 77c8f98e62..0000000000 --- a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/BloggingAdminPermissionDefinitionProvider.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Volo.Abp.Authorization.Permissions; -using Volo.Abp.Localization; -using Volo.Blogging.Localization; - -namespace Volo.Blogging.Admin -{ - public class BloggingAdminPermissionDefinitionProvider : PermissionDefinitionProvider - { - public override void Define(IPermissionDefinitionContext context) - { - var bloggingGroup = context.AddGroup(BloggingAdminPermissions.GroupName, L("Permission:BloggingAdmin")); - - var blogs = bloggingGroup.AddPermission(BloggingAdminPermissions.Blogs.Default, L("Permission:Blogs")); - blogs.AddChild(BloggingAdminPermissions.Blogs.Management, L("Permission:Management")); - blogs.AddChild(BloggingAdminPermissions.Blogs.Update, L("Permission:Edit")); - blogs.AddChild(BloggingAdminPermissions.Blogs.Delete, L("Permission:Delete")); - blogs.AddChild(BloggingAdminPermissions.Blogs.Create, L("Permission:Create")); - } - - private static LocalizableString L(string name) - { - return LocalizableString.Create(name); - } - } -} diff --git a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/BloggingAdminPermissions.cs b/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/BloggingAdminPermissions.cs deleted file mode 100644 index 9150af601b..0000000000 --- a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/BloggingAdminPermissions.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Volo.Abp.Reflection; - -namespace Volo.Blogging.Admin -{ - public class BloggingAdminPermissions - { - public const string GroupName = "Blogging.Admin"; - - public static class Blogs - { - public const string Default = GroupName + ".Blog"; - public const string Management = Default + ".Management"; - public const string Delete = Default + ".Delete"; - public const string Update = Default + ".Update"; - public const string Create = Default + ".Create"; - } - - public static string[] GetAll() - { - return ReflectionHelper.GetPublicConstantsRecursively(typeof(BloggingAdminPermissions)); - } - } -} diff --git a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/cs.json b/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/cs.json deleted file mode 100644 index 1c8657c85c..0000000000 --- a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/cs.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "culture": "cs", - "texts": { - "Permission:BloggingAdmin": "Blog", - "Permission:Blogs": "Blogy", - "Permission:Management": "Správa", - "Permission:Edit": "Upravit", - "Permission:Create": "Vytvořit", - "Permission:Delete": "Smazat" - } -} diff --git a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/de.json b/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/de.json deleted file mode 100644 index 5666cbb368..0000000000 --- a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/de.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "culture": "de", - "texts": { - "Permission:BloggingAdmin": "Blog", - "Permission:Blogs": "Blogs", - "Permission:Management": "Verwaltung", - "Permission:Edit": "Bearbeiten", - "Permission:Create": "Erstellen", - "Permission:Delete": "Löschen" - } -} diff --git a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/en.json b/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/en.json deleted file mode 100644 index f3708eacad..0000000000 --- a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/en.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "culture": "en", - "texts": { - "Permission:BloggingAdmin": "Blogging", - "Permission:Blogs": "Blogs", - "Permission:Management": "Management", - "Permission:Edit": "Edit", - "Permission:Create": "Create", - "Permission:Delete": "Delete" - } -} diff --git a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/nl.json b/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/nl.json deleted file mode 100644 index c1174611c1..0000000000 --- a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/nl.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "culture": "nl", - "texts": { - "Permission:BloggingAdmin": "Blog", - "Permission:Blogs": "Blogs", - "Permission:Management": "Beheer", - "Permission:Edit": "Bewerk", - "Permission:Create": "Maak aan", - "Permission:Delete": "Verwijder" - } -} diff --git a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/pl-PL.json b/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/pl-PL.json deleted file mode 100644 index e8f6135456..0000000000 --- a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/pl-PL.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "culture": "pl-PL", - "texts": { - "Permission:BloggingAdmin": "Blog", - "Permission:Blogs": "Blogi", - "Permission:Management": "Zarządzanie", - "Permission:Edit": "Edytuj", - "Permission:Create": "Utwórz", - "Permission:Delete": "Usuń" - } -} diff --git a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/pt-BR.json b/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/pt-BR.json deleted file mode 100644 index 09fd08fe5a..0000000000 --- a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/pt-BR.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "culture": "pt-BR", - "texts": { - "Permission:BloggingAdmin": "Blog", - "Permission:Blogs": "Blogs", - "Permission:Management": "Gerenciamento", - "Permission:Edit": "Editar", - "Permission:Create": "Criar", - "Permission:Delete": "Excluir" - } -} diff --git a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/sl.json b/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/sl.json deleted file mode 100644 index 455c49008c..0000000000 --- a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/sl.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "culture": "sl", - "texts": { - "Permission:BloggingAdmin": "Blog", - "Permission:Blogs": "Blogi", - "Permission:Management": "Upravljanje", - "Permission:Edit": "Urejanje", - "Permission:Create": "Ustvarjanje", - "Permission:Delete": "Brisanje" - } -} diff --git a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/tr.json b/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/tr.json deleted file mode 100644 index dedb5d3dd6..0000000000 --- a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/tr.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "culture": "tr", - "texts": { - "Permission:BloggingAdmin": "Blog", - "Permission:Blogs": "Bloglar", - "Permission:Management": "Yönetme", - "Permission:Edit": "Düzenle", - "Permission:Create": "Ekle", - "Permission:Delete": "Sil" - } -} diff --git a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/vi.json b/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/vi.json deleted file mode 100644 index 387083b100..0000000000 --- a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/vi.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "culture": "vi", - "texts": { - "Permission:BloggingAdmin": "Blog", - "Permission:Blogs": "Blogs", - "Permission:Management": "Quản lý", - "Permission:Edit": "Sửa", - "Permission:Create": "Tạo", - "Permission:Delete": "Xóa" - } -} diff --git a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/zh-Hans.json b/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/zh-Hans.json deleted file mode 100644 index a17bb45c09..0000000000 --- a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/zh-Hans.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "culture": "zh-Hans", - "texts": { - "Permission:BloggingAdmin": "博客", - "Permission:Blogs": "博客", - "Permission:Management": "管理", - "Permission:Edit": "编辑", - "Permission:Create": "创建", - "Permission:Delete": "删除" - } - } diff --git a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/zh-Hant.json b/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/zh-Hant.json deleted file mode 100644 index 2d2d28dcbb..0000000000 --- a/modules/blogging/src/Volo.Blogging.Admin.Application.Contracts/Volo/Blogging/Admin/Localization/Resources/Blogging/Admin/ApplicationContracts/zh-Hant.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "culture": "zh-Hant", - "texts": { - "Permission:BloggingAdmin": "部落格", - "Permission:Blogs": "部落格", - "Permission:Management": "管理", - "Permission:Edit": "標及", - "Permission:Create": "新增", - "Permission:Delete": "刪除" - } - } diff --git a/modules/blogging/src/Volo.Blogging.Admin.Application/Volo/Blogging/Admin/Blogs/BlogManagementAppService.cs b/modules/blogging/src/Volo.Blogging.Admin.Application/Volo/Blogging/Admin/Blogs/BlogManagementAppService.cs index 1df6f52c9b..242ccb6330 100644 --- a/modules/blogging/src/Volo.Blogging.Admin.Application/Volo/Blogging/Admin/Blogs/BlogManagementAppService.cs +++ b/modules/blogging/src/Volo.Blogging.Admin.Application/Volo/Blogging/Admin/Blogs/BlogManagementAppService.cs @@ -32,7 +32,7 @@ namespace Volo.Blogging.Admin.Blogs return ObjectMapper.Map(blog); } - [Authorize(BloggingAdminPermissions.Blogs.Create)] + [Authorize(BloggingPermissions.Blogs.Create)] public async Task CreateAsync(CreateBlogDto input) { var newBlog = await _blogRepository.InsertAsync(new Blog(GuidGenerator.Create(), input.Name, input.ShortName) @@ -43,7 +43,7 @@ namespace Volo.Blogging.Admin.Blogs return ObjectMapper.Map(newBlog); } - [Authorize(BloggingAdminPermissions.Blogs.Update)] + [Authorize(BloggingPermissions.Blogs.Update)] public async Task UpdateAsync(Guid id, UpdateBlogDto input) { var blog = await _blogRepository.GetAsync(id); @@ -55,7 +55,7 @@ namespace Volo.Blogging.Admin.Blogs return ObjectMapper.Map(blog); } - [Authorize(BloggingAdminPermissions.Blogs.Delete)] + [Authorize(BloggingPermissions.Blogs.Delete)] public async Task DeleteAsync(Guid id) { await _blogRepository.DeleteAsync(id); diff --git a/modules/blogging/src/Volo.Blogging.Admin.Web/BloggingAdminMenuContributor.cs b/modules/blogging/src/Volo.Blogging.Admin.Web/BloggingAdminMenuContributor.cs index 0058738299..cd8b073429 100644 --- a/modules/blogging/src/Volo.Blogging.Admin.Web/BloggingAdminMenuContributor.cs +++ b/modules/blogging/src/Volo.Blogging.Admin.Web/BloggingAdminMenuContributor.cs @@ -18,12 +18,12 @@ namespace Volo.Blogging.Admin { var l = context.GetLocalizer(); - if (await context.IsGrantedAsync(BloggingAdminPermissions.Blogs.Management)) + if (await context.IsGrantedAsync(BloggingPermissions.Blogs.Management)) { var managementRootMenuItem = new ApplicationMenuItem("BlogManagement", l["Menu:BlogManagement"]); //TODO: Using the same permission. Reconsider. - if (await context.IsGrantedAsync(BloggingAdminPermissions.Blogs.Management)) + if (await context.IsGrantedAsync(BloggingPermissions.Blogs.Management)) { managementRootMenuItem.AddItem(new ApplicationMenuItem("BlogManagement.Blogs", l["Menu:Blogs"], "~/Blogging/Admin/Blogs")); } diff --git a/modules/blogging/src/Volo.Blogging.Admin.Web/BloggingAdminWebModule.cs b/modules/blogging/src/Volo.Blogging.Admin.Web/BloggingAdminWebModule.cs index f62333fb02..09d6b8e3cc 100644 --- a/modules/blogging/src/Volo.Blogging.Admin.Web/BloggingAdminWebModule.cs +++ b/modules/blogging/src/Volo.Blogging.Admin.Web/BloggingAdminWebModule.cs @@ -40,7 +40,7 @@ namespace Volo.Blogging.Admin Configure(options => { - options.FileSets.AddEmbedded("Volo.Blogging.Admin"); + options.FileSets.AddEmbedded(); }); context.Services.AddAutoMapperObjectMapper(); diff --git a/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Create.cshtml.cs b/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Create.cshtml.cs index 6f4bae5e63..d3d66656b2 100644 --- a/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Create.cshtml.cs +++ b/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Create.cshtml.cs @@ -24,7 +24,7 @@ namespace Volo.Blogging.Admin.Pages.Blogging.Admin.Blogs public virtual async Task OnGetAsync() { - if (!await _authorization.IsGrantedAsync(BloggingAdminPermissions.Blogs.Create)) + if (!await _authorization.IsGrantedAsync(BloggingPermissions.Blogs.Create)) { return Redirect("/"); } diff --git a/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Edit.cshtml.cs b/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Edit.cshtml.cs index 8d0b2ba978..ae275cd4e6 100644 --- a/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Edit.cshtml.cs +++ b/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Edit.cshtml.cs @@ -28,7 +28,7 @@ namespace Volo.Blogging.Admin.Pages.Blogging.Admin.Blogs public virtual async Task OnGetAsync() { - if (!await _authorization.IsGrantedAsync(BloggingAdminPermissions.Blogs.Update)) + if (!await _authorization.IsGrantedAsync(BloggingPermissions.Blogs.Update)) { return Redirect("/"); } diff --git a/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Index.cshtml b/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Index.cshtml index bd0d4a0bfb..1c2b823852 100644 --- a/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Index.cshtml +++ b/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Index.cshtml @@ -28,7 +28,7 @@

    @L["Blogs"]

    - @if (await Authorization.IsGrantedAsync(BloggingAdminPermissions.Blogs.Create)) + @if (await Authorization.IsGrantedAsync(BloggingPermissions.Blogs.Create)) { } diff --git a/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Index.cshtml.cs b/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Index.cshtml.cs index 22a09389ef..ebeca3425d 100644 --- a/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Index.cshtml.cs +++ b/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/Index.cshtml.cs @@ -15,7 +15,7 @@ namespace Volo.Blogging.Admin.Pages.Blogging.Admin.Blogs public virtual async Task OnGetAsync() { - if (!await _authorization.IsGrantedAsync(BloggingAdminPermissions.Blogs.Management)) + if (!await _authorization.IsGrantedAsync(BloggingPermissions.Blogs.Management)) { return Redirect("/"); } diff --git a/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/create.js b/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/create.js index 276523c14d..599b2ad1f2 100644 --- a/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/create.js +++ b/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/create.js @@ -3,12 +3,10 @@ $(function () { abp.modals.blogCreate = function () { var initModal = function (publicApi, args) { var $form = publicApi.getForm(); - - }; return { - initModal: initModal - } + initModal: initModal, + }; }; -}); \ No newline at end of file +}); diff --git a/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/edit.js b/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/edit.js index 8d3cffb6c8..a459f7ff09 100644 --- a/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/edit.js +++ b/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/edit.js @@ -3,12 +3,10 @@ $(function () { abp.modals.blogEdit = function () { var initModal = function (publicApi, args) { var $form = publicApi.getForm(); - - }; return { - initModal: initModal - } + initModal: initModal, + }; }; -}); \ No newline at end of file +}); diff --git a/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/index.js b/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/index.js index a89f4cf995..fe1da6c3a9 100644 --- a/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/index.js +++ b/modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/index.js @@ -1,73 +1,84 @@ $(function () { - var l = abp.localization.getResource('Blogging'); - var _createModal = new abp.ModalManager(abp.appPath + 'Blogging/Admin/Blogs/Create'); - var _editModal = new abp.ModalManager(abp.appPath + 'Blogging/Admin/Blogs/Edit'); + var _createModal = new abp.ModalManager( + abp.appPath + 'Blogging/Admin/Blogs/Create' + ); + var _editModal = new abp.ModalManager( + abp.appPath + 'Blogging/Admin/Blogs/Edit' + ); - var _dataTable = $('#BlogsTable').DataTable(abp.libs.datatables.normalizeConfiguration({ - processing: true, - serverSide: true, - paging: false, - info: false, - scrollX: true, - searching: false, - autoWidth: false, - scrollCollapse: true, - order: [[3, "desc"]], - ajax: abp.libs.datatables.createAjax(volo.blogging.admin.blogManagement.getList), - columnDefs: [ - { - rowAction: { - items: - [ + var _dataTable = $('#BlogsTable').DataTable( + abp.libs.datatables.normalizeConfiguration({ + processing: true, + serverSide: true, + paging: false, + info: false, + scrollX: true, + searching: false, + autoWidth: false, + scrollCollapse: true, + order: [[3, 'desc']], + ajax: abp.libs.datatables.createAjax( + volo.blogging.admin.blogManagement.getList + ), + columnDefs: [ + { + rowAction: { + items: [ { text: l('Edit'), - visible: abp.auth.isGranted('Blogging.Admin.Blog.Update'), + visible: abp.auth.isGranted( + 'Blogging.Blog.Update' + ), action: function (data) { _editModal.open({ - blogId: data.record.id + blogId: data.record.id, }); - } + }, }, { text: l('Delete'), - visible: abp.auth.isGranted('Blogging.Admin.Blog.Delete'), - confirmMessage: function (data) { return l('BlogDeletionWarningMessage') }, + visible: abp.auth.isGranted( + 'Blogging.Blog.Delete' + ), + confirmMessage: function (data) { + return l('BlogDeletionWarningMessage'); + }, action: function (data) { volo.blogging.admin.blogManagement .delete(data.record.id) .then(function () { _dataTable.ajax.reload(); }); - } - } - ] - } - }, - { - target: 1, - data: "name" - }, - { - target: 2, - data: "shortName" - }, - { - target: 3, - data: "creationTime", - render: function (date) { - return date; - } - }, - { - target: 4, - data: "description" - } - ] - })); - + }, + }, + ], + }, + }, + { + target: 1, + data: 'name', + }, + { + target: 2, + data: 'shortName', + }, + { + target: 3, + data: 'creationTime', + render: function (date) { + return date; + }, + }, + { + target: 4, + data: 'description', + }, + ], + }) + ); - $("#CreateNewBlogButtonId").click(function () { + $('#CreateNewBlogButtonId').click(function () { _createModal.open(); }); @@ -78,5 +89,4 @@ _editModal.onResult(function () { _dataTable.ajax.reload(); }); - }); diff --git a/modules/blogging/src/Volo.Blogging.Admin.Web/Volo.Blogging.Admin.Web.csproj b/modules/blogging/src/Volo.Blogging.Admin.Web/Volo.Blogging.Admin.Web.csproj index fdbe0dd150..16520deaf6 100644 --- a/modules/blogging/src/Volo.Blogging.Admin.Web/Volo.Blogging.Admin.Web.csproj +++ b/modules/blogging/src/Volo.Blogging.Admin.Web/Volo.Blogging.Admin.Web.csproj @@ -10,6 +10,7 @@ 2.8 Library true + true Volo.Blogging.Admin @@ -22,6 +23,10 @@ + + + + diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/FodyWeavers.xml b/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/FodyWeavers.xml new file mode 100644 index 0000000000..00e1d9a1c1 --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/FodyWeavers.xsd b/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/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/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo.Blogging.Application.Contracts.Shared.csproj b/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo.Blogging.Application.Contracts.Shared.csproj new file mode 100644 index 0000000000..70f0218a37 --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo.Blogging.Application.Contracts.Shared.csproj @@ -0,0 +1,18 @@ + + + + + + + netstandard2.0 + Volo.Blogging.Application.Contracts.Shared + Volo.Blogging.Application.Contracts.Shared + + + + + + + + + diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingApplicationContractsSharedModule.cs b/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingApplicationContractsSharedModule.cs new file mode 100644 index 0000000000..f90c504c0e --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingApplicationContractsSharedModule.cs @@ -0,0 +1,15 @@ +using Volo.Abp.Application; +using Volo.Abp.Modularity; + +namespace Volo.Blogging +{ + [DependsOn(typeof(BloggingDomainSharedModule), + typeof(AbpDddApplicationModule))] + public class BloggingApplicationContractsSharedModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + + } + } +} diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissionDefinitionProvider.cs b/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissionDefinitionProvider.cs new file mode 100644 index 0000000000..65a6ab1ab7 --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissionDefinitionProvider.cs @@ -0,0 +1,40 @@ +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Localization; +using Volo.Blogging.Localization; + +namespace Volo.Blogging +{ + public class BloggingPermissionDefinitionProvider : PermissionDefinitionProvider + { + public override void Define(IPermissionDefinitionContext context) + { + var bloggingGroup = context.AddGroup(BloggingPermissions.GroupName, L("Permission:Blogging")); + + var blogs = bloggingGroup.AddPermission(BloggingPermissions.Blogs.Default, L("Permission:Blogs")); + blogs.AddChild(BloggingPermissions.Blogs.Management, L("Permission:Management")); + blogs.AddChild(BloggingPermissions.Blogs.Update, L("Permission:Edit")); + blogs.AddChild(BloggingPermissions.Blogs.Delete, L("Permission:Delete")); + blogs.AddChild(BloggingPermissions.Blogs.Create, L("Permission:Create")); + + var posts = bloggingGroup.AddPermission(BloggingPermissions.Posts.Default, L("Permission:Posts")); + posts.AddChild(BloggingPermissions.Posts.Update, L("Permission:Edit")); + posts.AddChild(BloggingPermissions.Posts.Delete, L("Permission:Delete")); + posts.AddChild(BloggingPermissions.Posts.Create, L("Permission:Create")); + + var tags = bloggingGroup.AddPermission(BloggingPermissions.Tags.Default, L("Permission:Tags")); + tags.AddChild(BloggingPermissions.Tags.Update, L("Permission:Edit")); + tags.AddChild(BloggingPermissions.Tags.Delete, L("Permission:Delete")); + tags.AddChild(BloggingPermissions.Tags.Create, L("Permission:Create")); + + var comments = bloggingGroup.AddPermission(BloggingPermissions.Comments.Default, L("Permission:Comments")); + comments.AddChild(BloggingPermissions.Comments.Update, L("Permission:Edit")); + comments.AddChild(BloggingPermissions.Comments.Delete, L("Permission:Delete")); + comments.AddChild(BloggingPermissions.Comments.Create, L("Permission:Create")); + } + + private static LocalizableString L(string name) + { + return LocalizableString.Create(name); + } + } +} diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissions.cs b/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissions.cs new file mode 100644 index 0000000000..fae85acf88 --- /dev/null +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts.Shared/Volo/Blogging/BloggingPermissions.cs @@ -0,0 +1,47 @@ +using Volo.Abp.Reflection; + +namespace Volo.Blogging +{ + public class BloggingPermissions + { + public const string GroupName = "Blogging"; + + public static class Blogs + { + public const string Default = GroupName + ".Blog"; + public const string Management = Default + ".Management"; + public const string Delete = Default + ".Delete"; + public const string Update = Default + ".Update"; + public const string Create = Default + ".Create"; + } + + public static class Posts + { + public const string Default = GroupName + ".Post"; + public const string Delete = Default + ".Delete"; + public const string Update = Default + ".Update"; + public const string Create = Default + ".Create"; + } + + public static class Tags + { + public const string Default = GroupName + ".Tag"; + public const string Delete = Default + ".Delete"; + public const string Update = Default + ".Update"; + public const string Create = Default + ".Create"; + } + + public static class Comments + { + public const string Default = GroupName + ".Comment"; + public const string Delete = Default + ".Delete"; + public const string Update = Default + ".Update"; + public const string Create = Default + ".Create"; + } + + public static string[] GetAll() + { + return ReflectionHelper.GetPublicConstantsRecursively(typeof(BloggingPermissions)); + } + } +} diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo.Blogging.Application.Contracts.csproj b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo.Blogging.Application.Contracts.csproj index ba9aa67e7a..2483d728b4 100644 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo.Blogging.Application.Contracts.csproj +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo.Blogging.Application.Contracts.csproj @@ -7,16 +7,12 @@ netstandard2.0 Volo.Blogging.Application.Contracts Volo.Blogging.Application.Contracts + true - - + - - - - diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingApplicationContractsModule.cs b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingApplicationContractsModule.cs index 74b675187d..641659a7c0 100644 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingApplicationContractsModule.cs +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingApplicationContractsModule.cs @@ -5,21 +5,12 @@ using Volo.Blogging.Localization; namespace Volo.Blogging { - [DependsOn(typeof(BloggingDomainSharedModule))] + [DependsOn(typeof(BloggingApplicationContractsSharedModule))] public class BloggingApplicationContractsModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { - Configure(options => - { - options.FileSets.AddEmbedded(); - }); - Configure(options => - { - options.Resources - .Get() - .AddVirtualJson("Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts"); - }); + } } } diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingPermissionDefinitionProvider.cs b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingPermissionDefinitionProvider.cs deleted file mode 100644 index 6c2361230d..0000000000 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingPermissionDefinitionProvider.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Volo.Abp.Authorization.Permissions; -using Volo.Abp.Localization; -using Volo.Blogging.Localization; - -namespace Volo.Blogging -{ - public class BloggingPermissionDefinitionProvider : PermissionDefinitionProvider - { - public override void Define(IPermissionDefinitionContext context) - { - var bloggingGroup = context.AddGroup(BloggingPermissions.GroupName, L("Permission:Blogging")); - - var posts = bloggingGroup.AddPermission(BloggingPermissions.Posts.Default, L("Permission:Posts")); - posts.AddChild(BloggingPermissions.Posts.Update, L("Permission:Edit")); - posts.AddChild(BloggingPermissions.Posts.Delete, L("Permission:Delete")); - posts.AddChild(BloggingPermissions.Posts.Create, L("Permission:Create")); - - var tags = bloggingGroup.AddPermission(BloggingPermissions.Tags.Default, L("Permission:Tags")); - tags.AddChild(BloggingPermissions.Tags.Update, L("Permission:Edit")); - tags.AddChild(BloggingPermissions.Tags.Delete, L("Permission:Delete")); - tags.AddChild(BloggingPermissions.Tags.Create, L("Permission:Create")); - - var comments = bloggingGroup.AddPermission(BloggingPermissions.Comments.Default, L("Permission:Comments")); - comments.AddChild(BloggingPermissions.Comments.Update, L("Permission:Edit")); - comments.AddChild(BloggingPermissions.Comments.Delete, L("Permission:Delete")); - comments.AddChild(BloggingPermissions.Comments.Create, L("Permission:Create")); - } - - private static LocalizableString L(string name) - { - return LocalizableString.Create(name); - } - } -} diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingPermissions.cs b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingPermissions.cs deleted file mode 100644 index 7aa05452e3..0000000000 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingPermissions.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Volo.Abp.Reflection; - -namespace Volo.Blogging -{ - public class BloggingPermissions - { - public const string GroupName = "Blogging"; - - public static class Posts - { - public const string Default = GroupName + ".Post"; - public const string Delete = Default + ".Delete"; - public const string Update = Default + ".Update"; - public const string Create = Default + ".Create"; - } - - public static class Tags - { - public const string Default = GroupName + ".Tag"; - public const string Delete = Default + ".Delete"; - public const string Update = Default + ".Update"; - public const string Create = Default + ".Create"; - } - - public static class Comments - { - public const string Default = GroupName + ".Comment"; - public const string Delete = Default + ".Delete"; - public const string Update = Default + ".Update"; - public const string Create = Default + ".Create"; - } - - public static string[] GetAll() - { - return ReflectionHelper.GetPublicConstantsRecursively(typeof(BloggingPermissions)); - } - } -} diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/cs.json b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/cs.json deleted file mode 100644 index 30063c1d6d..0000000000 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/cs.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "culture": "cs", - "texts": { - "Permission:Blogging": "Blog", - "Permission:Blogs": "Blogy", - "Permission:Posts": "Příspěvky", - "Permission:Tags": "Tagy", - "Permission:Comments": "Komentáře" - } -} diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/de.json b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/de.json deleted file mode 100644 index 9f2a7d4510..0000000000 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/de.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "culture": "de", - "texts": { - "Permission:Blogging": "Blog", - "Permission:Blogs": "Blogs", - "Permission:Posts": "Beitr�ge", - "Permission:Tags": "Tags", - "Permission:Comments": "Kommentare" - } -} diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/en.json b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/en.json deleted file mode 100644 index eb70a98276..0000000000 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/en.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "culture": "en", - "texts": { - "Permission:Blogging": "Blog", - "Permission:Blogs": "Blogs", - "Permission:Posts": "Posts", - "Permission:Tags": "Tags", - "Permission:Comments": "Comments" - } -} diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/nl.json b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/nl.json deleted file mode 100644 index 70440c8470..0000000000 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/nl.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "culture": "nl", - "texts": { - "Permission:Blogging": "Blog", - "Permission:Blogs": "Blogs", - "Permission:Posts": "Posts", - "Permission:Tags": "Tags", - "Permission:Comments": "Kommentaar" - } -} diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/pl-PL.json b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/pl-PL.json deleted file mode 100644 index 2a223d536b..0000000000 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/pl-PL.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "culture": "pl-PL", - "texts": { - "Permission:Blogging": "Blog", - "Permission:Blogs": "Blogi", - "Permission:Posts": "Posty", - "Permission:Tags": "Tagi", - "Permission:Comments": "Komentarze" - } -} diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/pt-BR.json b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/pt-BR.json deleted file mode 100644 index 9dd2977079..0000000000 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/pt-BR.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "culture": "pt-BR", - "texts": { - "Permission:Blogging": "Blog", - "Permission:Blogs": "Blogs", - "Permission:Posts": "Postagens", - "Permission:Tags": "Etiquetas", - "Permission:Comments": "Comentários" - } -} diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/sl.json b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/sl.json deleted file mode 100644 index cad2be999f..0000000000 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/sl.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "culture": "sl", - "texts": { - "Permission:Blogging": "Blog", - "Permission:Blogs": "Blogi", - "Permission:Posts": "Objave", - "Permission:Tags": "Oznake", - "Permission:Comments": "Komentarji" - } -} diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/tr.json b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/tr.json deleted file mode 100644 index 1d158bc664..0000000000 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/tr.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "culture": "tr", - "texts": { - "Permission:Blogging": "Blog", - "Permission:Blogs": "Bloglar", - "Permission:Posts": "Yazılar", - "Permission:Tags": "Etiketler", - "Permission:Comments": "Yorumlar" - } -} diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/vi.json b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/vi.json deleted file mode 100644 index 2f65d48fae..0000000000 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/vi.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "culture": "vi", - "texts": { - "Permission:Blogging": "Blog", - "Permission:Blogs": "Blogs", - "Permission:Posts": "Bài viết", - "Permission:Tags": "Thẻ", - "Permission:Comments": "Bình luận" - } -} diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/zh-Hans.json b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/zh-Hans.json deleted file mode 100644 index afc509cd35..0000000000 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/zh-Hans.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "culture": "zh-Hans", - "texts": { - "Permission:Blogging": "博客", - "Permission:Blogs": "博客", - "Permission:Posts": "帖子", - "Permission:Tags": "标签", - "Permission:Comments": "评论" - } -} diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/zh-Hant.json b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/zh-Hant.json deleted file mode 100644 index 487be120e5..0000000000 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/zh-Hant.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "culture": "zh-Hant", - "texts": { - "Permission:Blogging": "部落格", - "Permission:Blogs": "部落格", - "Permission:Posts": "文章", - "Permission:Tags": "標籤", - "Permission:Comments": "評論" - } -} diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo.Blogging.Domain.Shared.csproj b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo.Blogging.Domain.Shared.csproj index da706316f9..ca7dcca8a2 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo.Blogging.Domain.Shared.csproj +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo.Blogging.Domain.Shared.csproj @@ -7,6 +7,7 @@ netstandard2.0 Volo.Blogging.Domain.Shared Volo.Blogging.Domain.Shared + true @@ -19,4 +20,8 @@ + + + + diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/cs.json b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/cs.json index 2761ca0278..917050d28d 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/cs.json +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/cs.json @@ -2,7 +2,16 @@ "culture": "cs", "texts": { "Menu:Blogs": "Blogy", - "Menu:BlogManagement": "Správa blogu", + "Menu:BlogManagement": "Blogování", + "Permission:Blogs": "Blogy", + "Permission:Management": "Správa", + "Permission:Edit": "Upravit", + "Permission:Create": "Vytvořit", + "Permission:Delete": "Smazat", + "Permission:Blogging": "Blog", + "Permission:Posts": "Příspěvky", + "Permission:Tags": "Tagy", + "Permission:Comments": "Komentáře", "Title": "Nadpis", "Delete": "Smazat", "Reply": "Odpovědět", diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/de.json b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/de.json index 6489396692..a6e9215496 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/de.json +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/de.json @@ -3,8 +3,17 @@ "texts": { "Menu:Blogs": "Blogs", "Menu:BlogManagement": "Blog-Verwaltung", + "Permission:Blogs": "Blogs", + "Permission:Management": "Verwaltung", + "Permission:Edit": "Bearbeiten", + "Permission:Create": "Erstellen", + "Permission:Delete": "Löschen", + "Permission:Blogging": "Blog", + "Permission:Posts": "Beiträge", + "Permission:Tags": "Tags", + "Permission:Comments": "Kommentare", "Title": "Titel", - "Delete": "Löschen", + "Delete": "Löschen", "Reply": "Antwort", "ReplyTo": "Antwort auf {0}", "ContinueReading": "Weiterlesen", @@ -20,15 +29,15 @@ "SeeAll": "Alle anzeigen", "PopularTags": "Beliebte Tags", "WiewsWithCount": "{0} Aufrufe", - "LastPosts": "Letzte Beiträge", + "LastPosts": "Letzte Beiträge", "LeaveComment": "Kommentar hinterlassen", "TagsInThisArticle": "Tags in diesem Artikel", - "Posts": "Beiträge", + "Posts": "Beiträge", "Edit": "Bearbeiten", "BLOG": "BLOG", - "CommentDeletionWarningMessage": "Kommentar wird gelöscht.", - "PostDeletionWarningMessage": "Beitrag wird gelöscht.", - "BlogDeletionWarningMessage": "Blog wird gelöscht.", + "CommentDeletionWarningMessage": "Kommentar wird gelöscht.", + "PostDeletionWarningMessage": "Beitrag wird gelöscht.", + "BlogDeletionWarningMessage": "Blog wird gelöscht.", "AreYouSure": "Sind Sie sicher?", "CommentWithCount": "{0} Kommentare", "Comment": "Kommentar", @@ -46,4 +55,4 @@ "ShareOn": "Teilen auf", "TitleLengthWarning": "Halten Sie Ihren Titel unter 60 Zeichen, um SEO-freundlich zu sein!" } -} \ No newline at end of file +} diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json index 8ffe3d8fa7..5ebdf25d9f 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json @@ -3,6 +3,15 @@ "texts": { "Menu:Blogs": "Blogs", "Menu:BlogManagement": "Blogging", + "Permission:Management": "Management", + "Permission:Edit": "Edit", + "Permission:Create": "Create", + "Permission:Delete": "Delete", + "Permission:Blogging": "Blog", + "Permission:Blogs": "Blogs", + "Permission:Posts": "Posts", + "Permission:Tags": "Tags", + "Permission:Comments": "Comments", "Title": "Title", "Delete": "Delete", "Reply": "Reply", diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/nl.json b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/nl.json index 6548e4c0a8..a1c0b72de8 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/nl.json +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/nl.json @@ -3,6 +3,15 @@ "texts": { "Menu:Blogs": "Blogs", "Menu:BlogManagement": "Blog Beheer", + "Permission:Management": "Beheer", + "Permission:Edit": "Bewerk", + "Permission:Create": "Maak aan", + "Permission:Delete": "Verwijder", + "Permission:Blogging": "Blog", + "Permission:Blogs": "Blogs", + "Permission:Posts": "Posts", + "Permission:Tags": "Tags", + "Permission:Comments": "Kommentaar", "Title": "Titel", "Delete": "Verwijder", "Reply": "Antwoord", @@ -46,4 +55,4 @@ "ShareOn": "Delen op", "TitleLengthWarning": "Houd uw titel kleiner dan 60 tekens om SEO-vriendelijk te zijn!" } -} \ No newline at end of file +} diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pl-PL.json b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pl-PL.json index 3795ab93d3..5e6fd41c36 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pl-PL.json +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pl-PL.json @@ -3,6 +3,15 @@ "texts": { "Menu:Blogs": "Blogi", "Menu:BlogManagement": "Zarządzanie blogiem", + "Permission:Management": "Zarządzanie", + "Permission:Edit": "Edytuj", + "Permission:Create": "Utwórz", + "Permission:Delete": "Usuń", + "Permission:Blogging": "Blog", + "Permission:Blogs": "Blogi", + "Permission:Posts": "Posty", + "Permission:Tags": "Tagi", + "Permission:Comments": "Komentarze", "Title": "Tytuł", "Delete": "Usuń", "Reply": "Odpowiedz", @@ -44,4 +53,4 @@ "Blogs": "Blogi", "Tags": "Tagi" } -} \ No newline at end of file +} diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pt-BR.json b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pt-BR.json index 62416f49a5..a5fb209b0a 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pt-BR.json +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/pt-BR.json @@ -3,6 +3,15 @@ "texts": { "Menu:Blogs": "Blogs", "Menu:BlogManagement": "Gerenciar Blog", + "Permission:Management": "Gerenciamento", + "Permission:Edit": "Editar", + "Permission:Create": "Criar", + "Permission:Delete": "Excluir", + "Permission:Blogging": "Blog", + "Permission:Blogs": "Blogs", + "Permission:Posts": "Postagens", + "Permission:Tags": "Etiquetas", + "Permission:Comments": "Comentários", "Title": "Título", "Delete": "Excluir", "Reply": "Responder", @@ -44,4 +53,4 @@ "Blogs": "Blogs", "Tags": "Etiquetas" } -} \ No newline at end of file +} diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/sl.json b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/sl.json index 74672336f5..6946cefc55 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/sl.json +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/sl.json @@ -3,6 +3,15 @@ "texts": { "Menu:Blogs": "Blogi", "Menu:BlogManagement": "Upravljanje bloga", + "Permission:Management": "Upravljanje", + "Permission:Edit": "Urejanje", + "Permission:Create": "Ustvarjanje", + "Permission:Delete": "Brisanje", + "Permission:Blogging": "Blog", + "Permission:Blogs": "Blogi", + "Permission:Posts": "Objave", + "Permission:Tags": "Oznake", + "Permission:Comments": "Komentarji", "Title": "Naslov", "Delete": "Izbriši", "Reply": "Odgovori", @@ -44,4 +53,4 @@ "Blogs": "Blogi", "Tags": "Oznake" } -} \ No newline at end of file +} diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/tr.json b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/tr.json index 78e18601ff..4a9a38b040 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/tr.json +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/tr.json @@ -3,6 +3,15 @@ "texts": { "Menu:Blogs": "Bloglar", "Menu:BlogManagement": "Blog Yönetimi", + "Permission:Management": "Yönetme", + "Permission:Edit": "Düzenle", + "Permission:Create": "Ekle", + "Permission:Delete": "Sil", + "Permission:Blogging": "Blog", + "Permission:Blogs": "Bloglar", + "Permission:Posts": "Yazılar", + "Permission:Tags": "Etiketler", + "Permission:Comments": "Yorumlar", "Title": "Başlık", "Delete": "Sil", "Reply": "Yanıtla", @@ -46,4 +55,4 @@ "ShareOn": "Paylaş", "TitleLengthWarning": "Başlığınınz SEO dostu olabilmesi için 60 karakterden az olmasını sağlayın!" } -} \ No newline at end of file +} diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/vi.json b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/vi.json index 1405337271..28b832d87e 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/vi.json +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/vi.json @@ -3,6 +3,15 @@ "texts": { "Menu:Blogs": "Blogs", "Menu:BlogManagement": "Quản lý Blog", + "Permission:Management": "Quản lý", + "Permission:Edit": "Sửa", + "Permission:Create": "Tạo", + "Permission:Delete": "Xóa", + "Permission:Blogging": "Blog", + "Permission:Blogs": "Blogs", + "Permission:Posts": "Bài viết", + "Permission:Tags": "Thẻ", + "Permission:Comments": "Bình luận", "Title": "Tiêu đề", "Delete": "Xóa", "Reply": "Trả lời", diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/zh-Hans.json b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/zh-Hans.json index 2ea614b648..4b94db0607 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/zh-Hans.json +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/zh-Hans.json @@ -3,6 +3,15 @@ "texts": { "Menu:Blogs": "博客", "Menu:BlogManagement": "博客管理", + "Permission:Management": "管理", + "Permission:Edit": "编辑", + "Permission:Create": "创建", + "Permission:Delete": "删除", + "Permission:Blogging": "博客", + "Permission:Blogs": "博客", + "Permission:Posts": "帖子", + "Permission:Tags": "标签", + "Permission:Comments": "评论", "Title": "标题", "Delete": "删除", "Reply": "回复", @@ -44,4 +53,4 @@ "Blogs": "博客", "Tags": "标签" } -} \ No newline at end of file +} diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/zh-Hant.json b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/zh-Hant.json index b5781d305e..f51f088be5 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/zh-Hant.json +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/zh-Hant.json @@ -3,6 +3,15 @@ "texts": { "Menu:Blogs": "部落格", "Menu:BlogManagement": "部落格管理", + "Permission:Management": "管理", + "Permission:Edit": "標及", + "Permission:Create": "新增", + "Permission:Delete": "刪除", + "Permission:Blogging": "部落格", + "Permission:Blogs": "部落格", + "Permission:Posts": "文章", + "Permission:Tags": "標籤", + "Permission:Comments": "評論", "Title": "標題", "Delete": "刪除", "Reply": "回覆", @@ -46,4 +55,4 @@ "ShareOn": "分享在", "TitleLengthWarning": "為了優化搜索引擎,標題建議保持在60個字元以內" } -} \ No newline at end of file +} diff --git a/modules/blogging/src/Volo.Blogging.Web/BloggingWebModule.cs b/modules/blogging/src/Volo.Blogging.Web/BloggingWebModule.cs index e238f48a37..24b92848da 100644 --- a/modules/blogging/src/Volo.Blogging.Web/BloggingWebModule.cs +++ b/modules/blogging/src/Volo.Blogging.Web/BloggingWebModule.cs @@ -39,7 +39,7 @@ namespace Volo.Blogging { Configure(options => { - options.FileSets.AddEmbedded("Volo.Blogging"); + options.FileSets.AddEmbedded(); }); context.Services.AddAutoMapperObjectMapper(); diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml index 6909b6526b..1aa239efd7 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml @@ -45,6 +45,7 @@
    + *
    @@ -79,7 +80,7 @@
    diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/New.cshtml b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/New.cshtml index bcb41d8fc7..d57fd1b772 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/New.cshtml +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/New.cshtml @@ -45,6 +45,7 @@
    + *
    @@ -77,7 +78,7 @@
    diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/detail.js b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/detail.js index 571484bdac..debd2941c9 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/detail.js +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/detail.js @@ -1,32 +1,47 @@ (function ($) { - var l = abp.localization.getResource('Blogging'); var initSocialShareLinks = function () { - var re = new RegExp(/^.*\//); var rootUrl = re.exec(window.location.href); - var pageHeader = $("#PostTitle").text(); + var pageHeader = $('#PostTitle').text(); var blogName = $('#BlogFullName').attr('name'); - $('#TwitterShareLink').attr('href', - 'https://twitter.com/intent/tweet?text=' + encodeURI(pageHeader + " | " + blogName + " | " + window.location.href) + $('#TwitterShareLink').attr( + 'href', + 'https://twitter.com/intent/tweet?text=' + + encodeURI( + pageHeader + ' | ' + blogName + ' | ' + window.location.href + ) ); - $('#LinkedinShareLink').attr('href', - 'https://www.linkedin.com/shareArticle?' - + 'url=' + encodeURI(window.location.href) + '&' - + 'mini=true&' - + "summary=" + encodeURI(blogName) + '&' - + "title=" + encodeURI(pageHeader) + '&' - + "source=" + encodeURI(rootUrl) + $('#LinkedinShareLink').attr( + 'href', + 'https://www.linkedin.com/shareArticle?' + + 'url=' + + encodeURI(window.location.href) + + '&' + + 'mini=true&' + + 'summary=' + + encodeURI(blogName) + + '&' + + 'title=' + + encodeURI(pageHeader) + + '&' + + 'source=' + + encodeURI(rootUrl) ); - $('#EmailShareLink').attr('href', - 'mailto:?' - + 'body=' + encodeURI('I want you to look at ' + window.location.href) + '&' - + "subject=" + encodeURI(pageHeader + ' | ' + blogName) + '&' + $('#EmailShareLink').attr( + 'href', + 'mailto:?' + + 'body=' + + encodeURI('I want you to look at ' + window.location.href) + + '&' + + 'subject=' + + encodeURI(pageHeader + ' | ' + blogName) + + '&' ); }; @@ -39,18 +54,18 @@ var form = $(this).serializeFormToObject(); $.ajax({ - type: "POST", - url: "/Blog/Comments/Update", + type: 'POST', + url: '/Blog/Comments/Update', data: { id: form.commentId, commentDto: { - text: form.text - } + text: form.text, + }, }, success: function (response) { $('div .editForm').hide(); $('#' + form.commentId).text(form.text); - } + }, }); }); @@ -73,7 +88,7 @@ if (replyCommentId != '' && replyCommentId !== undefined) { var div = linkElement.parent().next(); - if (div.is(":hidden")) { + if (div.is(':hidden')) { $('div .replyForm').hide(); div.show(); } else { @@ -95,12 +110,12 @@ function (isConfirmed) { if (isConfirmed) { $.ajax({ - type: "POST", - url: "/Blog/Comments/Delete", + type: 'POST', + url: '/Blog/Comments/Delete', data: { id: deleteCommentId }, success: function (response) { linkElement.parent().parent().parent().remove(); - } + }, }); } } @@ -121,14 +136,16 @@ function (isConfirmed) { if (isConfirmed) { $.ajax({ - type: "POST", - url: "/Blog/Posts/Delete", + type: 'POST', + url: '/Blog/Posts/Delete', data: { id: deleteCommentId }, success: function () { var url = window.location.pathname; var postNameBeginning = url.lastIndexOf('/'); - window.location.replace(url.substring(0, postNameBeginning)); - } + window.location.replace( + url.substring(0, postNameBeginning) + ); + }, }); } } @@ -144,10 +161,9 @@ var updateCommentId = $(this).attr('data-updateid'); if (updateCommentId != '' && updateCommentId !== undefined) { - var div = linkElement.parent().next().next(); - if (div.is(":hidden")) { + if (div.is(':hidden')) { $('div .editForm').hide(); div.show(); } else { @@ -158,13 +174,16 @@ }); if ($('#FocusCommentId').val() != '00000000-0000-0000-0000-000000000000') { - $('html, body').animate({ - scrollTop: ($('#' + $('#FocusCommentId').val()).offset().top - 150) - }, 500); + $('html, body').animate( + { + scrollTop: + $('#' + $('#FocusCommentId').val()).offset().top - 150, + }, + 500 + ); } $(".post-content a[href^='http']").attr('target', '_blank'); initSocialShareLinks(); - })(jQuery); diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/edit.js b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/edit.js index e02ec975fe..718d5c4bc4 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/edit.js +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/edit.js @@ -1,22 +1,22 @@ $(function () { - - var $container = $("#edit-post-container"); - var $editorContainer = $container.find(".edit-post-editor"); - var $submitButton = $container.find("button[type=submit]"); - var $form = $container.find("form#edit-post-form"); - var editorDataKey = "tuiEditor"; - var $titleLengthWarning = $("#title-length-warning"); - var maxTitleLength = parseInt($titleLengthWarning.data("max-length")); + var $container = $('#edit-post-container'); + var $editorContainer = $container.find('.edit-post-editor'); + var $submitButton = $container.find('button[type=submit]'); + var $form = $container.find('form#edit-post-form'); + var editorDataKey = 'tuiEditor'; + var $titleLengthWarning = $('#title-length-warning'); + var maxTitleLength = parseInt($titleLengthWarning.data('max-length')); var $title = $('#Post_Title'); - var $coverImage = $("#CoverImage"); + var $coverImage = $('#CoverImage'); var $postCoverImage = $('#Post_CoverImage'); var $coverImageFile = $('#CoverImageFile'); - + var $postFormSubmitButton = $('#PostFormSubmitButton'); var setCoverImage = function (file) { $postCoverImage.val(file.fileUrl); - $coverImage.attr("src", file.fileUrl); + $coverImage.attr('src', file.fileUrl); + $postFormSubmitButton.removeAttr('disabled'); }; var uploadCoverImage = function (file) { @@ -24,14 +24,14 @@ formData.append('file', file); $.ajax({ - type: "POST", - url: "/api/blogging/files/images/upload", + type: 'POST', + url: '/api/blogging/files/images/upload', data: formData, contentType: false, processData: false, success: function (response) { setCoverImage(response); - } + }, }); }; @@ -47,7 +47,7 @@ checkTitleLength(); - $title.on("change paste keyup", function () { + $title.on('change paste keyup', function () { checkTitleLength(); }); @@ -56,54 +56,55 @@ return; } + $postFormSubmitButton.attr('disabled', true); var file = $coverImageFile.prop('files')[0]; uploadCoverImage(file); }); - var uploadImage = function (file, callbackFn) { var formData = new FormData(); formData.append('file', file); $.ajax({ - type: "POST", - url: "/api/blogging/files/images/upload", + type: 'POST', + url: '/api/blogging/files/images/upload', data: formData, contentType: false, processData: false, success: function (response) { callbackFn(response.fileUrl); - } + }, }); }; - var newPostEditor = $editorContainer.tuiEditor({ - usageStatistics: false, - initialEditType: 'markdown', - previewStyle: 'tab', - height: "auto", - initialValue: $form.find("input[name='Post.Content']").val(), - hooks: { - addImageBlobHook: function (blob, callback, source) { - var imageAltText = blob.name; - - uploadImage(blob, - function (fileUrl) { + var newPostEditor = $editorContainer + .tuiEditor({ + usageStatistics: false, + initialEditType: 'markdown', + previewStyle: 'tab', + height: 'auto', + initialValue: $form.find("input[name='Post.Content']").val(), + hooks: { + addImageBlobHook: function (blob, callback, source) { + var imageAltText = blob.name; + + uploadImage(blob, function (fileUrl) { callback(fileUrl, imageAltText); }); - } - }, - events: { - load: function () { - $editorContainer.find(".loading-cover").remove(); - $submitButton.prop("disabled", false); - $form.data("validator").settings.ignore = '.ignore'; - $editorContainer.find(':input').addClass('ignore'); - } - } - }).data(editorDataKey); - - $container.find("form#edit-post-form").submit(function (e) { + }, + }, + events: { + load: function () { + $editorContainer.find('.loading-cover').remove(); + $submitButton.prop('disabled', false); + $form.data('validator').settings.ignore = '.ignore'; + $editorContainer.find(':input').addClass('ignore'); + }, + }, + }) + .data(editorDataKey); + + $container.find('form#edit-post-form').submit(function (e) { var $postTextInput = $form.find("input[name='Post.Content']"); var postText = newPostEditor.getMarkdown(); @@ -113,13 +114,11 @@ var validationResult = $form.validate(); abp.message.warn(validationResult.errorList[0].message); //TODO: errors can be merged into lines. make sweetalert accept HTML. e.preventDefault(); - return false; //for old browsers + return false; //for old browsers } $submitButton.buttonBusy(); $(this).off('submit').submit(); return true; }); - }); - diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/new.js b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/new.js index 5e5c38d895..6b8c827722 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/new.js +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/new.js @@ -1,22 +1,23 @@ $(function () { - - var $container = $("#qa-new-post-container"); - var $editorContainer = $container.find(".new-post-editor"); - var $submitButton = $container.find("button[type=submit]"); - var $form = $container.find("form#new-post-form"); - var editorDataKey = "tuiEditor"; - var $titleLengthWarning = $("#title-length-warning"); - var maxTitleLength = parseInt($titleLengthWarning.data("max-length")); + var $container = $('#qa-new-post-container'); + var $editorContainer = $container.find('.new-post-editor'); + var $submitButton = $container.find('button[type=submit]'); + var $form = $container.find('form#new-post-form'); + var editorDataKey = 'tuiEditor'; + var $titleLengthWarning = $('#title-length-warning'); + var maxTitleLength = parseInt($titleLengthWarning.data('max-length')); var $title = $('#Post_Title'); var $url = $('#Post_Url'); - var $coverImage = $("#CoverImage"); + var $coverImage = $('#CoverImage'); var $postCoverImage = $('#Post_CoverImage'); var $coverImageFile = $('#CoverImageFile'); + var $postFormSubmitButton = $('#PostFormSubmitButton'); var setCoverImage = function (file) { $postCoverImage.val(file.fileUrl); - $coverImage.attr("src", file.fileUrl); + $coverImage.attr('src', file.fileUrl); $coverImage.show(); + $postFormSubmitButton.removeAttr('disabled'); }; var uploadCoverImage = function (file) { @@ -24,14 +25,14 @@ formData.append('file', file); $.ajax({ - type: "POST", - url: "/api/blogging/files/images/upload", + type: 'POST', + url: '/api/blogging/files/images/upload', data: formData, contentType: false, processData: false, success: function (response) { setCoverImage(response); - } + }, }); }; @@ -39,6 +40,7 @@ if (!$coverImageFile.prop('files').length) { return; } + $postFormSubmitButton.attr('disabled', true); var file = $coverImageFile.prop('files')[0]; uploadCoverImage(file); }); @@ -48,43 +50,44 @@ formData.append('file', file); $.ajax({ - type: "POST", - url: "/api/blogging/files/images/upload", + type: 'POST', + url: '/api/blogging/files/images/upload', data: formData, contentType: false, processData: false, success: function (response) { callbackFn(response.fileUrl); - } + }, }); }; - var newPostEditor = $editorContainer.tuiEditor({ - usageStatistics: false, - initialEditType: 'markdown', - previewStyle: 'tab', - height: "auto", - hooks: { - addImageBlobHook: function (blob, callback, source) { - var imageAltText = blob.name; - - uploadImage(blob, - function (fileUrl) { + var newPostEditor = $editorContainer + .tuiEditor({ + usageStatistics: false, + initialEditType: 'markdown', + previewStyle: 'tab', + height: 'auto', + hooks: { + addImageBlobHook: function (blob, callback, source) { + var imageAltText = blob.name; + + uploadImage(blob, function (fileUrl) { callback(fileUrl, imageAltText); }); - } - }, - events: { - load: function () { - $editorContainer.find(".loading-cover").remove(); - $submitButton.prop("disabled", false); - $form.data("validator").settings.ignore = '.ignore'; - $editorContainer.find(':input').addClass('ignore'); - } - } - }).data(editorDataKey); - - $container.find("form#new-post-form").submit(function (e) { + }, + }, + events: { + load: function () { + $editorContainer.find('.loading-cover').remove(); + $submitButton.prop('disabled', false); + $form.data('validator').settings.ignore = '.ignore'; + $editorContainer.find(':input').addClass('ignore'); + }, + }, + }) + .data(editorDataKey); + + $container.find('form#new-post-form').submit(function (e) { var $postTextInput = $form.find("input[name='Post.Content']"); var postText = newPostEditor.getMarkdown(); @@ -105,7 +108,7 @@ var urlEdited = false; var reflectedChange = false; - $title.on("change paste keyup", function () { + $title.on('change paste keyup', function () { if (urlEdited) { return; } @@ -134,5 +137,4 @@ urlEdited = true; } }); - }); diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Shared/Scripts/blog.js b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Shared/Scripts/blog.js index b78c8132aa..ca0b21d203 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Shared/Scripts/blog.js +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Shared/Scripts/blog.js @@ -1,9 +1,9 @@ -(function() { +(function () { function handleArrows() { - var herosWidth = $(".hero-articles").width(); - var arrowsPosition = (herosWidth / 2 - 90); - $(".owl-next").css("right", arrowsPosition); - $(".owl-prev").css("left", arrowsPosition); + var herosWidth = $('.hero-articles').width(); + var arrowsPosition = herosWidth / 2 - 90; + $('.owl-next').css('right', arrowsPosition); + $('.owl-prev').css('left', arrowsPosition); } function handleImages() { @@ -24,7 +24,7 @@ margin: 0, nav: false, dots: false, - //center: true, + //center: true, autoplay: true, autoHeight: true, autoplaySpeed: 1000, @@ -39,9 +39,9 @@ autoplaySpeed: 1000, responsive: { 0: { - items: 1 - } - } + items: 1, + }, + }, }); } @@ -49,7 +49,7 @@ handleImages(); handleOwlCarousel(); - $(".nav-link").on("click", function () { + $('.nav-link').on('click', function () { $(this).parent().parent(); }); @@ -64,4 +64,4 @@ handleArrows(); }, 500); }); -})(); \ No newline at end of file +})(); diff --git a/modules/blogging/src/Volo.Blogging.Web/Volo.Blogging.Web.csproj b/modules/blogging/src/Volo.Blogging.Web/Volo.Blogging.Web.csproj index 203c142de9..511b2b5d39 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Volo.Blogging.Web.csproj +++ b/modules/blogging/src/Volo.Blogging.Web/Volo.Blogging.Web.csproj @@ -10,6 +10,7 @@ 2.8 Library true + true Volo.Blogging @@ -22,6 +23,10 @@ + + + + diff --git a/modules/client-simulation/.prettierrc b/modules/client-simulation/.prettierrc new file mode 100644 index 0000000000..34a0e1ecde --- /dev/null +++ b/modules/client-simulation/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "useTabs": false, + "tabWidth": 4 +} diff --git a/modules/client-simulation/src/Volo.ClientSimulation.Web/Pages/ClientSimulation/Index.js b/modules/client-simulation/src/Volo.ClientSimulation.Web/Pages/ClientSimulation/Index.js index 39a9dc61f7..7c4cf805c1 100644 --- a/modules/client-simulation/src/Volo.ClientSimulation.Web/Pages/ClientSimulation/Index.js +++ b/modules/client-simulation/src/Volo.ClientSimulation.Web/Pages/ClientSimulation/Index.js @@ -2,4 +2,4 @@ $(function () { SimulationArea.init($('#SimulationArea')); }); -})(jQuery); \ No newline at end of file +})(jQuery); diff --git a/modules/client-simulation/src/Volo.ClientSimulation.Web/Pages/ClientSimulation/SimulationArea.js b/modules/client-simulation/src/Volo.ClientSimulation.Web/Pages/ClientSimulation/SimulationArea.js index 24a4d46460..0a404900c9 100644 --- a/modules/client-simulation/src/Volo.ClientSimulation.Web/Pages/ClientSimulation/SimulationArea.js +++ b/modules/client-simulation/src/Volo.ClientSimulation.Web/Pages/ClientSimulation/SimulationArea.js @@ -1,14 +1,13 @@ var SimulationArea = {}; (function ($) { - var $mainContainer = null; function refreshSimulationArea() { $.get('/ClientSimulation/SimulationArea') - .done(function(result) { + .done(function (result) { $mainContainer.html(result); }) - .always(function() { + .always(function () { setTimeout(refreshSimulationArea, 1000); }); } @@ -17,5 +16,4 @@ $mainContainer = $container; setTimeout(refreshSimulationArea, 1000); }; - -})(jQuery); \ No newline at end of file +})(jQuery); diff --git a/modules/cms-kit/.gitattributes b/modules/cms-kit/.gitattributes new file mode 100644 index 0000000000..c941e52669 --- /dev/null +++ b/modules/cms-kit/.gitattributes @@ -0,0 +1 @@ +**/wwwroot/libs/** linguist-vendored diff --git a/modules/cms-kit/.gitignore b/modules/cms-kit/.gitignore new file mode 100644 index 0000000000..e9589dd2d6 --- /dev/null +++ b/modules/cms-kit/.gitignore @@ -0,0 +1,258 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CmsKit +host/Volo.CmsKit.IdentityServer/Logs/logs.txt +host/Volo.CmsKit.HttpApi.Host/Logs/logs.txt +host/Volo.CmsKit.Web.Host/Logs/logs.txt +host/Volo.CmsKit.Web.Unified/Logs/logs.txt diff --git a/modules/cms-kit/.prettierrc b/modules/cms-kit/.prettierrc new file mode 100644 index 0000000000..56af76bd94 --- /dev/null +++ b/modules/cms-kit/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "useTabs": false, + "tabWidth": 4 +} diff --git a/modules/cms-kit/Volo.CmsKit.sln b/modules/cms-kit/Volo.CmsKit.sln new file mode 100644 index 0000000000..ddc1dd2000 --- /dev/null +++ b/modules/cms-kit/Volo.CmsKit.sln @@ -0,0 +1,284 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29001.49 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.CmsKit.Domain.Shared", "src\Volo.CmsKit.Domain.Shared\Volo.CmsKit.Domain.Shared.csproj", "{D64C1577-4929-4B60-939E-96DE1534891A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.CmsKit.Domain", "src\Volo.CmsKit.Domain\Volo.CmsKit.Domain.csproj", "{F2840BC7-0188-4606-9126-DADD0F5ABF7A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.CmsKit.Application.Contracts", "src\Volo.CmsKit.Application.Contracts\Volo.CmsKit.Application.Contracts.csproj", "{BD65D04F-08D5-40C1-8C24-77CA0BACB877}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.CmsKit.Application", "src\Volo.CmsKit.Application\Volo.CmsKit.Application.csproj", "{78040F9E-3501-4A40-82DF-00A597710F35}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{649A3FFA-182F-4E56-9717-E6A9A2BEC545}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{CCD2960C-23CC-4AB4-B84D-60C7AAA52F4D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "host", "host", "{E400416D-2895-4512-9D17-90681EEC7E0A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.CmsKit.EntityFrameworkCore", "src\Volo.CmsKit.EntityFrameworkCore\Volo.CmsKit.EntityFrameworkCore.csproj", "{0CE86223-D31D-4315-A1F5-87BA3EE1B844}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.CmsKit.MongoDB", "src\Volo.CmsKit.MongoDB\Volo.CmsKit.MongoDB.csproj", "{F1C58097-4C08-4D88-8976-6B3389391481}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.CmsKit.HttpApi", "src\Volo.CmsKit.HttpApi\Volo.CmsKit.HttpApi.csproj", "{077AA5F8-8B61-420C-A6B5-0150A66FDB34}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.CmsKit.HttpApi.Client", "src\Volo.CmsKit.HttpApi.Client\Volo.CmsKit.HttpApi.Client.csproj", "{36E2735F-CEAB-44C8-A6D1-2CDAFF399751}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.CmsKit.TestBase", "test\Volo.CmsKit.TestBase\Volo.CmsKit.TestBase.csproj", "{C5BB573D-3030-4BCB-88B7-F6A85C32766C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.CmsKit.EntityFrameworkCore.Tests", "test\Volo.CmsKit.EntityFrameworkCore.Tests\Volo.CmsKit.EntityFrameworkCore.Tests.csproj", "{527F645C-C1FC-406E-8479-81386C8ECF13}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.CmsKit.MongoDB.Tests", "test\Volo.CmsKit.MongoDB.Tests\Volo.CmsKit.MongoDB.Tests.csproj", "{D0AD9179-125C-40B2-A8EE-CD4C1EE24BB6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.CmsKit.Domain.Tests", "test\Volo.CmsKit.Domain.Tests\Volo.CmsKit.Domain.Tests.csproj", "{E60895E5-79C4-447D-88B7-85CB5BA336A4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.CmsKit.Application.Tests", "test\Volo.CmsKit.Application.Tests\Volo.CmsKit.Application.Tests.csproj", "{90CB5DC4-C040-45C7-8900-9688B26405BC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.CmsKit.HttpApi.Host", "host\Volo.CmsKit.HttpApi.Host\Volo.CmsKit.HttpApi.Host.csproj", "{37B135B0-DAFE-4616-B25C-1BDF32FC44A2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.CmsKit.Web", "src\Volo.CmsKit.Web\Volo.CmsKit.Web.csproj", "{3B7B6317-1B85-4164-8E11-75574F80AE17}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.CmsKit.HttpApi.Client.ConsoleTestApp", "test\Volo.CmsKit.HttpApi.Client.ConsoleTestApp\Volo.CmsKit.HttpApi.Client.ConsoleTestApp.csproj", "{1EDCD6D4-DF3A-4E3B-ABB6-C0D0B373EAB8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.CmsKit.Web.Host", "host\Volo.CmsKit.Web.Host\Volo.CmsKit.Web.Host.csproj", "{73513786-B6C6-4A21-89C5-0FBDD0A46107}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.CmsKit.IdentityServer", "host\Volo.CmsKit.IdentityServer\Volo.CmsKit.IdentityServer.csproj", "{690203F4-3CD5-4569-88D9-EE831EEA5F5F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.CmsKit.Host.Shared", "host\Volo.CmsKit.Host.Shared\Volo.CmsKit.Host.Shared.csproj", "{F6AC8D4A-EDD7-4514-8E8A-5BCB019864DB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.CmsKit.Web.Unified", "host\Volo.CmsKit.Web.Unified\Volo.CmsKit.Web.Unified.csproj", "{3D872C41-E226-45C8-89C1-9D3DBD7C73F2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "core", "core", "{DDE20914-DD1B-4C7B-86FF-F21E6B5DF1D1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "unified", "unified", "{963FBC0A-1FBA-47DA-8A61-8F57D3EC8D49}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "public", "public", "{3C74B8E8-CB34-49C8-B02A-05E959601FEE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "admin", "admin", "{E737206B-FA99-4460-861D-82902ECE93DB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.CmsKit.Admin.Application", "src\Volo.CmsKit.Admin.Application\Volo.CmsKit.Admin.Application.csproj", "{A5363215-BF61-467F-80D0-40D93590F509}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.CmsKit.Admin.Application.Contracts", "src\Volo.CmsKit.Admin.Application.Contracts\Volo.CmsKit.Admin.Application.Contracts.csproj", "{AE000591-86E3-4AD4-AA86-C47FED4A3ACB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.CmsKit.Admin.HttpApi", "src\Volo.CmsKit.Admin.HttpApi\Volo.CmsKit.Admin.HttpApi.csproj", "{A402371A-B714-4BBC-AC22-9C0BD7679F25}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.CmsKit.Admin.HttpApi.Client", "src\Volo.CmsKit.Admin.HttpApi.Client\Volo.CmsKit.Admin.HttpApi.Client.csproj", "{97D7BB7A-1D45-4E54-B327-3718F62E8A86}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.CmsKit.Admin.Web", "src\Volo.CmsKit.Admin.Web\Volo.CmsKit.Admin.Web.csproj", "{CA6A7FAF-0EF9-42D9-B7A3-5CA690687045}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.CmsKit.Public.Application", "src\Volo.CmsKit.Public.Application\Volo.CmsKit.Public.Application.csproj", "{E3963E05-9645-4AFD-AC99-C5E9F9153B61}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.CmsKit.Public.Application.Contracts", "src\Volo.CmsKit.Public.Application.Contracts\Volo.CmsKit.Public.Application.Contracts.csproj", "{50147423-F37D-46D3-A25D-C51CD48A7DA7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.CmsKit.Public.HttpApi", "src\Volo.CmsKit.Public.HttpApi\Volo.CmsKit.Public.HttpApi.csproj", "{37C03B72-5FD8-4B67-8090-8A20CD8095A6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.CmsKit.Public.HttpApi.Client", "src\Volo.CmsKit.Public.HttpApi.Client\Volo.CmsKit.Public.HttpApi.Client.csproj", "{E790C2C6-39AC-4068-AA7D-309DC9AA1437}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.CmsKit.Public.Web", "src\Volo.CmsKit.Public.Web\Volo.CmsKit.Public.Web.csproj", "{BE2572ED-F505-435D-9A90-30DBC6C2DC1D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.CmsKit.Common.Web", "src\Volo.CmsKit.Common.Web\Volo.CmsKit.Common.Web.csproj", "{4B2C2431-7351-41F0-B5E1-F8A9859FA838}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.CmsKit.Common.Application.Contracts", "src\Volo.CmsKit.Common.Application.Contracts\Volo.CmsKit.Common.Application.Contracts.csproj", "{A0D38C5B-047F-49C1-9A6D-B41E4FB9B323}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.CmsKit.Common.Application", "src\Volo.CmsKit.Common.Application\Volo.CmsKit.Common.Application.csproj", "{5D9BD65D-353E-4618-BE49-E1DF4CFF2393}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.CmsKit.Common.HttpApi", "src\Volo.CmsKit.Common.HttpApi\Volo.CmsKit.Common.HttpApi.csproj", "{E8CC280A-D049-4564-9C71-2F5657C17937}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.CmsKit.Common.HttpApi.Client", "src\Volo.CmsKit.Common.HttpApi.Client\Volo.CmsKit.Common.HttpApi.Client.csproj", "{EC98F006-2002-4CE2-AA62-5EBB589ACD79}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D64C1577-4929-4B60-939E-96DE1534891A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D64C1577-4929-4B60-939E-96DE1534891A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D64C1577-4929-4B60-939E-96DE1534891A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D64C1577-4929-4B60-939E-96DE1534891A}.Release|Any CPU.Build.0 = Release|Any CPU + {F2840BC7-0188-4606-9126-DADD0F5ABF7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2840BC7-0188-4606-9126-DADD0F5ABF7A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2840BC7-0188-4606-9126-DADD0F5ABF7A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2840BC7-0188-4606-9126-DADD0F5ABF7A}.Release|Any CPU.Build.0 = Release|Any CPU + {BD65D04F-08D5-40C1-8C24-77CA0BACB877}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BD65D04F-08D5-40C1-8C24-77CA0BACB877}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BD65D04F-08D5-40C1-8C24-77CA0BACB877}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BD65D04F-08D5-40C1-8C24-77CA0BACB877}.Release|Any CPU.Build.0 = Release|Any CPU + {78040F9E-3501-4A40-82DF-00A597710F35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78040F9E-3501-4A40-82DF-00A597710F35}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78040F9E-3501-4A40-82DF-00A597710F35}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78040F9E-3501-4A40-82DF-00A597710F35}.Release|Any CPU.Build.0 = Release|Any CPU + {0CE86223-D31D-4315-A1F5-87BA3EE1B844}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0CE86223-D31D-4315-A1F5-87BA3EE1B844}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0CE86223-D31D-4315-A1F5-87BA3EE1B844}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0CE86223-D31D-4315-A1F5-87BA3EE1B844}.Release|Any CPU.Build.0 = Release|Any CPU + {F1C58097-4C08-4D88-8976-6B3389391481}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1C58097-4C08-4D88-8976-6B3389391481}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1C58097-4C08-4D88-8976-6B3389391481}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1C58097-4C08-4D88-8976-6B3389391481}.Release|Any CPU.Build.0 = Release|Any CPU + {077AA5F8-8B61-420C-A6B5-0150A66FDB34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {077AA5F8-8B61-420C-A6B5-0150A66FDB34}.Debug|Any CPU.Build.0 = Debug|Any CPU + {077AA5F8-8B61-420C-A6B5-0150A66FDB34}.Release|Any CPU.ActiveCfg = Release|Any CPU + {077AA5F8-8B61-420C-A6B5-0150A66FDB34}.Release|Any CPU.Build.0 = Release|Any CPU + {36E2735F-CEAB-44C8-A6D1-2CDAFF399751}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {36E2735F-CEAB-44C8-A6D1-2CDAFF399751}.Debug|Any CPU.Build.0 = Debug|Any CPU + {36E2735F-CEAB-44C8-A6D1-2CDAFF399751}.Release|Any CPU.ActiveCfg = Release|Any CPU + {36E2735F-CEAB-44C8-A6D1-2CDAFF399751}.Release|Any CPU.Build.0 = Release|Any CPU + {C5BB573D-3030-4BCB-88B7-F6A85C32766C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C5BB573D-3030-4BCB-88B7-F6A85C32766C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C5BB573D-3030-4BCB-88B7-F6A85C32766C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C5BB573D-3030-4BCB-88B7-F6A85C32766C}.Release|Any CPU.Build.0 = Release|Any CPU + {527F645C-C1FC-406E-8479-81386C8ECF13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {527F645C-C1FC-406E-8479-81386C8ECF13}.Debug|Any CPU.Build.0 = Debug|Any CPU + {527F645C-C1FC-406E-8479-81386C8ECF13}.Release|Any CPU.ActiveCfg = Release|Any CPU + {527F645C-C1FC-406E-8479-81386C8ECF13}.Release|Any CPU.Build.0 = Release|Any CPU + {D0AD9179-125C-40B2-A8EE-CD4C1EE24BB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D0AD9179-125C-40B2-A8EE-CD4C1EE24BB6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D0AD9179-125C-40B2-A8EE-CD4C1EE24BB6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D0AD9179-125C-40B2-A8EE-CD4C1EE24BB6}.Release|Any CPU.Build.0 = Release|Any CPU + {E60895E5-79C4-447D-88B7-85CB5BA336A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E60895E5-79C4-447D-88B7-85CB5BA336A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E60895E5-79C4-447D-88B7-85CB5BA336A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E60895E5-79C4-447D-88B7-85CB5BA336A4}.Release|Any CPU.Build.0 = Release|Any CPU + {90CB5DC4-C040-45C7-8900-9688B26405BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90CB5DC4-C040-45C7-8900-9688B26405BC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90CB5DC4-C040-45C7-8900-9688B26405BC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90CB5DC4-C040-45C7-8900-9688B26405BC}.Release|Any CPU.Build.0 = Release|Any CPU + {37B135B0-DAFE-4616-B25C-1BDF32FC44A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {37B135B0-DAFE-4616-B25C-1BDF32FC44A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {37B135B0-DAFE-4616-B25C-1BDF32FC44A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {37B135B0-DAFE-4616-B25C-1BDF32FC44A2}.Release|Any CPU.Build.0 = Release|Any CPU + {3B7B6317-1B85-4164-8E11-75574F80AE17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B7B6317-1B85-4164-8E11-75574F80AE17}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B7B6317-1B85-4164-8E11-75574F80AE17}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B7B6317-1B85-4164-8E11-75574F80AE17}.Release|Any CPU.Build.0 = Release|Any CPU + {1EDCD6D4-DF3A-4E3B-ABB6-C0D0B373EAB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1EDCD6D4-DF3A-4E3B-ABB6-C0D0B373EAB8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1EDCD6D4-DF3A-4E3B-ABB6-C0D0B373EAB8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1EDCD6D4-DF3A-4E3B-ABB6-C0D0B373EAB8}.Release|Any CPU.Build.0 = Release|Any CPU + {73513786-B6C6-4A21-89C5-0FBDD0A46107}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {73513786-B6C6-4A21-89C5-0FBDD0A46107}.Debug|Any CPU.Build.0 = Debug|Any CPU + {73513786-B6C6-4A21-89C5-0FBDD0A46107}.Release|Any CPU.ActiveCfg = Release|Any CPU + {73513786-B6C6-4A21-89C5-0FBDD0A46107}.Release|Any CPU.Build.0 = Release|Any CPU + {690203F4-3CD5-4569-88D9-EE831EEA5F5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {690203F4-3CD5-4569-88D9-EE831EEA5F5F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {690203F4-3CD5-4569-88D9-EE831EEA5F5F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {690203F4-3CD5-4569-88D9-EE831EEA5F5F}.Release|Any CPU.Build.0 = Release|Any CPU + {F6AC8D4A-EDD7-4514-8E8A-5BCB019864DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F6AC8D4A-EDD7-4514-8E8A-5BCB019864DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F6AC8D4A-EDD7-4514-8E8A-5BCB019864DB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F6AC8D4A-EDD7-4514-8E8A-5BCB019864DB}.Release|Any CPU.Build.0 = Release|Any CPU + {3D872C41-E226-45C8-89C1-9D3DBD7C73F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3D872C41-E226-45C8-89C1-9D3DBD7C73F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3D872C41-E226-45C8-89C1-9D3DBD7C73F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3D872C41-E226-45C8-89C1-9D3DBD7C73F2}.Release|Any CPU.Build.0 = Release|Any CPU + {A5363215-BF61-467F-80D0-40D93590F509}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A5363215-BF61-467F-80D0-40D93590F509}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A5363215-BF61-467F-80D0-40D93590F509}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A5363215-BF61-467F-80D0-40D93590F509}.Release|Any CPU.Build.0 = Release|Any CPU + {AE000591-86E3-4AD4-AA86-C47FED4A3ACB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE000591-86E3-4AD4-AA86-C47FED4A3ACB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE000591-86E3-4AD4-AA86-C47FED4A3ACB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE000591-86E3-4AD4-AA86-C47FED4A3ACB}.Release|Any CPU.Build.0 = Release|Any CPU + {A402371A-B714-4BBC-AC22-9C0BD7679F25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A402371A-B714-4BBC-AC22-9C0BD7679F25}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A402371A-B714-4BBC-AC22-9C0BD7679F25}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A402371A-B714-4BBC-AC22-9C0BD7679F25}.Release|Any CPU.Build.0 = Release|Any CPU + {97D7BB7A-1D45-4E54-B327-3718F62E8A86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {97D7BB7A-1D45-4E54-B327-3718F62E8A86}.Debug|Any CPU.Build.0 = Debug|Any CPU + {97D7BB7A-1D45-4E54-B327-3718F62E8A86}.Release|Any CPU.ActiveCfg = Release|Any CPU + {97D7BB7A-1D45-4E54-B327-3718F62E8A86}.Release|Any CPU.Build.0 = Release|Any CPU + {CA6A7FAF-0EF9-42D9-B7A3-5CA690687045}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CA6A7FAF-0EF9-42D9-B7A3-5CA690687045}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CA6A7FAF-0EF9-42D9-B7A3-5CA690687045}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CA6A7FAF-0EF9-42D9-B7A3-5CA690687045}.Release|Any CPU.Build.0 = Release|Any CPU + {E3963E05-9645-4AFD-AC99-C5E9F9153B61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E3963E05-9645-4AFD-AC99-C5E9F9153B61}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E3963E05-9645-4AFD-AC99-C5E9F9153B61}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E3963E05-9645-4AFD-AC99-C5E9F9153B61}.Release|Any CPU.Build.0 = Release|Any CPU + {50147423-F37D-46D3-A25D-C51CD48A7DA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50147423-F37D-46D3-A25D-C51CD48A7DA7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50147423-F37D-46D3-A25D-C51CD48A7DA7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50147423-F37D-46D3-A25D-C51CD48A7DA7}.Release|Any CPU.Build.0 = Release|Any CPU + {37C03B72-5FD8-4B67-8090-8A20CD8095A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {37C03B72-5FD8-4B67-8090-8A20CD8095A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {37C03B72-5FD8-4B67-8090-8A20CD8095A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {37C03B72-5FD8-4B67-8090-8A20CD8095A6}.Release|Any CPU.Build.0 = Release|Any CPU + {E790C2C6-39AC-4068-AA7D-309DC9AA1437}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E790C2C6-39AC-4068-AA7D-309DC9AA1437}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E790C2C6-39AC-4068-AA7D-309DC9AA1437}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E790C2C6-39AC-4068-AA7D-309DC9AA1437}.Release|Any CPU.Build.0 = Release|Any CPU + {BE2572ED-F505-435D-9A90-30DBC6C2DC1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE2572ED-F505-435D-9A90-30DBC6C2DC1D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE2572ED-F505-435D-9A90-30DBC6C2DC1D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE2572ED-F505-435D-9A90-30DBC6C2DC1D}.Release|Any CPU.Build.0 = Release|Any CPU + {4B2C2431-7351-41F0-B5E1-F8A9859FA838}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4B2C2431-7351-41F0-B5E1-F8A9859FA838}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4B2C2431-7351-41F0-B5E1-F8A9859FA838}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4B2C2431-7351-41F0-B5E1-F8A9859FA838}.Release|Any CPU.Build.0 = Release|Any CPU + {A0D38C5B-047F-49C1-9A6D-B41E4FB9B323}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0D38C5B-047F-49C1-9A6D-B41E4FB9B323}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0D38C5B-047F-49C1-9A6D-B41E4FB9B323}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0D38C5B-047F-49C1-9A6D-B41E4FB9B323}.Release|Any CPU.Build.0 = Release|Any CPU + {5D9BD65D-353E-4618-BE49-E1DF4CFF2393}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D9BD65D-353E-4618-BE49-E1DF4CFF2393}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D9BD65D-353E-4618-BE49-E1DF4CFF2393}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D9BD65D-353E-4618-BE49-E1DF4CFF2393}.Release|Any CPU.Build.0 = Release|Any CPU + {E8CC280A-D049-4564-9C71-2F5657C17937}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E8CC280A-D049-4564-9C71-2F5657C17937}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E8CC280A-D049-4564-9C71-2F5657C17937}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E8CC280A-D049-4564-9C71-2F5657C17937}.Release|Any CPU.Build.0 = Release|Any CPU + {EC98F006-2002-4CE2-AA62-5EBB589ACD79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EC98F006-2002-4CE2-AA62-5EBB589ACD79}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EC98F006-2002-4CE2-AA62-5EBB589ACD79}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EC98F006-2002-4CE2-AA62-5EBB589ACD79}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {C5BB573D-3030-4BCB-88B7-F6A85C32766C} = {CCD2960C-23CC-4AB4-B84D-60C7AAA52F4D} + {527F645C-C1FC-406E-8479-81386C8ECF13} = {CCD2960C-23CC-4AB4-B84D-60C7AAA52F4D} + {D0AD9179-125C-40B2-A8EE-CD4C1EE24BB6} = {CCD2960C-23CC-4AB4-B84D-60C7AAA52F4D} + {E60895E5-79C4-447D-88B7-85CB5BA336A4} = {CCD2960C-23CC-4AB4-B84D-60C7AAA52F4D} + {90CB5DC4-C040-45C7-8900-9688B26405BC} = {CCD2960C-23CC-4AB4-B84D-60C7AAA52F4D} + {37B135B0-DAFE-4616-B25C-1BDF32FC44A2} = {E400416D-2895-4512-9D17-90681EEC7E0A} + {1EDCD6D4-DF3A-4E3B-ABB6-C0D0B373EAB8} = {CCD2960C-23CC-4AB4-B84D-60C7AAA52F4D} + {73513786-B6C6-4A21-89C5-0FBDD0A46107} = {E400416D-2895-4512-9D17-90681EEC7E0A} + {690203F4-3CD5-4569-88D9-EE831EEA5F5F} = {E400416D-2895-4512-9D17-90681EEC7E0A} + {F6AC8D4A-EDD7-4514-8E8A-5BCB019864DB} = {E400416D-2895-4512-9D17-90681EEC7E0A} + {3D872C41-E226-45C8-89C1-9D3DBD7C73F2} = {E400416D-2895-4512-9D17-90681EEC7E0A} + {DDE20914-DD1B-4C7B-86FF-F21E6B5DF1D1} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} + {F2840BC7-0188-4606-9126-DADD0F5ABF7A} = {DDE20914-DD1B-4C7B-86FF-F21E6B5DF1D1} + {D64C1577-4929-4B60-939E-96DE1534891A} = {DDE20914-DD1B-4C7B-86FF-F21E6B5DF1D1} + {F1C58097-4C08-4D88-8976-6B3389391481} = {DDE20914-DD1B-4C7B-86FF-F21E6B5DF1D1} + {0CE86223-D31D-4315-A1F5-87BA3EE1B844} = {DDE20914-DD1B-4C7B-86FF-F21E6B5DF1D1} + {963FBC0A-1FBA-47DA-8A61-8F57D3EC8D49} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} + {78040F9E-3501-4A40-82DF-00A597710F35} = {963FBC0A-1FBA-47DA-8A61-8F57D3EC8D49} + {BD65D04F-08D5-40C1-8C24-77CA0BACB877} = {963FBC0A-1FBA-47DA-8A61-8F57D3EC8D49} + {077AA5F8-8B61-420C-A6B5-0150A66FDB34} = {963FBC0A-1FBA-47DA-8A61-8F57D3EC8D49} + {36E2735F-CEAB-44C8-A6D1-2CDAFF399751} = {963FBC0A-1FBA-47DA-8A61-8F57D3EC8D49} + {3B7B6317-1B85-4164-8E11-75574F80AE17} = {963FBC0A-1FBA-47DA-8A61-8F57D3EC8D49} + {3C74B8E8-CB34-49C8-B02A-05E959601FEE} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} + {E737206B-FA99-4460-861D-82902ECE93DB} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} + {A5363215-BF61-467F-80D0-40D93590F509} = {E737206B-FA99-4460-861D-82902ECE93DB} + {AE000591-86E3-4AD4-AA86-C47FED4A3ACB} = {E737206B-FA99-4460-861D-82902ECE93DB} + {A402371A-B714-4BBC-AC22-9C0BD7679F25} = {E737206B-FA99-4460-861D-82902ECE93DB} + {97D7BB7A-1D45-4E54-B327-3718F62E8A86} = {E737206B-FA99-4460-861D-82902ECE93DB} + {CA6A7FAF-0EF9-42D9-B7A3-5CA690687045} = {E737206B-FA99-4460-861D-82902ECE93DB} + {E3963E05-9645-4AFD-AC99-C5E9F9153B61} = {3C74B8E8-CB34-49C8-B02A-05E959601FEE} + {50147423-F37D-46D3-A25D-C51CD48A7DA7} = {3C74B8E8-CB34-49C8-B02A-05E959601FEE} + {37C03B72-5FD8-4B67-8090-8A20CD8095A6} = {3C74B8E8-CB34-49C8-B02A-05E959601FEE} + {E790C2C6-39AC-4068-AA7D-309DC9AA1437} = {3C74B8E8-CB34-49C8-B02A-05E959601FEE} + {BE2572ED-F505-435D-9A90-30DBC6C2DC1D} = {3C74B8E8-CB34-49C8-B02A-05E959601FEE} + {4B2C2431-7351-41F0-B5E1-F8A9859FA838} = {DDE20914-DD1B-4C7B-86FF-F21E6B5DF1D1} + {A0D38C5B-047F-49C1-9A6D-B41E4FB9B323} = {DDE20914-DD1B-4C7B-86FF-F21E6B5DF1D1} + {5D9BD65D-353E-4618-BE49-E1DF4CFF2393} = {DDE20914-DD1B-4C7B-86FF-F21E6B5DF1D1} + {E8CC280A-D049-4564-9C71-2F5657C17937} = {DDE20914-DD1B-4C7B-86FF-F21E6B5DF1D1} + {EC98F006-2002-4CE2-AA62-5EBB589ACD79} = {DDE20914-DD1B-4C7B-86FF-F21E6B5DF1D1} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD} + EndGlobalSection +EndGlobal diff --git a/modules/cms-kit/Volo.CmsKit.sln.DotSettings b/modules/cms-kit/Volo.CmsKit.sln.DotSettings new file mode 100644 index 0000000000..cb0b2c919f --- /dev/null +++ b/modules/cms-kit/Volo.CmsKit.sln.DotSettings @@ -0,0 +1,23 @@ + + True + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + Required + Required + Required + Required + False + True + False + False + True + False + False + SQL + \ No newline at end of file diff --git a/modules/cms-kit/angular/.editorconfig b/modules/cms-kit/angular/.editorconfig new file mode 100644 index 0000000000..59d9a3a3e7 --- /dev/null +++ b/modules/cms-kit/angular/.editorconfig @@ -0,0 +1,16 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +quote_type = single + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/modules/cms-kit/angular/.gitignore b/modules/cms-kit/angular/.gitignore new file mode 100644 index 0000000000..a8e3c659a1 --- /dev/null +++ b/modules/cms-kit/angular/.gitignore @@ -0,0 +1,48 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp +/out-tsc +# Only exists if Bazel was run +/bazel-out + +# dependencies +/node_modules + +# profiling files +chrome-profiler-events*.json +speed-measure-plugin*.json + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings + +# System Files +.DS_Store +Thumbs.db + +yarn.lock \ No newline at end of file diff --git a/modules/cms-kit/angular/README.md b/modules/cms-kit/angular/README.md new file mode 100644 index 0000000000..38d90cb368 --- /dev/null +++ b/modules/cms-kit/angular/README.md @@ -0,0 +1,27 @@ +# CmsKit + +This is a startup project based on the ABP framework. For more information, visit abp.io + +## Development server + +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. + +## Code scaffolding + +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. + +## Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. + +## Running unit tests + +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Running end-to-end tests + +Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). + +## Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). diff --git a/modules/cms-kit/angular/angular.json b/modules/cms-kit/angular/angular.json new file mode 100644 index 0000000000..b5692b9aec --- /dev/null +++ b/modules/cms-kit/angular/angular.json @@ -0,0 +1,202 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "cms-kit": { + "projectType": "library", + "root": "projects/cms-kit", + "sourceRoot": "projects/cms-kit/src", + "prefix": "lib", + "architect": { + "build": { + "builder": "@angular-devkit/build-ng-packagr:build", + "options": { + "tsConfig": "projects/cms-kit/tsconfig.lib.json", + "project": "projects/cms-kit/ng-package.json" + }, + "configurations": { + "production": { + "tsConfig": "projects/cms-kit/tsconfig.lib.prod.json" + } + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "projects/cms-kit/src/test.ts", + "tsConfig": "projects/cms-kit/tsconfig.spec.json", + "karmaConfig": "projects/cms-kit/karma.conf.js" + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "projects/cms-kit/tsconfig.lib.json", + "projects/cms-kit/tsconfig.spec.json" + ], + "exclude": ["**/node_modules/**"] + } + } + } + }, + "dev-app": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" + } + }, + "root": "projects/dev-app", + "sourceRoot": "projects/dev-app/src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/dev-app", + "index": "projects/dev-app/src/index.html", + "main": "projects/dev-app/src/main.ts", + "polyfills": "projects/dev-app/src/polyfills.ts", + "tsConfig": "projects/dev-app/tsconfig.app.json", + "aot": true, + "assets": [ + "projects/dev-app/src/favicon.ico", + "projects/dev-app/src/assets" + ], + "styles": [ + { + "input": "node_modules/@fortawesome/fontawesome-free/css/all.min.css", + "inject": true, + "bundleName": "fontawesome-all.min" + }, + { + "input": "node_modules/@fortawesome/fontawesome-free/css/v4-shims.min.css", + "inject": true, + "bundleName": "fontawesome-v4-shims.min" + }, + { + "input": "node_modules/@swimlane/ngx-datatable/index.css", + "inject": true, + "bundleName": "ngx-datatable-index" + }, + { + "input": "node_modules/@swimlane/ngx-datatable/assets/icons.css", + "inject": true, + "bundleName": "ngx-datatable-icons" + }, + { + "input": "node_modules/@swimlane/ngx-datatable/themes/material.css", + "inject": true, + "bundleName": "ngx-datatable-material" + }, + { + "input": "node_modules/@abp/ng.theme.shared/styles/bootstrap-rtl.min.css", + "inject": false, + "bundleName": "bootstrap-rtl.min" + }, + { + "input": "node_modules/bootstrap/dist/css/bootstrap.min.css", + "inject": true, + "bundleName": "bootstrap-ltr.min" + }, + "projects/dev-app/src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "tsConfig": "projects/dev-app/tsconfig.prod.json", + "fileReplacements": [ + { + "replace": "projects/dev-app/src/environments/environment.ts", + "with": "projects/dev-app/src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true, + "budgets": [ + { + "type": "initial", + "maximumWarning": "2mb", + "maximumError": "5mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "6kb", + "maximumError": "10kb" + } + ] + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "dev-app:build" + }, + "configurations": { + "production": { + "browserTarget": "dev-app:build:production" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "dev-app:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "projects/dev-app/src/test.ts", + "polyfills": "projects/dev-app/src/polyfills.ts", + "tsConfig": "projects/dev-app/tsconfig.spec.json", + "karmaConfig": "projects/dev-app/karma.conf.js", + "assets": [ + "projects/dev-app/src/favicon.ico", + "projects/dev-app/src/assets" + ], + "styles": ["projects/dev-app/src/styles.scss"], + "scripts": [] + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "projects/dev-app/tsconfig.app.json", + "projects/dev-app/tsconfig.spec.json", + "projects/dev-app/e2e/tsconfig.json" + ], + "exclude": ["**/node_modules/**"] + } + }, + "e2e": { + "builder": "@angular-devkit/build-angular:protractor", + "options": { + "protractorConfig": "projects/dev-app/e2e/protractor.conf.js", + "devServerTarget": "dev-app:serve" + }, + "configurations": { + "production": { + "devServerTarget": "dev-app:serve:production" + } + } + } + } + } + }, + "cli": { + "analytics": false + }, + "defaultProject": "dev-app" +} diff --git a/modules/cms-kit/angular/package.json b/modules/cms-kit/angular/package.json new file mode 100644 index 0000000000..f162ebd366 --- /dev/null +++ b/modules/cms-kit/angular/package.json @@ -0,0 +1,58 @@ +{ + "name": "cms-kit", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve dev-app --open", + "build": "ng build cms-kit --prod", + "build:app": "npm run symlink:copy -- --no-watch && ng build dev-app --prod", + "symlink:copy": "symlink copy --angular --packages @my-company-name/cms-kit --prod", + "test": "ng test", + "lint": "ng lint", + "e2e": "ng e2e", + "compile:ivy": "yarn ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points --tsconfig './tsconfig.prod.json' --source node_modules --async false", + "postinstall": "npm run compile:ivy" + }, + "private": true, + "dependencies": { + "@abp/ng.account": "~3.0.3", + "@abp/ng.identity": "~3.0.3", + "@abp/ng.setting-management": "~3.0.3", + "@abp/ng.tenant-management": "~3.0.3", + "@abp/ng.theme.basic": "~3.0.3", + "@angular/animations": "~10.0.0", + "@angular/common": "~10.0.0", + "@angular/compiler": "~10.0.0", + "@angular/core": "~10.0.0", + "@angular/forms": "~10.0.0", + "@angular/platform-browser": "~10.0.0", + "@angular/platform-browser-dynamic": "~10.0.0", + "@angular/router": "~10.0.0", + "rxjs": "~6.5.4", + "tslib": "^2.0.0", + "zone.js": "~0.10.2" + }, + "devDependencies": { + "@angular-devkit/build-angular": "~0.1000.0", + "@angular-devkit/build-ng-packagr": "~0.1000.0", + "@angular/cli": "~10.0.0", + "@angular/compiler-cli": "~10.0.0", + "@types/jasmine": "~3.5.0", + "@types/jasminewd2": "~2.0.3", + "@types/node": "^12.11.1", + "codelyzer": "^5.1.2", + "jasmine-core": "~3.5.0", + "jasmine-spec-reporter": "~5.0.0", + "karma": "~5.0.0", + "karma-chrome-launcher": "~3.1.0", + "karma-coverage-istanbul-reporter": "~3.0.2", + "karma-jasmine": "~3.3.0", + "karma-jasmine-html-reporter": "^1.5.0", + "ng-packagr": "^10.0.0", + "protractor": "~7.0.0", + "symlink-manager": "^1.5.0", + "ts-node": "~8.3.0", + "tslint": "~6.1.0", + "typescript": "~3.9.5" + } +} \ No newline at end of file diff --git a/modules/cms-kit/angular/projects/cms-kit/README.md b/modules/cms-kit/angular/projects/cms-kit/README.md new file mode 100644 index 0000000000..4174520f2c --- /dev/null +++ b/modules/cms-kit/angular/projects/cms-kit/README.md @@ -0,0 +1,24 @@ +# CmsKit + +This is a startup project based on the ABP framework. For more information, visit abp.io Note: Don't forget to add `--project cms-kit` or else it will be added to the default project in your `angular.json` file. + +## Build + +Run `ng build cms-kit` to build the project. The build artifacts will be stored in the `dist/` directory. + +## Publishing + +After building your library with `ng build cms-kit`, go to the dist folder `cd dist/cms-kit` and run `npm publish`. + +## Running unit tests + +Run `ng test cms-kit` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). diff --git a/modules/cms-kit/angular/projects/cms-kit/config/ng-package.json b/modules/cms-kit/angular/projects/cms-kit/config/ng-package.json new file mode 100644 index 0000000000..c2e5f0483f --- /dev/null +++ b/modules/cms-kit/angular/projects/cms-kit/config/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "dest": "../../dist/cms-kit/config", + "lib": { + "entryFile": "src/public-api.ts" + } +} diff --git a/modules/cms-kit/angular/projects/cms-kit/config/src/cms-kit-config.module.ts b/modules/cms-kit/angular/projects/cms-kit/config/src/cms-kit-config.module.ts new file mode 100644 index 0000000000..6f20218868 --- /dev/null +++ b/modules/cms-kit/angular/projects/cms-kit/config/src/cms-kit-config.module.ts @@ -0,0 +1,12 @@ +import { ModuleWithProviders, NgModule } from '@angular/core'; +import { MY_PROJECT_NAME_ROUTE_PROVIDERS } from './providers/route.provider'; + +@NgModule() +export class CmsKitConfigModule { + static forRoot(): ModuleWithProviders { + return { + ngModule: CmsKitConfigModule, + providers: [MY_PROJECT_NAME_ROUTE_PROVIDERS], + }; + } +} diff --git a/modules/cms-kit/angular/projects/cms-kit/config/src/enums/index.ts b/modules/cms-kit/angular/projects/cms-kit/config/src/enums/index.ts new file mode 100644 index 0000000000..3bda94b078 --- /dev/null +++ b/modules/cms-kit/angular/projects/cms-kit/config/src/enums/index.ts @@ -0,0 +1 @@ +export * from './route-names'; diff --git a/modules/cms-kit/angular/projects/cms-kit/config/src/enums/route-names.ts b/modules/cms-kit/angular/projects/cms-kit/config/src/enums/route-names.ts new file mode 100644 index 0000000000..c05735b0ec --- /dev/null +++ b/modules/cms-kit/angular/projects/cms-kit/config/src/enums/route-names.ts @@ -0,0 +1,3 @@ +export const enum eCmsKitRouteNames { + CmsKit = 'CmsKit', +} diff --git a/modules/cms-kit/angular/projects/cms-kit/config/src/providers/index.ts b/modules/cms-kit/angular/projects/cms-kit/config/src/providers/index.ts new file mode 100644 index 0000000000..fe08efba8c --- /dev/null +++ b/modules/cms-kit/angular/projects/cms-kit/config/src/providers/index.ts @@ -0,0 +1 @@ +export * from './route.provider'; diff --git a/modules/cms-kit/angular/projects/cms-kit/config/src/providers/route.provider.ts b/modules/cms-kit/angular/projects/cms-kit/config/src/providers/route.provider.ts new file mode 100644 index 0000000000..2fa8ce1bb8 --- /dev/null +++ b/modules/cms-kit/angular/projects/cms-kit/config/src/providers/route.provider.ts @@ -0,0 +1,27 @@ +import { eLayoutType, RoutesService } from '@abp/ng.core'; +import { eThemeSharedRouteNames } from '@abp/ng.theme.shared'; +import { APP_INITIALIZER } from '@angular/core'; +import { eCmsKitRouteNames } from '../enums/route-names'; + +export const MY_PROJECT_NAME_ROUTE_PROVIDERS = [ + { + provide: APP_INITIALIZER, + useFactory: configureRoutes, + deps: [RoutesService], + multi: true, + }, +]; + +export function configureRoutes(routes: RoutesService) { + return () => { + routes.add([ + { + path: '/cms-kit', + name: eCmsKitRouteNames.CmsKit, + iconClass: 'fas fa-book', + layout: eLayoutType.application, + order: 3, + }, + ]); + }; +} diff --git a/modules/cms-kit/angular/projects/cms-kit/config/src/public-api.ts b/modules/cms-kit/angular/projects/cms-kit/config/src/public-api.ts new file mode 100644 index 0000000000..a77eadc283 --- /dev/null +++ b/modules/cms-kit/angular/projects/cms-kit/config/src/public-api.ts @@ -0,0 +1,3 @@ +export * from './enums'; +export * from './cms-kit-config.module'; +export * from './providers'; diff --git a/modules/cms-kit/angular/projects/cms-kit/karma.conf.js b/modules/cms-kit/angular/projects/cms-kit/karma.conf.js new file mode 100644 index 0000000000..5a1214c588 --- /dev/null +++ b/modules/cms-kit/angular/projects/cms-kit/karma.conf.js @@ -0,0 +1,32 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular-devkit/build-angular'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage-istanbul-reporter'), + require('@angular-devkit/build-angular/plugins/karma') + ], + client: { + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + coverageIstanbulReporter: { + dir: require('path').join(__dirname, '../../coverage/cms-kit'), + reports: ['html', 'lcovonly', 'text-summary'], + fixWebpackSourcePaths: true + }, + reporters: ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false, + restartOnFileChange: true + }); +}; diff --git a/modules/cms-kit/angular/projects/cms-kit/ng-package.json b/modules/cms-kit/angular/projects/cms-kit/ng-package.json new file mode 100644 index 0000000000..26aa884e57 --- /dev/null +++ b/modules/cms-kit/angular/projects/cms-kit/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "dest": "../../dist/cms-kit", + "lib": { + "entryFile": "src/public-api.ts" + } +} \ No newline at end of file diff --git a/modules/cms-kit/angular/projects/cms-kit/ngcc.config.js b/modules/cms-kit/angular/projects/cms-kit/ngcc.config.js new file mode 100644 index 0000000000..c34df8ab72 --- /dev/null +++ b/modules/cms-kit/angular/projects/cms-kit/ngcc.config.js @@ -0,0 +1,7 @@ +module.exports = { + entryPoints: { + ".": {}, + "./config": {}, + "./dist": { ignore: true }, + }, +}; diff --git a/modules/cms-kit/angular/projects/cms-kit/package.json b/modules/cms-kit/angular/projects/cms-kit/package.json new file mode 100644 index 0000000000..1dea156a57 --- /dev/null +++ b/modules/cms-kit/angular/projects/cms-kit/package.json @@ -0,0 +1,13 @@ +{ + "name": "@my-company-name/cms-kit", + "version": "0.0.1", + "peerDependencies": { + "@angular/common": "^9.1.11", + "@angular/core": "^9.1.11", + "@abp/ng.core": ">=3.0.3", + "@abp/ng.theme.shared": ">=3.0.3" + }, + "dependencies": { + "tslib": "^2.0.0" +} +} diff --git a/modules/cms-kit/angular/projects/cms-kit/src/lib/cms-kit-routing.module.ts b/modules/cms-kit/angular/projects/cms-kit/src/lib/cms-kit-routing.module.ts new file mode 100644 index 0000000000..7079b008ff --- /dev/null +++ b/modules/cms-kit/angular/projects/cms-kit/src/lib/cms-kit-routing.module.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core'; +import { DynamicLayoutComponent } from '@abp/ng.core'; +import { Routes, RouterModule } from '@angular/router'; +import { CmsKitComponent } from './components/cms-kit.component'; + +const routes: Routes = [ + { + path: '', + pathMatch: 'full', + component: DynamicLayoutComponent, + children: [ + { + path: '', + component: CmsKitComponent, + }, + ], + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class CmsKitRoutingModule {} diff --git a/modules/cms-kit/angular/projects/cms-kit/src/lib/cms-kit.component.spec.ts b/modules/cms-kit/angular/projects/cms-kit/src/lib/cms-kit.component.spec.ts new file mode 100644 index 0000000000..acd7e447a2 --- /dev/null +++ b/modules/cms-kit/angular/projects/cms-kit/src/lib/cms-kit.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CmsKitComponent } from './cms-kit.component'; + +describe('CmsKitComponent', () => { + let component: CmsKitComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CmsKitComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CmsKitComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/modules/cms-kit/angular/projects/cms-kit/src/lib/cms-kit.module.ts b/modules/cms-kit/angular/projects/cms-kit/src/lib/cms-kit.module.ts new file mode 100644 index 0000000000..951dd8f33e --- /dev/null +++ b/modules/cms-kit/angular/projects/cms-kit/src/lib/cms-kit.module.ts @@ -0,0 +1,23 @@ +import { NgModule, NgModuleFactory, ModuleWithProviders } from '@angular/core'; +import { CoreModule, LazyModuleFactory } from '@abp/ng.core'; +import { ThemeSharedModule } from '@abp/ng.theme.shared'; +import { CmsKitComponent } from './components/cms-kit.component'; +import { CmsKitRoutingModule } from './cms-kit-routing.module'; + +@NgModule({ + declarations: [CmsKitComponent], + imports: [CoreModule, ThemeSharedModule, CmsKitRoutingModule], + exports: [CmsKitComponent], +}) +export class CmsKitModule { + static forChild(): ModuleWithProviders { + return { + ngModule: CmsKitModule, + providers: [], + }; + } + + static forLazy(): NgModuleFactory { + return new LazyModuleFactory(CmsKitModule.forChild()); + } +} diff --git a/modules/cms-kit/angular/projects/cms-kit/src/lib/cms-kit.service.spec.ts b/modules/cms-kit/angular/projects/cms-kit/src/lib/cms-kit.service.spec.ts new file mode 100644 index 0000000000..f488c97994 --- /dev/null +++ b/modules/cms-kit/angular/projects/cms-kit/src/lib/cms-kit.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { CmsKitService } from './cms-kit.service'; + +describe('CmsKitService', () => { + let service: CmsKitService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(CmsKitService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/modules/cms-kit/angular/projects/cms-kit/src/lib/components/cms-kit.component.ts b/modules/cms-kit/angular/projects/cms-kit/src/lib/components/cms-kit.component.ts new file mode 100644 index 0000000000..b6ef6589c5 --- /dev/null +++ b/modules/cms-kit/angular/projects/cms-kit/src/lib/components/cms-kit.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; +import { CmsKitService } from '../services/cms-kit.service'; + +@Component({ + selector: 'lib-cms-kit', + template: `

    cms-kit works!

    `, + styles: [], +}) +export class CmsKitComponent implements OnInit { + constructor(private service: CmsKitService) {} + + ngOnInit(): void { + this.service.sample().subscribe(console.log); + } +} diff --git a/modules/cms-kit/angular/projects/cms-kit/src/lib/services/cms-kit.service.ts b/modules/cms-kit/angular/projects/cms-kit/src/lib/services/cms-kit.service.ts new file mode 100644 index 0000000000..9e966cfbe1 --- /dev/null +++ b/modules/cms-kit/angular/projects/cms-kit/src/lib/services/cms-kit.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; +import { RestService } from '@abp/ng.core'; + +@Injectable({ + providedIn: 'root', +}) +export class CmsKitService { + apiName = 'CmsKit'; + + constructor(private restService: RestService) {} + + sample() { + return this.restService.request( + { method: 'GET', url: '/api/CmsKit/sample' }, + { apiName: this.apiName } + ); + } +} diff --git a/modules/cms-kit/angular/projects/cms-kit/src/public-api.ts b/modules/cms-kit/angular/projects/cms-kit/src/public-api.ts new file mode 100644 index 0000000000..b07b030e09 --- /dev/null +++ b/modules/cms-kit/angular/projects/cms-kit/src/public-api.ts @@ -0,0 +1,7 @@ +/* + * Public API Surface of cms-kit + */ + +export * from './lib/components/cms-kit.component'; +export * from './lib/services/cms-kit.service'; +export * from './lib/cms-kit.module'; diff --git a/modules/cms-kit/angular/projects/cms-kit/src/test.ts b/modules/cms-kit/angular/projects/cms-kit/src/test.ts new file mode 100644 index 0000000000..303b32a220 --- /dev/null +++ b/modules/cms-kit/angular/projects/cms-kit/src/test.ts @@ -0,0 +1,26 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/dist/zone'; +import 'zone.js/dist/zone-testing'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +declare const require: { + context(path: string, deep?: boolean, filter?: RegExp): { + keys(): string[]; + (id: string): T; + }; +}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); diff --git a/modules/cms-kit/angular/projects/cms-kit/tsconfig.lib.json b/modules/cms-kit/angular/projects/cms-kit/tsconfig.lib.json new file mode 100644 index 0000000000..c433a69f62 --- /dev/null +++ b/modules/cms-kit/angular/projects/cms-kit/tsconfig.lib.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.prod.json", + "compilerOptions": { + "outDir": "../../out-tsc/lib", + "target": "es2015", + "declaration": true, + "inlineSources": true, + "types": [], + "lib": ["dom", "es2018"] + }, + "angularCompilerOptions": { + "skipTemplateCodegen": true, + "strictMetadataEmit": true, + "enableResourceInlining": true + }, + "exclude": ["src/test.ts", "**/*.spec.ts"] +} diff --git a/modules/cms-kit/angular/projects/cms-kit/tsconfig.lib.prod.json b/modules/cms-kit/angular/projects/cms-kit/tsconfig.lib.prod.json new file mode 100644 index 0000000000..cbae794224 --- /dev/null +++ b/modules/cms-kit/angular/projects/cms-kit/tsconfig.lib.prod.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.lib.json", + "angularCompilerOptions": { + "enableIvy": false + } +} diff --git a/modules/cms-kit/angular/projects/cms-kit/tsconfig.spec.json b/modules/cms-kit/angular/projects/cms-kit/tsconfig.spec.json new file mode 100644 index 0000000000..a9ea5ea114 --- /dev/null +++ b/modules/cms-kit/angular/projects/cms-kit/tsconfig.spec.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "../../out-tsc/spec", + "types": [ + "jasmine", + "node" + ] + }, + "files": [ + "src/test.ts" + ], + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +} diff --git a/modules/cms-kit/angular/projects/cms-kit/tslint.json b/modules/cms-kit/angular/projects/cms-kit/tslint.json new file mode 100644 index 0000000000..124133f849 --- /dev/null +++ b/modules/cms-kit/angular/projects/cms-kit/tslint.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tslint.json", + "rules": { + "directive-selector": [ + true, + "attribute", + "lib", + "camelCase" + ], + "component-selector": [ + true, + "element", + "lib", + "kebab-case" + ] + } +} diff --git a/modules/cms-kit/angular/projects/dev-app/.browserslistrc b/modules/cms-kit/angular/projects/dev-app/.browserslistrc new file mode 100644 index 0000000000..80848532e4 --- /dev/null +++ b/modules/cms-kit/angular/projects/dev-app/.browserslistrc @@ -0,0 +1,12 @@ +# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries + +# You can see what browsers were selected by your queries by running: +# npx browserslist + +> 0.5% +last 2 versions +Firefox ESR +not dead +not IE 9-11 # For IE 9-11 support, remove 'not'. \ No newline at end of file diff --git a/modules/cms-kit/angular/projects/dev-app/e2e/protractor.conf.js b/modules/cms-kit/angular/projects/dev-app/e2e/protractor.conf.js new file mode 100644 index 0000000000..7c798cfff0 --- /dev/null +++ b/modules/cms-kit/angular/projects/dev-app/e2e/protractor.conf.js @@ -0,0 +1,32 @@ +// @ts-check +// Protractor configuration file, see link for more information +// https://github.com/angular/protractor/blob/master/lib/config.ts + +const { SpecReporter } = require('jasmine-spec-reporter'); + +/** + * @type { import("protractor").Config } + */ +exports.config = { + allScriptsTimeout: 11000, + specs: [ + './src/**/*.e2e-spec.ts' + ], + capabilities: { + browserName: 'chrome' + }, + directConnect: true, + baseUrl: 'http://localhost:4200/', + framework: 'jasmine', + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000, + print: function() {} + }, + onPrepare() { + require('ts-node').register({ + project: require('path').join(__dirname, './tsconfig.json') + }); + jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); + } +}; \ No newline at end of file diff --git a/modules/cms-kit/angular/projects/dev-app/e2e/src/app.e2e-spec.ts b/modules/cms-kit/angular/projects/dev-app/e2e/src/app.e2e-spec.ts new file mode 100644 index 0000000000..e8eda49d2a --- /dev/null +++ b/modules/cms-kit/angular/projects/dev-app/e2e/src/app.e2e-spec.ts @@ -0,0 +1,23 @@ +import { AppPage } from './app.po'; +import { browser, logging } from 'protractor'; + +describe('workspace-project App', () => { + let page: AppPage; + + beforeEach(() => { + page = new AppPage(); + }); + + it('should display welcome message', () => { + page.navigateTo(); + expect(page.getTitleText()).toEqual('dev-app app is running!'); + }); + + afterEach(async () => { + // Assert that there are no errors emitted from the browser + const logs = await browser.manage().logs().get(logging.Type.BROWSER); + expect(logs).not.toContain(jasmine.objectContaining({ + level: logging.Level.SEVERE, + } as logging.Entry)); + }); +}); diff --git a/modules/cms-kit/angular/projects/dev-app/e2e/src/app.po.ts b/modules/cms-kit/angular/projects/dev-app/e2e/src/app.po.ts new file mode 100644 index 0000000000..b68475e0fc --- /dev/null +++ b/modules/cms-kit/angular/projects/dev-app/e2e/src/app.po.ts @@ -0,0 +1,11 @@ +import { browser, by, element } from 'protractor'; + +export class AppPage { + navigateTo(): Promise { + return browser.get(browser.baseUrl) as Promise; + } + + getTitleText(): Promise { + return element(by.css('app-root .content span')).getText() as Promise; + } +} diff --git a/modules/cms-kit/angular/projects/dev-app/e2e/tsconfig.json b/modules/cms-kit/angular/projects/dev-app/e2e/tsconfig.json new file mode 100644 index 0000000000..746a242801 --- /dev/null +++ b/modules/cms-kit/angular/projects/dev-app/e2e/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "../../../out-tsc/e2e", + "module": "commonjs", + "target": "es2018", + "types": [ + "jasmine", + "jasminewd2", + "node" + ] + } +} diff --git a/modules/cms-kit/angular/projects/dev-app/favicon.ico b/modules/cms-kit/angular/projects/dev-app/favicon.ico new file mode 100644 index 0000000000..39695854d2 Binary files /dev/null and b/modules/cms-kit/angular/projects/dev-app/favicon.ico differ diff --git a/modules/cms-kit/angular/projects/dev-app/karma.conf.js b/modules/cms-kit/angular/projects/dev-app/karma.conf.js new file mode 100644 index 0000000000..6059f553f2 --- /dev/null +++ b/modules/cms-kit/angular/projects/dev-app/karma.conf.js @@ -0,0 +1,32 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular-devkit/build-angular'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage-istanbul-reporter'), + require('@angular-devkit/build-angular/plugins/karma') + ], + client: { + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + coverageIstanbulReporter: { + dir: require('path').join(__dirname, '../../coverage/dev-app'), + reports: ['html', 'lcovonly', 'text-summary'], + fixWebpackSourcePaths: true + }, + reporters: ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false, + restartOnFileChange: true + }); +}; diff --git a/modules/cms-kit/angular/projects/dev-app/src/app/app-routing.module.ts b/modules/cms-kit/angular/projects/dev-app/src/app/app-routing.module.ts new file mode 100644 index 0000000000..b2c95094c1 --- /dev/null +++ b/modules/cms-kit/angular/projects/dev-app/src/app/app-routing.module.ts @@ -0,0 +1,49 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +const routes: Routes = [ + { + path: '', + pathMatch: 'full', + loadChildren: () => import('./home/home.module').then((m) => m.HomeModule), + }, + { + path: 'account', + loadChildren: () => + import('@abp/ng.account').then((m) => + m.AccountModule.forLazy({ redirectUrl: '/' }) + ), + }, + { + path: 'identity', + loadChildren: () => + import('@abp/ng.identity').then((m) => m.IdentityModule.forLazy()), + }, + { + path: 'tenant-management', + loadChildren: () => + import('@abp/ng.tenant-management').then((m) => + m.TenantManagementModule.forLazy() + ), + }, + { + path: 'setting-management', + loadChildren: () => + import('@abp/ng.setting-management').then((m) => + m.SettingManagementModule.forLazy() + ), + }, + { + path: 'cms-kit', + loadChildren: () => + import('@my-company-name/cms-kit').then((m) => + m.CmsKitModule.forLazy() + ), + }, +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule], +}) +export class AppRoutingModule {} diff --git a/modules/cms-kit/angular/projects/dev-app/src/app/app.component.ts b/modules/cms-kit/angular/projects/dev-app/src/app/app.component.ts new file mode 100644 index 0000000000..a26e745334 --- /dev/null +++ b/modules/cms-kit/angular/projects/dev-app/src/app/app.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-root', + template: ` + + + `, +}) +export class AppComponent {} diff --git a/modules/cms-kit/angular/projects/dev-app/src/app/app.module.ts b/modules/cms-kit/angular/projects/dev-app/src/app/app.module.ts new file mode 100644 index 0000000000..e97243133f --- /dev/null +++ b/modules/cms-kit/angular/projects/dev-app/src/app/app.module.ts @@ -0,0 +1,41 @@ +import { AccountConfigModule } from '@abp/ng.account/config'; +import { CoreModule } from '@abp/ng.core'; +import { IdentityConfigModule } from '@abp/ng.identity/config'; +import { SettingManagementConfigModule } from '@abp/ng.setting-management/config'; +import { TenantManagementConfigModule } from '@abp/ng.tenant-management/config'; +import { ThemeSharedModule } from '@abp/ng.theme.shared'; +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { CmsKitConfigModule } from '@my-company-name/cms-kit/config'; +import { NgxsModule } from '@ngxs/store'; +import { environment } from '../environments/environment'; +import { AppRoutingModule } from './app-routing.module'; +import { AppComponent } from './app.component'; +import { APP_ROUTE_PROVIDER } from './route.provider'; +import { ThemeBasicModule } from '@abp/ng.theme.basic'; + +@NgModule({ + imports: [ + BrowserModule, + BrowserAnimationsModule, + AppRoutingModule, + CoreModule.forRoot({ + environment, + sendNullsAsQueryParam: false, + skipGetAppConfiguration: false, + }), + ThemeSharedModule.forRoot(), + AccountConfigModule.forRoot(), + IdentityConfigModule.forRoot(), + CmsKitConfigModule.forRoot(), + TenantManagementConfigModule.forRoot(), + SettingManagementConfigModule.forRoot(), + NgxsModule.forRoot(), + ThemeBasicModule.forRoot(), + ], + providers: [APP_ROUTE_PROVIDER], + declarations: [AppComponent], + bootstrap: [AppComponent], +}) +export class AppModule {} diff --git a/modules/cms-kit/angular/projects/dev-app/src/app/home/home-routing.module.ts b/modules/cms-kit/angular/projects/dev-app/src/app/home/home-routing.module.ts new file mode 100644 index 0000000000..634706a36c --- /dev/null +++ b/modules/cms-kit/angular/projects/dev-app/src/app/home/home-routing.module.ts @@ -0,0 +1,16 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; +import { HomeComponent } from './home.component'; + +const routes: Routes = [ + { + path: '', + children: [{ path: '', component: HomeComponent }], + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class HomeRoutingModule {} diff --git a/modules/cms-kit/angular/projects/dev-app/src/app/home/home.component.html b/modules/cms-kit/angular/projects/dev-app/src/app/home/home.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/cms-kit/angular/projects/dev-app/src/app/home/home.component.ts b/modules/cms-kit/angular/projects/dev-app/src/app/home/home.component.ts new file mode 100644 index 0000000000..ab1f354e89 --- /dev/null +++ b/modules/cms-kit/angular/projects/dev-app/src/app/home/home.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-home', + templateUrl: './home.component.html', +}) +export class HomeComponent {} diff --git a/modules/cms-kit/angular/projects/dev-app/src/app/home/home.module.ts b/modules/cms-kit/angular/projects/dev-app/src/app/home/home.module.ts new file mode 100644 index 0000000000..72d20ccc65 --- /dev/null +++ b/modules/cms-kit/angular/projects/dev-app/src/app/home/home.module.ts @@ -0,0 +1,10 @@ +import { NgModule } from '@angular/core'; +import { SharedModule } from '../shared/shared.module'; +import { HomeRoutingModule } from './home-routing.module'; +import { HomeComponent } from './home.component'; + +@NgModule({ + declarations: [HomeComponent], + imports: [SharedModule, HomeRoutingModule], +}) +export class HomeModule {} diff --git a/modules/cms-kit/angular/projects/dev-app/src/app/route.provider.ts b/modules/cms-kit/angular/projects/dev-app/src/app/route.provider.ts new file mode 100644 index 0000000000..a412259466 --- /dev/null +++ b/modules/cms-kit/angular/projects/dev-app/src/app/route.provider.ts @@ -0,0 +1,25 @@ +import { RoutesService, eLayoutType } from '@abp/ng.core'; +import { APP_INITIALIZER } from '@angular/core'; + +export const APP_ROUTE_PROVIDER = [ + { + provide: APP_INITIALIZER, + useFactory: configureRoutes, + deps: [RoutesService], + multi: true, + }, +]; + +function configureRoutes(routes: RoutesService) { + return () => { + routes.add([ + { + path: '/', + name: 'Home', + iconClass: 'fas fa-home', + order: 1, + layout: eLayoutType.application, + }, + ]); + }; +} diff --git a/modules/cms-kit/angular/projects/dev-app/src/app/shared/shared.module.ts b/modules/cms-kit/angular/projects/dev-app/src/app/shared/shared.module.ts new file mode 100644 index 0000000000..1f268b4572 --- /dev/null +++ b/modules/cms-kit/angular/projects/dev-app/src/app/shared/shared.module.ts @@ -0,0 +1,26 @@ +import { CoreModule } from '@abp/ng.core'; +import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; +import { NgModule } from '@angular/core'; +import { ThemeBasicModule } from '@abp/ng.theme.basic'; +import { ThemeSharedModule } from '@abp/ng.theme.shared'; +import { NgxValidateCoreModule } from '@ngx-validate/core'; + +@NgModule({ + declarations: [], + imports: [ + CoreModule, + ThemeSharedModule, + ThemeBasicModule, + NgbDropdownModule, + NgxValidateCoreModule, + ], + exports: [ + CoreModule, + ThemeSharedModule, + ThemeBasicModule, + NgbDropdownModule, + NgxValidateCoreModule, + ], + providers: [], +}) +export class SharedModule {} diff --git a/modules/cms-kit/angular/projects/dev-app/src/assets/.gitkeep b/modules/cms-kit/angular/projects/dev-app/src/assets/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/cms-kit/angular/projects/dev-app/src/environments/environment.prod.ts b/modules/cms-kit/angular/projects/dev-app/src/environments/environment.prod.ts new file mode 100644 index 0000000000..6850f7102a --- /dev/null +++ b/modules/cms-kit/angular/projects/dev-app/src/environments/environment.prod.ts @@ -0,0 +1,24 @@ +export const environment = { + production: true, + application: { + name: 'CmsKit', + logoUrl: '', + }, + oAuthConfig: { + issuer: 'https://localhost:44318', + clientId: 'CmsKit_ConsoleTestApp', + dummyClientSecret: '1q2w3e*', + scope: 'CmsKit', + showDebugInformation: true, + oidc: false, + requireHttps: true, + }, + apis: { + default: { + url: 'https://localhost:44318', + }, + CmsKit: { + url: 'https://localhost:44371', + }, + }, +}; diff --git a/modules/cms-kit/angular/projects/dev-app/src/environments/environment.ts b/modules/cms-kit/angular/projects/dev-app/src/environments/environment.ts new file mode 100644 index 0000000000..f1381092b1 --- /dev/null +++ b/modules/cms-kit/angular/projects/dev-app/src/environments/environment.ts @@ -0,0 +1,24 @@ +export const environment = { + production: false, + application: { + name: 'CmsKit', + logoUrl: '', + }, + oAuthConfig: { + issuer: 'https://localhost:44318', + clientId: 'CmsKit_ConsoleTestApp', + dummyClientSecret: '1q2w3e*', + scope: 'CmsKit', + showDebugInformation: true, + oidc: false, + requireHttps: true, + }, + apis: { + default: { + url: 'https://localhost:44318', + }, + CmsKit: { + url: 'https://localhost:44371', + }, + }, +}; diff --git a/modules/cms-kit/angular/projects/dev-app/src/index.html b/modules/cms-kit/angular/projects/dev-app/src/index.html new file mode 100644 index 0000000000..067936da71 --- /dev/null +++ b/modules/cms-kit/angular/projects/dev-app/src/index.html @@ -0,0 +1,13 @@ + + + + + DevApp + + + + + + + + diff --git a/modules/cms-kit/angular/projects/dev-app/src/main.ts b/modules/cms-kit/angular/projects/dev-app/src/main.ts new file mode 100644 index 0000000000..c7b673cf44 --- /dev/null +++ b/modules/cms-kit/angular/projects/dev-app/src/main.ts @@ -0,0 +1,12 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.error(err)); diff --git a/modules/cms-kit/angular/projects/dev-app/src/polyfills.ts b/modules/cms-kit/angular/projects/dev-app/src/polyfills.ts new file mode 100644 index 0000000000..6e00c561ed --- /dev/null +++ b/modules/cms-kit/angular/projects/dev-app/src/polyfills.ts @@ -0,0 +1,66 @@ +/*************************************************************************************************** + * Load `$localize` onto the global scope - used if i18n tags appear in Angular templates. + */ +import '@angular/localize/init'; +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/guide/browser-support + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** + * Web Animations `@angular/platform-browser/animations` + * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. + * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). + */ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + +/** + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + * because those flags need to be set before `zone.js` being loaded, and webpack + * will put import in the top of bundle, so user need to create a separate file + * in this directory (for example: zone-flags.ts), and put the following flags + * into that file, and then add the following code before importing zone.js. + * import './zone-flags'; + * + * The flags allowed in zone-flags.ts are listed here. + * + * The following flags will work for all browsers. + * + * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + * + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + * + * (window as any).__Zone_enable_cross_context_check = true; + * + */ + +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ diff --git a/modules/cms-kit/angular/projects/dev-app/src/styles.scss b/modules/cms-kit/angular/projects/dev-app/src/styles.scss new file mode 100644 index 0000000000..90d4ee0072 --- /dev/null +++ b/modules/cms-kit/angular/projects/dev-app/src/styles.scss @@ -0,0 +1 @@ +/* You can add global styles to this file, and also import other style files */ diff --git a/modules/cms-kit/angular/projects/dev-app/src/test.ts b/modules/cms-kit/angular/projects/dev-app/src/test.ts new file mode 100644 index 0000000000..50193eb0f2 --- /dev/null +++ b/modules/cms-kit/angular/projects/dev-app/src/test.ts @@ -0,0 +1,25 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/dist/zone-testing'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +declare const require: { + context(path: string, deep?: boolean, filter?: RegExp): { + keys(): string[]; + (id: string): T; + }; +}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); diff --git a/modules/cms-kit/angular/projects/dev-app/tsconfig.app.json b/modules/cms-kit/angular/projects/dev-app/tsconfig.app.json new file mode 100644 index 0000000000..406dd61a4a --- /dev/null +++ b/modules/cms-kit/angular/projects/dev-app/tsconfig.app.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "../../out-tsc/app", + "types": [] + }, + "files": [ + "src/main.ts", + "src/polyfills.ts" + ], + "include": [ + "src/**/*.d.ts" + ] +} diff --git a/modules/cms-kit/angular/projects/dev-app/tsconfig.prod.json b/modules/cms-kit/angular/projects/dev-app/tsconfig.prod.json new file mode 100644 index 0000000000..3d4cab2c18 --- /dev/null +++ b/modules/cms-kit/angular/projects/dev-app/tsconfig.prod.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.prod.json", + "compilerOptions": { + "outDir": "../../out-tsc/app", + "types": [] + }, + "files": ["src/main.ts", "src/polyfills.ts"], + "include": ["src/**/*.d.ts"] +} diff --git a/modules/cms-kit/angular/projects/dev-app/tsconfig.spec.json b/modules/cms-kit/angular/projects/dev-app/tsconfig.spec.json new file mode 100644 index 0000000000..ed5461c09c --- /dev/null +++ b/modules/cms-kit/angular/projects/dev-app/tsconfig.spec.json @@ -0,0 +1,18 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "../../out-tsc/spec", + "types": [ + "jasmine", + "node" + ] + }, + "files": [ + "src/test.ts", + "src/polyfills.ts" + ], + "include": [ + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/modules/cms-kit/angular/projects/dev-app/tslint.json b/modules/cms-kit/angular/projects/dev-app/tslint.json new file mode 100644 index 0000000000..19e8161a0b --- /dev/null +++ b/modules/cms-kit/angular/projects/dev-app/tslint.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tslint.json", + "rules": { + "directive-selector": [ + true, + "attribute", + "app", + "camelCase" + ], + "component-selector": [ + true, + "element", + "app", + "kebab-case" + ] + } +} diff --git a/modules/cms-kit/angular/tsconfig.base.json b/modules/cms-kit/angular/tsconfig.base.json new file mode 100644 index 0000000000..c315ff5654 --- /dev/null +++ b/modules/cms-kit/angular/tsconfig.base.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.prod.json", + "compilerOptions": { + "paths": { + "@my-company-name/cms-kit": [ + "projects/cms-kit/src/public-api.ts" + ], + "@my-company-name/cms-kit/config": [ + "projects/cms-kit/config/src/public-api.ts" + ] + } + } +} diff --git a/modules/cms-kit/angular/tsconfig.json b/modules/cms-kit/angular/tsconfig.json new file mode 100644 index 0000000000..3e169eac5c --- /dev/null +++ b/modules/cms-kit/angular/tsconfig.json @@ -0,0 +1,23 @@ +/* + This is a "Solution Style" tsconfig.json file, and is used by editors and TypeScript’s language server to improve development experience. + It is not intended to be used to perform a compilation. + + To learn more about this file see: https://angular.io/config/solution-tsconfig. +*/ +{ + "files": [], + "references": [ + { + "path": "./projects/cms-kit/tsconfig.lib.json" + }, + { + "path": "./projects/cms-kit/tsconfig.spec.json" + }, + { + "path": "./projects/dev-app/tsconfig.app.json" + }, + { + "path": "./projects/dev-app/tsconfig.spec.json" + } + ] +} \ No newline at end of file diff --git a/modules/cms-kit/angular/tsconfig.prod.json b/modules/cms-kit/angular/tsconfig.prod.json new file mode 100644 index 0000000000..8cbc5cd8a7 --- /dev/null +++ b/modules/cms-kit/angular/tsconfig.prod.json @@ -0,0 +1,20 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "downlevelIteration": true, + "experimentalDecorators": true, + "module": "esnext", + "moduleResolution": "node", + "importHelpers": true, + "target": "es2015", + "lib": ["es2018", "dom"] + }, + "angularCompilerOptions": { + "fullTemplateTypeCheck": true, + "strictInjectionParameters": true + } +} diff --git a/modules/cms-kit/angular/tslint.json b/modules/cms-kit/angular/tslint.json new file mode 100644 index 0000000000..def88d1c87 --- /dev/null +++ b/modules/cms-kit/angular/tslint.json @@ -0,0 +1,136 @@ +{ + "extends": "tslint:recommended", + "rulesDirectory": [ + "codelyzer" + ], + "rules": { + "align": { + "options": [ + "parameters", + "statements" + ] + }, + "array-type": false, + "arrow-return-shorthand": true, + "curly": true, + "deprecation": { + "severity": "warning" + }, + "eofline": true, + "import-blacklist": [ + true, + "rxjs/Rx" + ], + "import-spacing": true, + "indent": { + "options": [ + "spaces" + ] + }, + "max-classes-per-file": false, + "max-line-length": [ + true, + 140 + ], + "member-ordering": [ + true, + { + "order": [ + "static-field", + "instance-field", + "static-method", + "instance-method" + ] + } + ], + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-empty": false, + "no-inferrable-types": [ + true, + "ignore-params" + ], + "no-non-null-assertion": true, + "no-redundant-jsdoc": true, + "no-switch-case-fall-through": true, + "no-var-requires": false, + "object-literal-key-quotes": [ + true, + "as-needed" + ], + "quotemark": [ + true, + "single" + ], + "semicolon": { + "options": [ + "always" + ] + }, + "space-before-function-paren": { + "options": { + "anonymous": "never", + "asyncArrow": "always", + "constructor": "never", + "method": "never", + "named": "never" + } + }, + "typedef-whitespace": { + "options": [ + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + }, + { + "call-signature": "onespace", + "index-signature": "onespace", + "parameter": "onespace", + "property-declaration": "onespace", + "variable-declaration": "onespace" + } + ] + }, + "variable-name": { + "options": [ + "ban-keywords", + "check-format", + "allow-pascal-case" + ] + }, + "whitespace": { + "options": [ + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type", + "check-typecast" + ] + }, + "component-class-suffix": true, + "contextual-lifecycle": true, + "directive-class-suffix": true, + "no-conflicting-lifecycle": true, + "no-host-metadata-property": true, + "no-input-rename": true, + "no-inputs-metadata-property": true, + "no-output-native": true, + "no-output-on-prefix": true, + "no-output-rename": true, + "no-outputs-metadata-property": true, + "template-banana-in-box": true, + "template-no-negated-async": true, + "use-lifecycle-interface": true, + "use-pipe-transform-interface": true + } +} diff --git a/modules/cms-kit/common.props b/modules/cms-kit/common.props new file mode 100644 index 0000000000..0e2bf35066 --- /dev/null +++ b/modules/cms-kit/common.props @@ -0,0 +1,13 @@ + + + latest + 0.1.0 + $(NoWarn);CS1591 + module + + + + + + + \ No newline at end of file diff --git a/modules/cms-kit/database/Dockerfile b/modules/cms-kit/database/Dockerfile new file mode 100644 index 0000000000..6257db9899 --- /dev/null +++ b/modules/cms-kit/database/Dockerfile @@ -0,0 +1,20 @@ +FROM mcr.microsoft.com/dotnet/core/sdk:3.0-buster AS build +COPY . . + +WORKDIR /templates/service/host/IdentityServerHost +RUN dotnet restore +RUN dotnet ef migrations script -i -o migrations-IdentityServerHost.sql + +WORKDIR /templates/service/host/Volo.CmsKit.Host +RUN dotnet restore +RUN dotnet ef migrations script -i -o migrations-CmsKit.sql + +FROM mcr.microsoft.com/mssql-tools AS final +WORKDIR /src +COPY --from=build /templates/service/host/IdentityServerHost/migrations-IdentityServerHost.sql migrations-IdentityServerHost.sql +COPY --from=build /templates/service/host/Volo.CmsKit.Host/migrations-CmsKit.sql migrations-CmsKit.sql +COPY --from=build /templates/service/database/entrypoint.sh . +RUN /bin/bash -c "sed -i $'s/\r$//' entrypoint.sh" +RUN chmod +x ./entrypoint.sh + +ENTRYPOINT ["./entrypoint.sh"] \ No newline at end of file diff --git a/modules/cms-kit/database/entrypoint.sh b/modules/cms-kit/database/entrypoint.sh new file mode 100644 index 0000000000..dd9e4d6610 --- /dev/null +++ b/modules/cms-kit/database/entrypoint.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +until /opt/mssql-tools/bin/sqlcmd -S sqlserver -U SA -P $SA_PASSWORD -Q 'SELECT name FROM master.sys.databases'; do +>&2 echo "SQL Server is starting up" +sleep 1 +done + +/opt/mssql-tools/bin/sqlcmd -S sqlserver -U SA -P $SA_PASSWORD -Q "CREATE DATABASE [$IdentityServer_DB]" +/opt/mssql-tools/bin/sqlcmd -S sqlserver -U SA -P $SA_PASSWORD -Q "CREATE DATABASE [$CmsKit_DB]" + +/opt/mssql-tools/bin/sqlcmd -d $IdentityServer_DB -S sqlserver -U sa -P $SA_PASSWORD -i migrations-IdentityServerHost.sql +/opt/mssql-tools/bin/sqlcmd -d $CmsKit_DB -S sqlserver -U sa -P $SA_PASSWORD -i migrations-CmsKit.sql \ No newline at end of file diff --git a/modules/cms-kit/docker-compose.migrations.yml b/modules/cms-kit/docker-compose.migrations.yml new file mode 100644 index 0000000000..7897fdc250 --- /dev/null +++ b/modules/cms-kit/docker-compose.migrations.yml @@ -0,0 +1,13 @@ +version: '3.4' + +services: + migrations: + build: + context: ../../ + dockerfile: templates/service/database/Dockerfile + depends_on: + - sqlserver + environment: + - IdentityServer_DB=CmsKit_Identity + - CmsKit_DB=CmsKit_ModuleDb + - SA_PASSWORD=yourStrong(!)Password diff --git a/modules/cms-kit/docker-compose.override.yml b/modules/cms-kit/docker-compose.override.yml new file mode 100644 index 0000000000..94b9df0c22 --- /dev/null +++ b/modules/cms-kit/docker-compose.override.yml @@ -0,0 +1,29 @@ +version: '3.4' + +services: + sqlserver: + environment: + - SA_PASSWORD=yourStrong(!)Password + - ACCEPT_EULA=Y + ports: + - "51599:1433" + + identity-server: + environment: + - ASPNETCORE_URLS=http://0.0.0.0:80 + - ConnectionStrings__Default=Server=sqlserver;Database=CmsKit_Identity;Trusted_Connection=True;MultipleActiveResultSets=true;User=sa;Password=yourStrong(!)Password;Integrated Security=false + - ConnectionStrings__SqlServerCache=Server=sqlserver;Database=CmsKit_Cache;Trusted_Connection=True;MultipleActiveResultSets=true;User=sa;Password=yourStrong(!)Password;Integrated Security=false + ports: + - "51600:80" + + cms-kit: + environment: + - ASPNETCORE_URLS=http://0.0.0.0:80 + - ConnectionStrings__Default=Server=sqlserver;Database=CmsKit_ModuleDb;Trusted_Connection=True;MultipleActiveResultSets=true;User=sa;Password=yourStrong(!)Password;Integrated Security=false + - ConnectionStrings__AbpSettingManagement=Server=sqlserver;Database=CmsKit_Identity;Trusted_Connection=True;MultipleActiveResultSets=true;User=sa;Password=yourStrong(!)Password;Integrated Security=false + - ConnectionStrings__AbpPermissionManagement=Server=sqlserver;Database=CmsKit_Identity;Trusted_Connection=True;MultipleActiveResultSets=true;User=sa;Password=yourStrong(!)Password;Integrated Security=false + - ConnectionStrings__AbpAuditLogging=Server=sqlserver;Database=CmsKit_Identity;Trusted_Connection=True;MultipleActiveResultSets=true;User=sa;Password=yourStrong(!)Password;Integrated Security=false + - ConnectionStrings__SqlServerCache=Server=sqlserver;Database=CmsKit_Cache;Trusted_Connection=True;MultipleActiveResultSets=true;User=sa;Password=yourStrong(!)Password;Integrated Security=false + - AuthServer__Authority=http://identity-server + ports: + - "51601:80" \ No newline at end of file diff --git a/modules/cms-kit/docker-compose.yml b/modules/cms-kit/docker-compose.yml new file mode 100644 index 0000000000..564fd5ac06 --- /dev/null +++ b/modules/cms-kit/docker-compose.yml @@ -0,0 +1,25 @@ +version: '3.4' + +services: + sqlserver: + image: mcr.microsoft.com/mssql/server + volumes: + - dbdata:/var/opt/mssql + + identity-server: + build: + context: ../../ + dockerfile: templates/service/host/IdentityServerHost/Dockerfile + depends_on: + - sqlserver + + cms-kit: + build: + context: ../../ + dockerfile: templates/service/host/Volo.CmsKit.Host/Dockerfile + depends_on: + - sqlserver + - identity-server + +volumes: + dbdata: \ No newline at end of file diff --git a/modules/cms-kit/host/Volo.CmsKit.Host.Shared/MultiTenancy/MultiTenancyConsts.cs b/modules/cms-kit/host/Volo.CmsKit.Host.Shared/MultiTenancy/MultiTenancyConsts.cs new file mode 100644 index 0000000000..8c222ee1ce --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.Host.Shared/MultiTenancy/MultiTenancyConsts.cs @@ -0,0 +1,10 @@ +namespace Volo.CmsKit.MultiTenancy +{ + public static class MultiTenancyConsts + { + /* Enable/disable multi-tenancy in a single point + * to test your module with multi-tenancy. + */ + public const bool IsEnabled = false; + } +} diff --git a/modules/cms-kit/host/Volo.CmsKit.Host.Shared/Volo.CmsKit.Host.Shared.csproj b/modules/cms-kit/host/Volo.CmsKit.Host.Shared/Volo.CmsKit.Host.Shared.csproj new file mode 100644 index 0000000000..3f8c167b98 --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.Host.Shared/Volo.CmsKit.Host.Shared.csproj @@ -0,0 +1,10 @@ + + + + + + netstandard2.0 + Volo.CmsKit + + + diff --git a/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/CmsKitHttpApiHostModule.cs b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/CmsKitHttpApiHostModule.cs new file mode 100644 index 0000000000..c63d28081f --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/CmsKitHttpApiHostModule.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Claims; +using IdentityModel; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Volo.CmsKit.EntityFrameworkCore; +using Volo.CmsKit.MultiTenancy; +using StackExchange.Redis; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.Swagger; +using Volo.Abp; +using Volo.Abp.AspNetCore.MultiTenancy; +using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy; +using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; +using Volo.Abp.AspNetCore.Serilog; +using Volo.Abp.AuditLogging.EntityFrameworkCore; +using Volo.Abp.Autofac; +using Volo.Abp.Caching; +using Volo.Abp.Caching.StackExchangeRedis; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore.SqlServer; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; +using Volo.Abp.PermissionManagement.EntityFrameworkCore; +using Volo.Abp.Security.Claims; +using Volo.Abp.SettingManagement.EntityFrameworkCore; +using Volo.Abp.VirtualFileSystem; + +namespace Volo.CmsKit +{ + [DependsOn( + typeof(CmsKitApplicationModule), + typeof(CmsKitEntityFrameworkCoreModule), + typeof(CmsKitHttpApiModule), + typeof(AbpAspNetCoreMvcUiMultiTenancyModule), + typeof(AbpAutofacModule), + typeof(AbpCachingStackExchangeRedisModule), + typeof(AbpEntityFrameworkCoreSqlServerModule), + typeof(AbpAuditLoggingEntityFrameworkCoreModule), + typeof(AbpPermissionManagementEntityFrameworkCoreModule), + typeof(AbpSettingManagementEntityFrameworkCoreModule), + typeof(AbpAspNetCoreSerilogModule) + )] + public class CmsKitHttpApiHostModule : AbpModule + { + private const string DefaultCorsPolicyName = "Default"; + + public override void ConfigureServices(ServiceConfigurationContext context) + { + var hostingEnvironment = context.Services.GetHostingEnvironment(); + var configuration = context.Services.GetConfiguration(); + + Configure(options => + { + options.UseSqlServer(); + }); + + Configure(options => + { + options.IsEnabled = MultiTenancyConsts.IsEnabled; + }); + + if (hostingEnvironment.IsDevelopment()) + { + Configure(options => + { + options.FileSets.ReplaceEmbeddedByPhysical(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}..{0}src{0}Volo.CmsKit.Domain.Shared", Path.DirectorySeparatorChar))); + options.FileSets.ReplaceEmbeddedByPhysical(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}..{0}src{0}Volo.CmsKit.Domain", Path.DirectorySeparatorChar))); + options.FileSets.ReplaceEmbeddedByPhysical(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}..{0}src{0}Volo.CmsKit.Application.Contracts", Path.DirectorySeparatorChar))); + options.FileSets.ReplaceEmbeddedByPhysical(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}..{0}src{0}Volo.CmsKit.Application", Path.DirectorySeparatorChar))); + }); + } + + context.Services.AddSwaggerGen( + options => + { + options.SwaggerDoc("v1", new OpenApiInfo { Title = "CmsKit API", Version = "v1" }); + options.DocInclusionPredicate((docName, description) => true); + options.CustomSchemaIds(type => type.FullName); + }); + + Configure(options => + { + options.Languages.Add(new LanguageInfo("cs", "cs", "Čeština")); + options.Languages.Add(new LanguageInfo("en", "en", "English")); + options.Languages.Add(new LanguageInfo("pt-BR", "pt-BR", "Português")); + options.Languages.Add(new LanguageInfo("ru", "ru", "Русский")); + options.Languages.Add(new LanguageInfo("tr", "tr", "Türkçe")); + options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文")); + options.Languages.Add(new LanguageInfo("zh-Hant", "zh-Hant", "繁體中文")); + }); + + //Updates AbpClaimTypes to be compatible with identity server claims. + AbpClaimTypes.UserId = JwtClaimTypes.Subject; + AbpClaimTypes.UserName = JwtClaimTypes.Name; + AbpClaimTypes.Role = JwtClaimTypes.Role; + AbpClaimTypes.Email = JwtClaimTypes.Email; + + context.Services.AddAuthentication("Bearer") + .AddIdentityServerAuthentication(options => + { + options.Authority = configuration["AuthServer:Authority"]; + options.RequireHttpsMetadata = false; + options.ApiName = "CmsKit"; + }); + + Configure(options => + { + options.KeyPrefix = "CmsKit:"; + }); + + if (!hostingEnvironment.IsDevelopment()) + { + var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]); + context.Services + .AddDataProtection() + .PersistKeysToStackExchangeRedis(redis, "CmsKit-Protection-Keys"); + } + + context.Services.AddCors(options => + { + options.AddPolicy(DefaultCorsPolicyName, builder => + { + builder + .WithOrigins( + configuration["App:CorsOrigins"] + .Split(",", StringSplitOptions.RemoveEmptyEntries) + .Select(o => o.RemovePostFix("/")) + .ToArray() + ) + .WithAbpExposedHeaders() + .SetIsOriginAllowedToAllowWildcardSubdomains() + .AllowAnyHeader() + .AllowAnyMethod() + .AllowCredentials(); + }); + }); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + var app = context.GetApplicationBuilder(); + var env = context.GetEnvironment(); + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseErrorPage(); + app.UseHsts(); + } + + app.UseHttpsRedirection(); + app.UseCorrelationId(); + app.UseVirtualFiles(); + app.UseRouting(); + app.UseCors(DefaultCorsPolicyName); + app.UseAuthentication(); + app.UseAbpClaimsMap(); + if (MultiTenancyConsts.IsEnabled) + { + app.UseMultiTenancy(); + } + app.UseAbpRequestLocalization(); + app.UseAuthorization(); + app.UseSwagger(); + app.UseSwaggerUI(options => + { + options.SwaggerEndpoint("/swagger/v1/swagger.json", "Support APP API"); + }); + app.UseAuditing(); + app.UseAbpSerilogEnrichers(); + app.UseConfiguredEndpoints(); + } + } +} diff --git a/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Controllers/HomeController.cs b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Controllers/HomeController.cs new file mode 100644 index 0000000000..a9d544e8af --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Controllers/HomeController.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc; + +namespace Volo.CmsKit.Controllers +{ + public class HomeController : AbpController + { + public ActionResult Index() + { + return Redirect("~/swagger"); + } + } +} diff --git a/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Dockerfile b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Dockerfile new file mode 100644 index 0000000000..c13abce9a3 --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Dockerfile @@ -0,0 +1,18 @@ +FROM mcr.microsoft.com/dotnet/core/aspnet:3.0-buster-slim AS base +WORKDIR /app +EXPOSE 80 + +FROM mcr.microsoft.com/dotnet/core/sdk:3.0-buster AS build +WORKDIR /src +COPY . . +WORKDIR /src/templates/service/host/Volo.CmsKit.HttpApi.Host +RUN dotnet restore -nowarn:msb3202,nu1503 +RUN dotnet build --no-restore -c Release -o /app + +FROM build AS publish +RUN dotnet publish --no-restore -c Release -o /app + +FROM base AS final +WORKDIR /app +COPY --from=publish /app . +ENTRYPOINT ["dotnet", "Volo.CmsKit.HttpApi.Host.dll"] diff --git a/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/EntityFrameworkCore/CmsKitHttpApiHostMigrationsDbContext.cs b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/EntityFrameworkCore/CmsKitHttpApiHostMigrationsDbContext.cs new file mode 100644 index 0000000000..c31c977ebe --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/EntityFrameworkCore/CmsKitHttpApiHostMigrationsDbContext.cs @@ -0,0 +1,21 @@ +using Microsoft.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +namespace Volo.CmsKit.EntityFrameworkCore +{ + public class CmsKitHttpApiHostMigrationsDbContext : AbpDbContext + { + public CmsKitHttpApiHostMigrationsDbContext(DbContextOptions options) + : base(options) + { + + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.ConfigureCmsKit(); + } + } +} diff --git a/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/EntityFrameworkCore/CmsKitHttpApiHostMigrationsDbContextFactory.cs b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/EntityFrameworkCore/CmsKitHttpApiHostMigrationsDbContextFactory.cs new file mode 100644 index 0000000000..3a6dbb47ad --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/EntityFrameworkCore/CmsKitHttpApiHostMigrationsDbContextFactory.cs @@ -0,0 +1,29 @@ +using System.IO; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Extensions.Configuration; + +namespace Volo.CmsKit.EntityFrameworkCore +{ + public class CmsKitHttpApiHostMigrationsDbContextFactory : IDesignTimeDbContextFactory + { + public CmsKitHttpApiHostMigrationsDbContext CreateDbContext(string[] args) + { + var configuration = BuildConfiguration(); + + var builder = new DbContextOptionsBuilder() + .UseSqlServer(configuration.GetConnectionString("CmsKit")); + + return new CmsKitHttpApiHostMigrationsDbContext(builder.Options); + } + + private static IConfigurationRoot BuildConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false); + + return builder.Build(); + } + } +} diff --git a/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Program.cs b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Program.cs new file mode 100644 index 0000000000..7349f8eb0c --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Program.cs @@ -0,0 +1,51 @@ +using System; +using System.IO; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; +using Serilog; +using Serilog.Events; + +namespace Volo.CmsKit +{ + public class Program + { + public static int Main(string[] args) + { + Log.Logger = new LoggerConfiguration() +#if DEBUG + .MinimumLevel.Debug() +#else + .MinimumLevel.Information() +#endif + .MinimumLevel.Override("Microsoft", LogEventLevel.Information) + .Enrich.FromLogContext() + .WriteTo.File("Logs/logs.txt") + .CreateLogger(); + + try + { + Log.Information("Starting web host."); + CreateHostBuilder(args).Build().Run(); + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Host terminated unexpectedly!"); + return 1; + } + finally + { + Log.CloseAndFlush(); + } + } + + internal static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }) + .UseAutofac() + .UseSerilog(); + } +} diff --git a/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Properties/launchSettings.json b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Properties/launchSettings.json new file mode 100644 index 0000000000..48d1823cea --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "https://localhost:44371", + "sslPort": 44371 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Volo.CmsKit.DemoApp": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "https://localhost:44371", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Startup.cs b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Startup.cs new file mode 100644 index 0000000000..bfbaaadfa4 --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Startup.cs @@ -0,0 +1,21 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Volo.CmsKit +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddApplication(); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) + { + app.InitializeApplication(); + } + } +} diff --git a/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Volo.CmsKit.HttpApi.Host.csproj b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Volo.CmsKit.HttpApi.Host.csproj new file mode 100644 index 0000000000..2a0c5e57c1 --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/Volo.CmsKit.HttpApi.Host.csproj @@ -0,0 +1,43 @@ + + + + + + netcoreapp3.1 + Volo.CmsKit + true + Volo.CmsKit-c2d31439-b723-48e2-b061-5ebd7aeb6010 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/appsettings.json b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/appsettings.json new file mode 100644 index 0000000000..06b66fcede --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/appsettings.json @@ -0,0 +1,15 @@ +{ + "App": { + "CorsOrigins": "https://*.CmsKit.com,http://localhost:4200" + }, + "ConnectionStrings": { + "Default": "Server=localhost;Database=CmsKit_Main;Trusted_Connection=True;MultipleActiveResultSets=true", + "CmsKit": "Server=localhost;Database=CmsKit_Module;Trusted_Connection=True;MultipleActiveResultSets=true" + }, + "Redis": { + "Configuration": "127.0.0.1" + }, + "AuthServer": { + "Authority": "https://localhost:44318/" + } +} diff --git a/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/yarn.lock b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/yarn.lock new file mode 100644 index 0000000000..fb57ccd13a --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/CmsKitIdentityServerModule.cs b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/CmsKitIdentityServerModule.cs new file mode 100644 index 0000000000..faf90a3fc2 --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/CmsKitIdentityServerModule.cs @@ -0,0 +1,222 @@ +using System; +using System.Linq; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Volo.CmsKit.MultiTenancy; +using StackExchange.Redis; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.Swagger; +using Volo.Abp; +using Volo.Abp.Account; +using Volo.Abp.Account.Web; +using Volo.Abp.AspNetCore.Authentication.JwtBearer; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy; +using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic; +using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; +using Volo.Abp.AspNetCore.Serilog; +using Volo.Abp.Auditing; +using Volo.Abp.AuditLogging.EntityFrameworkCore; +using Volo.Abp.Autofac; +using Volo.Abp.Caching; +using Volo.Abp.Caching.StackExchangeRedis; +using Volo.Abp.Data; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore.SqlServer; +using Volo.Abp.FeatureManagement; +using Volo.Abp.Identity; +using Volo.Abp.Identity.EntityFrameworkCore; +using Volo.Abp.IdentityServer.EntityFrameworkCore; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; +using Volo.Abp.PermissionManagement; +using Volo.Abp.PermissionManagement.EntityFrameworkCore; +using Volo.Abp.PermissionManagement.HttpApi; +using Volo.Abp.PermissionManagement.Identity; +using Volo.Abp.SettingManagement.EntityFrameworkCore; +using Volo.Abp.TenantManagement; +using Volo.Abp.TenantManagement.EntityFrameworkCore; +using Volo.Abp.Threading; +using Volo.Abp.UI.Navigation.Urls; + +namespace Volo.CmsKit +{ + [DependsOn( + typeof(AbpAccountWebIdentityServerModule), + typeof(AbpAccountApplicationModule), + typeof(AbpAspNetCoreMvcUiMultiTenancyModule), + typeof(AbpAspNetCoreMvcModule), + typeof(AbpAspNetCoreMvcUiBasicThemeModule), + typeof(AbpAuditLoggingEntityFrameworkCoreModule), + typeof(AbpAutofacModule), + typeof(AbpCachingStackExchangeRedisModule), + typeof(AbpEntityFrameworkCoreSqlServerModule), + typeof(AbpIdentityEntityFrameworkCoreModule), + typeof(AbpIdentityApplicationModule), + typeof(AbpIdentityHttpApiModule), + typeof(AbpIdentityServerEntityFrameworkCoreModule), + typeof(AbpPermissionManagementDomainIdentityModule), + typeof(AbpPermissionManagementEntityFrameworkCoreModule), + typeof(AbpPermissionManagementApplicationModule), + typeof(AbpPermissionManagementHttpApiModule), + typeof(AbpSettingManagementEntityFrameworkCoreModule), + typeof(AbpFeatureManagementApplicationModule), + typeof(AbpTenantManagementEntityFrameworkCoreModule), + typeof(AbpTenantManagementApplicationModule), + typeof(AbpTenantManagementHttpApiModule), + typeof(AbpAspNetCoreAuthenticationJwtBearerModule), + typeof(CmsKitApplicationContractsModule), + typeof(AbpAspNetCoreSerilogModule) + )] + public class CmsKitIdentityServerModule : AbpModule + { + private const string DefaultCorsPolicyName = "Default"; + + public override void ConfigureServices(ServiceConfigurationContext context) + { + var hostingEnvironment = context.Services.GetHostingEnvironment(); + var configuration = context.Services.GetConfiguration(); + + Configure(options => + { + options.UseSqlServer(); + }); + + context.Services.AddSwaggerGen( + options => + { + options.SwaggerDoc("v1", new OpenApiInfo { Title = "CmsKit API", Version = "v1" }); + options.DocInclusionPredicate((docName, description) => true); + options.CustomSchemaIds(type => type.FullName); + }); + + Configure(options => + { + options.Languages.Add(new LanguageInfo("cs", "cs", "Čeština")); + options.Languages.Add(new LanguageInfo("en", "en", "English")); + options.Languages.Add(new LanguageInfo("pt-BR", "pt-BR", "Português")); + options.Languages.Add(new LanguageInfo("ru", "ru", "Русский")); + options.Languages.Add(new LanguageInfo("tr", "tr", "Türkçe")); + options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文")); + options.Languages.Add(new LanguageInfo("zh-Hant", "zh-Hant", "繁體中文")); + }); + + Configure(options => + { + //options.IsEnabledForGetRequests = true; + options.ApplicationName = "AuthServer"; + }); + + Configure(options => + { + options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"]; + }); + + context.Services.AddAuthentication() + .AddIdentityServerAuthentication(options => + { + options.Authority = configuration["AuthServer:Authority"]; + options.RequireHttpsMetadata = false; + options.ApiName = configuration["AuthServer:ApiName"]; + }); + + Configure(options => + { + options.KeyPrefix = "CmsKit:"; + }); + + Configure(options => + { + options.IsEnabled = MultiTenancyConsts.IsEnabled; + }); + + if (!hostingEnvironment.IsDevelopment()) + { + var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]); + context.Services + .AddDataProtection() + .PersistKeysToStackExchangeRedis(redis, "CmsKit-Protection-Keys"); + } + + context.Services.AddCors(options => + { + options.AddPolicy(DefaultCorsPolicyName, builder => + { + builder + .WithOrigins( + configuration["App:CorsOrigins"] + .Split(",", StringSplitOptions.RemoveEmptyEntries) + .Select(o => o.RemovePostFix("/")) + .ToArray() + ) + .WithAbpExposedHeaders() + .SetIsOriginAllowedToAllowWildcardSubdomains() + .AllowAnyHeader() + .AllowAnyMethod() + .AllowCredentials(); + }); + }); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + var app = context.GetApplicationBuilder(); + var env = context.GetEnvironment(); + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseErrorPage(); + app.UseHsts(); + } + + app.UseHttpsRedirection(); + app.UseCorrelationId(); + app.UseVirtualFiles(); + app.UseRouting(); + app.UseCors(DefaultCorsPolicyName); + app.UseAuthentication(); + app.UseJwtTokenMiddleware(); + + if (MultiTenancyConsts.IsEnabled) + { + app.UseMultiTenancy(); + } + + app.UseAbpRequestLocalization(); + app.UseIdentityServer(); + app.UseAuthorization(); + app.UseSwagger(); + app.UseSwaggerUI(options => + { + options.SwaggerEndpoint("/swagger/v1/swagger.json", "Support APP API"); + }); + app.UseAuditing(); + app.UseAbpSerilogEnrichers(); + app.UseConfiguredEndpoints(); + + SeedData(context); + } + + private void SeedData(ApplicationInitializationContext context) + { + AsyncHelper.RunSync(async () => + { + using (var scope = context.ServiceProvider.CreateScope()) + { + await scope.ServiceProvider + .GetRequiredService() + .SeedAsync(); + } + }); + } + } +} diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Dockerfile b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Dockerfile new file mode 100644 index 0000000000..bc353a0abd --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Dockerfile @@ -0,0 +1,18 @@ +FROM mcr.microsoft.com/dotnet/core/aspnet:3.0-buster-slim AS base +WORKDIR /app +EXPOSE 80 + +FROM mcr.microsoft.com/dotnet/core/sdk:3.0-buster AS build +WORKDIR /src +COPY . . +WORKDIR /src/templates/service/host/Volo.CmsKit.IdentityServer +RUN dotnet restore -nowarn:msb3202,nu1503 +RUN dotnet build --no-restore -c Release -o /app + +FROM build AS publish +RUN dotnet publish --no-restore -c Release -o /app + +FROM base AS final +WORKDIR /app +COPY --from=publish /app . +ENTRYPOINT ["dotnet", "Volo.CmsKit.IdentityServer.dll"] diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/EntityFrameworkCore/IdentityServerHostMigrationsDbContext.cs b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/EntityFrameworkCore/IdentityServerHostMigrationsDbContext.cs new file mode 100644 index 0000000000..b0bd516dee --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/EntityFrameworkCore/IdentityServerHostMigrationsDbContext.cs @@ -0,0 +1,32 @@ +using Microsoft.EntityFrameworkCore; +using Volo.Abp.AuditLogging.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.Identity.EntityFrameworkCore; +using Volo.Abp.IdentityServer.EntityFrameworkCore; +using Volo.Abp.PermissionManagement.EntityFrameworkCore; +using Volo.Abp.SettingManagement.EntityFrameworkCore; +using Volo.Abp.TenantManagement.EntityFrameworkCore; + +namespace Volo.CmsKit.EntityFrameworkCore +{ + public class IdentityServerHostMigrationsDbContext : AbpDbContext + { + public IdentityServerHostMigrationsDbContext(DbContextOptions options) + : base(options) + { + + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.ConfigurePermissionManagement(); + modelBuilder.ConfigureSettingManagement(); + modelBuilder.ConfigureAuditLogging(); + modelBuilder.ConfigureIdentity(); + modelBuilder.ConfigureIdentityServer(); + modelBuilder.ConfigureTenantManagement(); + } + } +} diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/EntityFrameworkCore/IdentityServerHostMigrationsDbContextFactory.cs b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/EntityFrameworkCore/IdentityServerHostMigrationsDbContextFactory.cs new file mode 100644 index 0000000000..f6e80d62ec --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/EntityFrameworkCore/IdentityServerHostMigrationsDbContextFactory.cs @@ -0,0 +1,29 @@ +using System.IO; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Extensions.Configuration; + +namespace Volo.CmsKit.EntityFrameworkCore +{ + public class IdentityServerHostMigrationsDbContextFactory : IDesignTimeDbContextFactory + { + public IdentityServerHostMigrationsDbContext CreateDbContext(string[] args) + { + var configuration = BuildConfiguration(); + + var builder = new DbContextOptionsBuilder() + .UseSqlServer(configuration.GetConnectionString("Default")); + + return new IdentityServerHostMigrationsDbContext(builder.Options); + } + + private static IConfigurationRoot BuildConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false); + + return builder.Build(); + } + } +} diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/IdentityServer/IdentityServerDataSeedContributor.cs b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/IdentityServer/IdentityServerDataSeedContributor.cs new file mode 100644 index 0000000000..c245c663f8 --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/IdentityServer/IdentityServerDataSeedContributor.cs @@ -0,0 +1,218 @@ +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Guids; +using Volo.Abp.IdentityServer.ApiResources; +using Volo.Abp.IdentityServer.Clients; +using Volo.Abp.IdentityServer.IdentityResources; +using Volo.Abp.PermissionManagement; +using Volo.Abp.Uow; + +namespace Volo.CmsKit.IdentityServer +{ + public class IdentityServerDataSeedContributor : IDataSeedContributor, ITransientDependency + { + private readonly IApiResourceRepository _apiResourceRepository; + private readonly IClientRepository _clientRepository; + private readonly IIdentityResourceDataSeeder _identityResourceDataSeeder; + private readonly IGuidGenerator _guidGenerator; + private readonly IPermissionDataSeeder _permissionDataSeeder; + private readonly IConfiguration _configuration; + + public IdentityServerDataSeedContributor( + IClientRepository clientRepository, + IApiResourceRepository apiResourceRepository, + IIdentityResourceDataSeeder identityResourceDataSeeder, + IGuidGenerator guidGenerator, + IPermissionDataSeeder permissionDataSeeder, + IConfiguration configuration) + { + _clientRepository = clientRepository; + _apiResourceRepository = apiResourceRepository; + _identityResourceDataSeeder = identityResourceDataSeeder; + _guidGenerator = guidGenerator; + _permissionDataSeeder = permissionDataSeeder; + _configuration = configuration; + } + + [UnitOfWork] + public virtual async Task SeedAsync(DataSeedContext context) + { + await _identityResourceDataSeeder.CreateStandardResourcesAsync(); + await CreateApiResourcesAsync(); + await CreateClientsAsync(); + } + + private async Task CreateApiResourcesAsync() + { + var commonApiUserClaims = new[] + { + "email", + "email_verified", + "name", + "phone_number", + "phone_number_verified", + "role" + }; + + await CreateApiResourceAsync("CmsKit", commonApiUserClaims); + } + + private async Task CreateApiResourceAsync(string name, IEnumerable claims) + { + var apiResource = await _apiResourceRepository.FindByNameAsync(name); + if (apiResource == null) + { + apiResource = await _apiResourceRepository.InsertAsync( + new ApiResource( + _guidGenerator.Create(), + name, + name + " API" + ), + autoSave: true + ); + } + + foreach (var claim in claims) + { + if (apiResource.FindClaim(claim) == null) + { + apiResource.AddUserClaim(claim); + } + } + + return await _apiResourceRepository.UpdateAsync(apiResource); + } + + private async Task CreateClientsAsync() + { + const string commonSecret = "E5Xd4yMqjP5kjWFKrYgySBju6JVfCzMyFp7n2QmMrME="; + + var commonScopes = new[] + { + "email", + "openid", + "profile", + "role", + "phone", + "address", + "CmsKit" + }; + + var configurationSection = _configuration.GetSection("IdentityServer:Clients"); + + //Web Client + var webClientId = configurationSection["CmsKit_Web:ClientId"]; + if (!webClientId.IsNullOrWhiteSpace()) + { + var webClientRootUrl = configurationSection["CmsKit_Web:RootUrl"].EnsureEndsWith('/'); + await CreateClientAsync( + webClientId, + commonScopes, + new[] { "hybrid" }, + commonSecret, + redirectUri: $"{webClientRootUrl}signin-oidc", + postLogoutRedirectUri: $"{webClientRootUrl}signout-callback-oidc" + ); + } + + //Console Test Client + var consoleClientId = configurationSection["CmsKit_ConsoleTestApp:ClientId"]; + if (!consoleClientId.IsNullOrWhiteSpace()) + { + await CreateClientAsync( + consoleClientId, + commonScopes, + new[] { "password", "client_credentials" }, + commonSecret + ); + } + } + + private async Task CreateClientAsync( + string name, + IEnumerable scopes, + IEnumerable grantTypes, + string secret, + string redirectUri = null, + string postLogoutRedirectUri = null, + IEnumerable permissions = null) + { + var client = await _clientRepository.FindByCliendIdAsync(name); + if (client == null) + { + client = await _clientRepository.InsertAsync( + new Client( + _guidGenerator.Create(), + name + ) + { + ClientName = name, + ProtocolType = "oidc", + Description = name, + AlwaysIncludeUserClaimsInIdToken = true, + AllowOfflineAccess = true, + AbsoluteRefreshTokenLifetime = 31536000, //365 days + AccessTokenLifetime = 31536000, //365 days + AuthorizationCodeLifetime = 300, + IdentityTokenLifetime = 300, + RequireConsent = false + }, + autoSave: true + ); + } + + foreach (var scope in scopes) + { + if (client.FindScope(scope) == null) + { + client.AddScope(scope); + } + } + + foreach (var grantType in grantTypes) + { + if (client.FindGrantType(grantType) == null) + { + client.AddGrantType(grantType); + } + } + + if (client.FindSecret(secret) == null) + { + client.AddSecret(secret); + } + + if (redirectUri != null) + { + if (client.FindRedirectUri(redirectUri) == null) + { + client.AddRedirectUri(redirectUri); + } + } + + if (postLogoutRedirectUri != null) + { + if (client.FindPostLogoutRedirectUri(postLogoutRedirectUri) == null) + { + client.AddPostLogoutRedirectUri(postLogoutRedirectUri); + } + } + + if (permissions != null) + { + await _permissionDataSeeder.SeedAsync( + ClientPermissionValueProvider.ProviderName, + name, + permissions + ); + } + + return await _clientRepository.UpdateAsync(client); + } + } +} diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Migrations/20200810022553_Initial.Designer.cs b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Migrations/20200810022553_Initial.Designer.cs new file mode 100644 index 0000000000..aac1944355 --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Migrations/20200810022553_Initial.Designer.cs @@ -0,0 +1,1861 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Volo.Abp.EntityFrameworkCore; +using Volo.CmsKit.EntityFrameworkCore; + +namespace Volo.CmsKit.Migrations +{ + [DbContext(typeof(IdentityServerHostMigrationsDbContext))] + [Migration("20200810022553_Initial")] + partial class Initial + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) + .HasAnnotation("ProductVersion", "3.1.6") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApplicationName") + .HasColumnName("ApplicationName") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("BrowserInfo") + .HasColumnName("BrowserInfo") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("ClientId") + .HasColumnName("ClientId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ClientIpAddress") + .HasColumnName("ClientIpAddress") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ClientName") + .HasColumnName("ClientName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Comments") + .HasColumnName("Comments") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CorrelationId") + .HasColumnName("CorrelationId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Exceptions") + .HasColumnName("Exceptions") + .HasColumnType("nvarchar(4000)") + .HasMaxLength(4000); + + b.Property("ExecutionDuration") + .HasColumnName("ExecutionDuration") + .HasColumnType("int"); + + b.Property("ExecutionTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("HttpMethod") + .HasColumnName("HttpMethod") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("HttpStatusCode") + .HasColumnName("HttpStatusCode") + .HasColumnType("int"); + + b.Property("ImpersonatorTenantId") + .HasColumnName("ImpersonatorTenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("ImpersonatorUserId") + .HasColumnName("ImpersonatorUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantName") + .HasColumnType("nvarchar(max)"); + + b.Property("Url") + .HasColumnName("Url") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("UserId") + .HasColumnName("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .HasColumnName("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "ExecutionTime"); + + b.HasIndex("TenantId", "UserId", "ExecutionTime"); + + b.ToTable("AbpAuditLogs"); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLogAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuditLogId") + .HasColumnName("AuditLogId") + .HasColumnType("uniqueidentifier"); + + b.Property("ExecutionDuration") + .HasColumnName("ExecutionDuration") + .HasColumnType("int"); + + b.Property("ExecutionTime") + .HasColumnName("ExecutionTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("MethodName") + .HasColumnName("MethodName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Parameters") + .HasColumnName("Parameters") + .HasColumnType("nvarchar(2000)") + .HasMaxLength(2000); + + b.Property("ServiceName") + .HasColumnName("ServiceName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AuditLogId"); + + b.HasIndex("TenantId", "ServiceName", "MethodName", "ExecutionTime"); + + b.ToTable("AbpAuditLogActions"); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityChange", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuditLogId") + .HasColumnName("AuditLogId") + .HasColumnType("uniqueidentifier"); + + b.Property("ChangeTime") + .HasColumnName("ChangeTime") + .HasColumnType("datetime2"); + + b.Property("ChangeType") + .HasColumnName("ChangeType") + .HasColumnType("tinyint"); + + b.Property("EntityId") + .IsRequired() + .HasColumnName("EntityId") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("EntityTenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("EntityTypeFullName") + .IsRequired() + .HasColumnName("EntityTypeFullName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AuditLogId"); + + b.HasIndex("TenantId", "EntityTypeFullName", "EntityId"); + + b.ToTable("AbpEntityChanges"); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityPropertyChange", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EntityChangeId") + .HasColumnType("uniqueidentifier"); + + b.Property("NewValue") + .HasColumnName("NewValue") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("OriginalValue") + .HasColumnName("OriginalValue") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("PropertyName") + .IsRequired() + .HasColumnName("PropertyName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("PropertyTypeFullName") + .IsRequired() + .HasColumnName("PropertyTypeFullName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EntityChangeId"); + + b.ToTable("AbpEntityPropertyChanges"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityClaimType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("Description") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsStatic") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("Regex") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("RegexDescription") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Required") + .HasColumnType("bit"); + + b.Property("ValueType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("AbpClaimTypes"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDefault") + .HasColumnName("IsDefault") + .HasColumnType("bit"); + + b.Property("IsPublic") + .HasColumnName("IsPublic") + .HasColumnType("bit"); + + b.Property("IsStatic") + .HasColumnName("IsStatic") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedName") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName"); + + b.ToTable("AbpRoles"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("ClaimType") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AbpRoleClaims"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccessFailedCount") + .ValueGeneratedOnAdd() + .HasColumnName("AccessFailedCount") + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasColumnName("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("EmailConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("LockoutEnabled") + .ValueGeneratedOnAdd() + .HasColumnName("LockoutEnabled") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("Name") + .HasColumnName("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("NormalizedEmail") + .IsRequired() + .HasColumnName("NormalizedEmail") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .IsRequired() + .HasColumnName("NormalizedUserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("PasswordHash") + .HasColumnName("PasswordHash") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("PhoneNumber") + .HasColumnName("PhoneNumber") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("PhoneNumberConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("PhoneNumberConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("SecurityStamp") + .IsRequired() + .HasColumnName("SecurityStamp") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("Surname") + .HasColumnName("Surname") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TwoFactorEnabled") + .ValueGeneratedOnAdd() + .HasColumnName("TwoFactorEnabled") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("UserName") + .IsRequired() + .HasColumnName("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("Email"); + + b.HasIndex("NormalizedEmail"); + + b.HasIndex("NormalizedUserName"); + + b.HasIndex("UserName"); + + b.ToTable("AbpUsers"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("ClaimType") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AbpUserClaims"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .IsRequired() + .HasColumnType("nvarchar(196)") + .HasMaxLength(196); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "LoginProvider"); + + b.HasIndex("LoginProvider", "ProviderKey"); + + b.ToTable("AbpUserLogins"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserOrganizationUnit", b => + { + b.Property("OrganizationUnitId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("OrganizationUnitId", "UserId"); + + b.HasIndex("UserId", "OrganizationUnitId"); + + b.ToTable("AbpUserOrganizationUnits"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId", "UserId"); + + b.ToTable("AbpUserRoles"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Name") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AbpUserTokens"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Code") + .IsRequired() + .HasColumnName("Code") + .HasColumnType("nvarchar(95)") + .HasMaxLength(95); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnName("DisplayName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("Code"); + + b.HasIndex("ParentId"); + + b.ToTable("AbpOrganizationUnits"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnitRole", b => + { + b.Property("OrganizationUnitId") + .HasColumnType("uniqueidentifier"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("OrganizationUnitId", "RoleId"); + + b.HasIndex("RoleId", "OrganizationUnitId"); + + b.ToTable("AbpOrganizationUnitRoles"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.ApiResources.ApiResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(1000)") + .HasMaxLength(1000); + + b.Property("DisplayName") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("Properties") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("IdentityServerApiResources"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.ApiResources.ApiResourceClaim", b => + { + b.Property("ApiResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Type") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.HasKey("ApiResourceId", "Type"); + + b.ToTable("IdentityServerApiClaims"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.ApiResources.ApiScope", b => + { + b.Property("ApiResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("Description") + .HasColumnType("nvarchar(1000)") + .HasMaxLength(1000); + + b.Property("DisplayName") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("Emphasize") + .HasColumnType("bit"); + + b.Property("Required") + .HasColumnType("bit"); + + b.Property("ShowInDiscoveryDocument") + .HasColumnType("bit"); + + b.HasKey("ApiResourceId", "Name"); + + b.ToTable("IdentityServerApiScopes"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.ApiResources.ApiScopeClaim", b => + { + b.Property("ApiResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("Type") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.HasKey("ApiResourceId", "Name", "Type"); + + b.ToTable("IdentityServerApiScopeClaims"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.ApiResources.ApiSecret", b => + { + b.Property("ApiResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Type") + .HasColumnType("nvarchar(250)") + .HasMaxLength(250); + + b.Property("Value") + .HasColumnType("nvarchar(4000)") + .HasMaxLength(4000); + + b.Property("Description") + .HasColumnType("nvarchar(2000)") + .HasMaxLength(2000); + + b.Property("Expiration") + .HasColumnType("datetime2"); + + b.HasKey("ApiResourceId", "Type", "Value"); + + b.ToTable("IdentityServerApiSecrets"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.Client", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AbsoluteRefreshTokenLifetime") + .HasColumnType("int"); + + b.Property("AccessTokenLifetime") + .HasColumnType("int"); + + b.Property("AccessTokenType") + .HasColumnType("int"); + + b.Property("AllowAccessTokensViaBrowser") + .HasColumnType("bit"); + + b.Property("AllowOfflineAccess") + .HasColumnType("bit"); + + b.Property("AllowPlainTextPkce") + .HasColumnType("bit"); + + b.Property("AllowRememberConsent") + .HasColumnType("bit"); + + b.Property("AlwaysIncludeUserClaimsInIdToken") + .HasColumnType("bit"); + + b.Property("AlwaysSendClientClaims") + .HasColumnType("bit"); + + b.Property("AuthorizationCodeLifetime") + .HasColumnType("int"); + + b.Property("BackChannelLogoutSessionRequired") + .HasColumnType("bit"); + + b.Property("BackChannelLogoutUri") + .HasColumnType("nvarchar(2000)") + .HasMaxLength(2000); + + b.Property("ClientClaimsPrefix") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("ClientName") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("ClientUri") + .HasColumnType("nvarchar(2000)") + .HasMaxLength(2000); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("ConsentLifetime") + .HasColumnType("int"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(1000)") + .HasMaxLength(1000); + + b.Property("DeviceCodeLifetime") + .HasColumnType("int"); + + b.Property("EnableLocalLogin") + .HasColumnType("bit"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("FrontChannelLogoutSessionRequired") + .HasColumnType("bit"); + + b.Property("FrontChannelLogoutUri") + .HasColumnType("nvarchar(2000)") + .HasMaxLength(2000); + + b.Property("IdentityTokenLifetime") + .HasColumnType("int"); + + b.Property("IncludeJwtId") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("LogoUri") + .HasColumnType("nvarchar(2000)") + .HasMaxLength(2000); + + b.Property("PairWiseSubjectSalt") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("ProtocolType") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("RefreshTokenExpiration") + .HasColumnType("int"); + + b.Property("RefreshTokenUsage") + .HasColumnType("int"); + + b.Property("RequireClientSecret") + .HasColumnType("bit"); + + b.Property("RequireConsent") + .HasColumnType("bit"); + + b.Property("RequirePkce") + .HasColumnType("bit"); + + b.Property("SlidingRefreshTokenLifetime") + .HasColumnType("int"); + + b.Property("UpdateAccessTokenClaimsOnRefresh") + .HasColumnType("bit"); + + b.Property("UserCodeType") + .HasColumnType("nvarchar(100)") + .HasMaxLength(100); + + b.Property("UserSsoLifetime") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("IdentityServerClients"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientClaim", b => + { + b.Property("ClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("Type") + .HasColumnType("nvarchar(250)") + .HasMaxLength(250); + + b.Property("Value") + .HasColumnType("nvarchar(250)") + .HasMaxLength(250); + + b.HasKey("ClientId", "Type", "Value"); + + b.ToTable("IdentityServerClientClaims"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientCorsOrigin", b => + { + b.Property("ClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("Origin") + .HasColumnType("nvarchar(150)") + .HasMaxLength(150); + + b.HasKey("ClientId", "Origin"); + + b.ToTable("IdentityServerClientCorsOrigins"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientGrantType", b => + { + b.Property("ClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantType") + .HasColumnType("nvarchar(250)") + .HasMaxLength(250); + + b.HasKey("ClientId", "GrantType"); + + b.ToTable("IdentityServerClientGrantTypes"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientIdPRestriction", b => + { + b.Property("ClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.HasKey("ClientId", "Provider"); + + b.ToTable("IdentityServerClientIdPRestrictions"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientPostLogoutRedirectUri", b => + { + b.Property("ClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("PostLogoutRedirectUri") + .HasColumnType("nvarchar(2000)") + .HasMaxLength(2000); + + b.HasKey("ClientId", "PostLogoutRedirectUri"); + + b.ToTable("IdentityServerClientPostLogoutRedirectUris"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientProperty", b => + { + b.Property("ClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("Key") + .HasColumnType("nvarchar(250)") + .HasMaxLength(250); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(2000)") + .HasMaxLength(2000); + + b.HasKey("ClientId", "Key"); + + b.ToTable("IdentityServerClientProperties"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientRedirectUri", b => + { + b.Property("ClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("RedirectUri") + .HasColumnType("nvarchar(2000)") + .HasMaxLength(2000); + + b.HasKey("ClientId", "RedirectUri"); + + b.ToTable("IdentityServerClientRedirectUris"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientScope", b => + { + b.Property("ClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("Scope") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.HasKey("ClientId", "Scope"); + + b.ToTable("IdentityServerClientScopes"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientSecret", b => + { + b.Property("ClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("Type") + .HasColumnType("nvarchar(250)") + .HasMaxLength(250); + + b.Property("Value") + .HasColumnType("nvarchar(4000)") + .HasMaxLength(4000); + + b.Property("Description") + .HasColumnType("nvarchar(2000)") + .HasMaxLength(2000); + + b.Property("Expiration") + .HasColumnType("datetime2"); + + b.HasKey("ClientId", "Type", "Value"); + + b.ToTable("IdentityServerClientSecrets"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Devices.DeviceFlowCodes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("Data") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasMaxLength(50000); + + b.Property("DeviceCode") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("Expiration") + .IsRequired() + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("SubjectId") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("UserCode") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("DeviceCode") + .IsUnique(); + + b.HasIndex("Expiration"); + + b.HasIndex("UserCode") + .IsUnique(); + + b.ToTable("IdentityServerDeviceFlowCodes"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Grants.PersistedGrant", b => + { + b.Property("Key") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("Data") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasMaxLength(50000); + + b.Property("Expiration") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("SubjectId") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("Type") + .IsRequired() + .HasColumnType("nvarchar(50)") + .HasMaxLength(50); + + b.HasKey("Key"); + + b.HasIndex("Expiration"); + + b.HasIndex("SubjectId", "ClientId", "Type"); + + b.ToTable("IdentityServerPersistedGrants"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.IdentityResources.IdentityClaim", b => + { + b.Property("IdentityResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Type") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.HasKey("IdentityResourceId", "Type"); + + b.ToTable("IdentityServerIdentityClaims"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.IdentityResources.IdentityResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(1000)") + .HasMaxLength(1000); + + b.Property("DisplayName") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("Emphasize") + .HasColumnType("bit"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("Properties") + .HasColumnType("nvarchar(max)"); + + b.Property("Required") + .HasColumnType("bit"); + + b.Property("ShowInDiscoveryDocument") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.ToTable("IdentityServerIdentityResources"); + }); + + modelBuilder.Entity("Volo.Abp.PermissionManagement.PermissionGrant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderName") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("Name", "ProviderName", "ProviderKey"); + + b.ToTable("AbpPermissionGrants"); + }); + + modelBuilder.Entity("Volo.Abp.SettingManagement.Setting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(2048)") + .HasMaxLength(2048); + + b.HasKey("Id"); + + b.HasIndex("Name", "ProviderName", "ProviderKey"); + + b.ToTable("AbpSettings"); + }); + + modelBuilder.Entity("Volo.Abp.TenantManagement.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.ToTable("AbpTenants"); + }); + + modelBuilder.Entity("Volo.Abp.TenantManagement.TenantConnectionString", b => + { + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.HasKey("TenantId", "Name"); + + b.ToTable("AbpTenantConnectionStrings"); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLogAction", b => + { + b.HasOne("Volo.Abp.AuditLogging.AuditLog", null) + .WithMany("Actions") + .HasForeignKey("AuditLogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityChange", b => + { + b.HasOne("Volo.Abp.AuditLogging.AuditLog", null) + .WithMany("EntityChanges") + .HasForeignKey("AuditLogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityPropertyChange", b => + { + b.HasOne("Volo.Abp.AuditLogging.EntityChange", null) + .WithMany("PropertyChanges") + .HasForeignKey("EntityChangeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => + { + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany("Claims") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Claims") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Logins") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserOrganizationUnit", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany() + .HasForeignKey("OrganizationUnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("OrganizationUnits") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => + { + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Roles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Tokens") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany() + .HasForeignKey("ParentId"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnitRole", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany("Roles") + .HasForeignKey("OrganizationUnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.ApiResources.ApiResourceClaim", b => + { + b.HasOne("Volo.Abp.IdentityServer.ApiResources.ApiResource", null) + .WithMany("UserClaims") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.ApiResources.ApiScope", b => + { + b.HasOne("Volo.Abp.IdentityServer.ApiResources.ApiResource", null) + .WithMany("Scopes") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.ApiResources.ApiScopeClaim", b => + { + b.HasOne("Volo.Abp.IdentityServer.ApiResources.ApiScope", null) + .WithMany("UserClaims") + .HasForeignKey("ApiResourceId", "Name") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.ApiResources.ApiSecret", b => + { + b.HasOne("Volo.Abp.IdentityServer.ApiResources.ApiResource", null) + .WithMany("Secrets") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientClaim", b => + { + b.HasOne("Volo.Abp.IdentityServer.Clients.Client", null) + .WithMany("Claims") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientCorsOrigin", b => + { + b.HasOne("Volo.Abp.IdentityServer.Clients.Client", null) + .WithMany("AllowedCorsOrigins") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientGrantType", b => + { + b.HasOne("Volo.Abp.IdentityServer.Clients.Client", null) + .WithMany("AllowedGrantTypes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientIdPRestriction", b => + { + b.HasOne("Volo.Abp.IdentityServer.Clients.Client", null) + .WithMany("IdentityProviderRestrictions") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientPostLogoutRedirectUri", b => + { + b.HasOne("Volo.Abp.IdentityServer.Clients.Client", null) + .WithMany("PostLogoutRedirectUris") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientProperty", b => + { + b.HasOne("Volo.Abp.IdentityServer.Clients.Client", null) + .WithMany("Properties") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientRedirectUri", b => + { + b.HasOne("Volo.Abp.IdentityServer.Clients.Client", null) + .WithMany("RedirectUris") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientScope", b => + { + b.HasOne("Volo.Abp.IdentityServer.Clients.Client", null) + .WithMany("AllowedScopes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientSecret", b => + { + b.HasOne("Volo.Abp.IdentityServer.Clients.Client", null) + .WithMany("ClientSecrets") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.IdentityResources.IdentityClaim", b => + { + b.HasOne("Volo.Abp.IdentityServer.IdentityResources.IdentityResource", null) + .WithMany("UserClaims") + .HasForeignKey("IdentityResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.TenantManagement.TenantConnectionString", b => + { + b.HasOne("Volo.Abp.TenantManagement.Tenant", null) + .WithMany("ConnectionStrings") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Migrations/20200810022553_Initial.cs b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Migrations/20200810022553_Initial.cs new file mode 100644 index 0000000000..573c52149b --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Migrations/20200810022553_Initial.cs @@ -0,0 +1,1135 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Volo.CmsKit.Migrations +{ + public partial class Initial : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AbpAuditLogs", + columns: table => new + { + Id = table.Column(nullable: false), + ExtraProperties = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), + ApplicationName = table.Column(maxLength: 96, nullable: true), + UserId = table.Column(nullable: true), + UserName = table.Column(maxLength: 256, nullable: true), + TenantId = table.Column(nullable: true), + TenantName = table.Column(nullable: true), + ImpersonatorUserId = table.Column(nullable: true), + ImpersonatorTenantId = table.Column(nullable: true), + ExecutionTime = table.Column(nullable: false), + ExecutionDuration = table.Column(nullable: false), + ClientIpAddress = table.Column(maxLength: 64, nullable: true), + ClientName = table.Column(maxLength: 128, nullable: true), + ClientId = table.Column(maxLength: 64, nullable: true), + CorrelationId = table.Column(maxLength: 64, nullable: true), + BrowserInfo = table.Column(maxLength: 512, nullable: true), + HttpMethod = table.Column(maxLength: 16, nullable: true), + Url = table.Column(maxLength: 256, nullable: true), + Exceptions = table.Column(maxLength: 4000, nullable: true), + Comments = table.Column(maxLength: 256, nullable: true), + HttpStatusCode = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpAuditLogs", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpClaimTypes", + columns: table => new + { + Id = table.Column(nullable: false), + ExtraProperties = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), + Name = table.Column(maxLength: 256, nullable: false), + Required = table.Column(nullable: false), + IsStatic = table.Column(nullable: false), + Regex = table.Column(maxLength: 512, nullable: true), + RegexDescription = table.Column(maxLength: 128, nullable: true), + Description = table.Column(maxLength: 256, nullable: true), + ValueType = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpClaimTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpOrganizationUnits", + columns: table => new + { + Id = table.Column(nullable: false), + ExtraProperties = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), + CreationTime = table.Column(nullable: false), + CreatorId = table.Column(nullable: true), + LastModificationTime = table.Column(nullable: true), + LastModifierId = table.Column(nullable: true), + IsDeleted = table.Column(nullable: false, defaultValue: false), + DeleterId = table.Column(nullable: true), + DeletionTime = table.Column(nullable: true), + TenantId = table.Column(nullable: true), + ParentId = table.Column(nullable: true), + Code = table.Column(maxLength: 95, nullable: false), + DisplayName = table.Column(maxLength: 128, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpOrganizationUnits", x => x.Id); + table.ForeignKey( + name: "FK_AbpOrganizationUnits_AbpOrganizationUnits_ParentId", + column: x => x.ParentId, + principalTable: "AbpOrganizationUnits", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "AbpPermissionGrants", + columns: table => new + { + Id = table.Column(nullable: false), + TenantId = table.Column(nullable: true), + Name = table.Column(maxLength: 128, nullable: false), + ProviderName = table.Column(maxLength: 64, nullable: false), + ProviderKey = table.Column(maxLength: 64, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpPermissionGrants", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpRoles", + columns: table => new + { + Id = table.Column(nullable: false), + ExtraProperties = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), + TenantId = table.Column(nullable: true), + Name = table.Column(maxLength: 256, nullable: false), + NormalizedName = table.Column(maxLength: 256, nullable: false), + IsDefault = table.Column(nullable: false), + IsStatic = table.Column(nullable: false), + IsPublic = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpSettings", + columns: table => new + { + Id = table.Column(nullable: false), + Name = table.Column(maxLength: 128, nullable: false), + Value = table.Column(maxLength: 2048, nullable: false), + ProviderName = table.Column(maxLength: 64, nullable: true), + ProviderKey = table.Column(maxLength: 64, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpSettings", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpTenants", + columns: table => new + { + Id = table.Column(nullable: false), + ExtraProperties = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), + CreationTime = table.Column(nullable: false), + CreatorId = table.Column(nullable: true), + LastModificationTime = table.Column(nullable: true), + LastModifierId = table.Column(nullable: true), + IsDeleted = table.Column(nullable: false, defaultValue: false), + DeleterId = table.Column(nullable: true), + DeletionTime = table.Column(nullable: true), + Name = table.Column(maxLength: 64, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpTenants", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpUsers", + columns: table => new + { + Id = table.Column(nullable: false), + ExtraProperties = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), + CreationTime = table.Column(nullable: false), + CreatorId = table.Column(nullable: true), + LastModificationTime = table.Column(nullable: true), + LastModifierId = table.Column(nullable: true), + IsDeleted = table.Column(nullable: false, defaultValue: false), + DeleterId = table.Column(nullable: true), + DeletionTime = table.Column(nullable: true), + TenantId = table.Column(nullable: true), + UserName = table.Column(maxLength: 256, nullable: false), + NormalizedUserName = table.Column(maxLength: 256, nullable: false), + Name = table.Column(maxLength: 64, nullable: true), + Surname = table.Column(maxLength: 64, nullable: true), + Email = table.Column(maxLength: 256, nullable: false), + NormalizedEmail = table.Column(maxLength: 256, nullable: false), + EmailConfirmed = table.Column(nullable: false, defaultValue: false), + PasswordHash = table.Column(maxLength: 256, nullable: true), + SecurityStamp = table.Column(maxLength: 256, nullable: false), + PhoneNumber = table.Column(maxLength: 16, nullable: true), + PhoneNumberConfirmed = table.Column(nullable: false, defaultValue: false), + TwoFactorEnabled = table.Column(nullable: false, defaultValue: false), + LockoutEnd = table.Column(nullable: true), + LockoutEnabled = table.Column(nullable: false, defaultValue: false), + AccessFailedCount = table.Column(nullable: false, defaultValue: 0) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "IdentityServerApiResources", + columns: table => new + { + Id = table.Column(nullable: false), + ExtraProperties = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), + CreationTime = table.Column(nullable: false), + CreatorId = table.Column(nullable: true), + LastModificationTime = table.Column(nullable: true), + LastModifierId = table.Column(nullable: true), + IsDeleted = table.Column(nullable: false, defaultValue: false), + DeleterId = table.Column(nullable: true), + DeletionTime = table.Column(nullable: true), + Name = table.Column(maxLength: 200, nullable: false), + DisplayName = table.Column(maxLength: 200, nullable: true), + Description = table.Column(maxLength: 1000, nullable: true), + Enabled = table.Column(nullable: false), + Properties = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityServerApiResources", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "IdentityServerClients", + columns: table => new + { + Id = table.Column(nullable: false), + ExtraProperties = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), + CreationTime = table.Column(nullable: false), + CreatorId = table.Column(nullable: true), + LastModificationTime = table.Column(nullable: true), + LastModifierId = table.Column(nullable: true), + IsDeleted = table.Column(nullable: false, defaultValue: false), + DeleterId = table.Column(nullable: true), + DeletionTime = table.Column(nullable: true), + ClientId = table.Column(maxLength: 200, nullable: false), + ClientName = table.Column(maxLength: 200, nullable: true), + Description = table.Column(maxLength: 1000, nullable: true), + ClientUri = table.Column(maxLength: 2000, nullable: true), + LogoUri = table.Column(maxLength: 2000, nullable: true), + Enabled = table.Column(nullable: false), + ProtocolType = table.Column(maxLength: 200, nullable: false), + RequireClientSecret = table.Column(nullable: false), + RequireConsent = table.Column(nullable: false), + AllowRememberConsent = table.Column(nullable: false), + AlwaysIncludeUserClaimsInIdToken = table.Column(nullable: false), + RequirePkce = table.Column(nullable: false), + AllowPlainTextPkce = table.Column(nullable: false), + AllowAccessTokensViaBrowser = table.Column(nullable: false), + FrontChannelLogoutUri = table.Column(maxLength: 2000, nullable: true), + FrontChannelLogoutSessionRequired = table.Column(nullable: false), + BackChannelLogoutUri = table.Column(maxLength: 2000, nullable: true), + BackChannelLogoutSessionRequired = table.Column(nullable: false), + AllowOfflineAccess = table.Column(nullable: false), + IdentityTokenLifetime = table.Column(nullable: false), + AccessTokenLifetime = table.Column(nullable: false), + AuthorizationCodeLifetime = table.Column(nullable: false), + ConsentLifetime = table.Column(nullable: true), + AbsoluteRefreshTokenLifetime = table.Column(nullable: false), + SlidingRefreshTokenLifetime = table.Column(nullable: false), + RefreshTokenUsage = table.Column(nullable: false), + UpdateAccessTokenClaimsOnRefresh = table.Column(nullable: false), + RefreshTokenExpiration = table.Column(nullable: false), + AccessTokenType = table.Column(nullable: false), + EnableLocalLogin = table.Column(nullable: false), + IncludeJwtId = table.Column(nullable: false), + AlwaysSendClientClaims = table.Column(nullable: false), + ClientClaimsPrefix = table.Column(maxLength: 200, nullable: true), + PairWiseSubjectSalt = table.Column(maxLength: 200, nullable: true), + UserSsoLifetime = table.Column(nullable: true), + UserCodeType = table.Column(maxLength: 100, nullable: true), + DeviceCodeLifetime = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityServerClients", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "IdentityServerDeviceFlowCodes", + columns: table => new + { + Id = table.Column(nullable: false), + ExtraProperties = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), + CreationTime = table.Column(nullable: false), + CreatorId = table.Column(nullable: true), + DeviceCode = table.Column(maxLength: 200, nullable: false), + UserCode = table.Column(maxLength: 200, nullable: false), + SubjectId = table.Column(maxLength: 200, nullable: true), + ClientId = table.Column(maxLength: 200, nullable: false), + Expiration = table.Column(nullable: false), + Data = table.Column(maxLength: 50000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityServerDeviceFlowCodes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "IdentityServerIdentityResources", + columns: table => new + { + Id = table.Column(nullable: false), + ExtraProperties = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), + CreationTime = table.Column(nullable: false), + CreatorId = table.Column(nullable: true), + LastModificationTime = table.Column(nullable: true), + LastModifierId = table.Column(nullable: true), + IsDeleted = table.Column(nullable: false, defaultValue: false), + DeleterId = table.Column(nullable: true), + DeletionTime = table.Column(nullable: true), + Name = table.Column(maxLength: 200, nullable: false), + DisplayName = table.Column(maxLength: 200, nullable: true), + Description = table.Column(maxLength: 1000, nullable: true), + Enabled = table.Column(nullable: false), + Required = table.Column(nullable: false), + Emphasize = table.Column(nullable: false), + ShowInDiscoveryDocument = table.Column(nullable: false), + Properties = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityServerIdentityResources", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "IdentityServerPersistedGrants", + columns: table => new + { + Key = table.Column(maxLength: 200, nullable: false), + Id = table.Column(nullable: false), + ExtraProperties = table.Column(nullable: true), + ConcurrencyStamp = table.Column(maxLength: 40, nullable: true), + Type = table.Column(maxLength: 50, nullable: false), + SubjectId = table.Column(maxLength: 200, nullable: true), + ClientId = table.Column(maxLength: 200, nullable: false), + CreationTime = table.Column(nullable: false), + Expiration = table.Column(nullable: true), + Data = table.Column(maxLength: 50000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityServerPersistedGrants", x => x.Key); + }); + + migrationBuilder.CreateTable( + name: "AbpAuditLogActions", + columns: table => new + { + Id = table.Column(nullable: false), + TenantId = table.Column(nullable: true), + AuditLogId = table.Column(nullable: false), + ServiceName = table.Column(maxLength: 256, nullable: true), + MethodName = table.Column(maxLength: 128, nullable: true), + Parameters = table.Column(maxLength: 2000, nullable: true), + ExecutionTime = table.Column(nullable: false), + ExecutionDuration = table.Column(nullable: false), + ExtraProperties = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpAuditLogActions", x => x.Id); + table.ForeignKey( + name: "FK_AbpAuditLogActions_AbpAuditLogs_AuditLogId", + column: x => x.AuditLogId, + principalTable: "AbpAuditLogs", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpEntityChanges", + columns: table => new + { + Id = table.Column(nullable: false), + AuditLogId = table.Column(nullable: false), + TenantId = table.Column(nullable: true), + ChangeTime = table.Column(nullable: false), + ChangeType = table.Column(nullable: false), + EntityTenantId = table.Column(nullable: true), + EntityId = table.Column(maxLength: 128, nullable: false), + EntityTypeFullName = table.Column(maxLength: 128, nullable: false), + ExtraProperties = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpEntityChanges", x => x.Id); + table.ForeignKey( + name: "FK_AbpEntityChanges_AbpAuditLogs_AuditLogId", + column: x => x.AuditLogId, + principalTable: "AbpAuditLogs", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpOrganizationUnitRoles", + columns: table => new + { + RoleId = table.Column(nullable: false), + OrganizationUnitId = table.Column(nullable: false), + CreationTime = table.Column(nullable: false), + CreatorId = table.Column(nullable: true), + TenantId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpOrganizationUnitRoles", x => new { x.OrganizationUnitId, x.RoleId }); + table.ForeignKey( + name: "FK_AbpOrganizationUnitRoles_AbpOrganizationUnits_OrganizationUnitId", + column: x => x.OrganizationUnitId, + principalTable: "AbpOrganizationUnits", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AbpOrganizationUnitRoles_AbpRoles_RoleId", + column: x => x.RoleId, + principalTable: "AbpRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpRoleClaims", + columns: table => new + { + Id = table.Column(nullable: false), + TenantId = table.Column(nullable: true), + ClaimType = table.Column(maxLength: 256, nullable: false), + ClaimValue = table.Column(maxLength: 1024, nullable: true), + RoleId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AbpRoleClaims_AbpRoles_RoleId", + column: x => x.RoleId, + principalTable: "AbpRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpTenantConnectionStrings", + columns: table => new + { + TenantId = table.Column(nullable: false), + Name = table.Column(maxLength: 64, nullable: false), + Value = table.Column(maxLength: 1024, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpTenantConnectionStrings", x => new { x.TenantId, x.Name }); + table.ForeignKey( + name: "FK_AbpTenantConnectionStrings_AbpTenants_TenantId", + column: x => x.TenantId, + principalTable: "AbpTenants", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpUserClaims", + columns: table => new + { + Id = table.Column(nullable: false), + TenantId = table.Column(nullable: true), + ClaimType = table.Column(maxLength: 256, nullable: false), + ClaimValue = table.Column(maxLength: 1024, nullable: true), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AbpUserClaims_AbpUsers_UserId", + column: x => x.UserId, + principalTable: "AbpUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpUserLogins", + columns: table => new + { + UserId = table.Column(nullable: false), + LoginProvider = table.Column(maxLength: 64, nullable: false), + TenantId = table.Column(nullable: true), + ProviderKey = table.Column(maxLength: 196, nullable: false), + ProviderDisplayName = table.Column(maxLength: 128, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUserLogins", x => new { x.UserId, x.LoginProvider }); + table.ForeignKey( + name: "FK_AbpUserLogins_AbpUsers_UserId", + column: x => x.UserId, + principalTable: "AbpUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpUserOrganizationUnits", + columns: table => new + { + UserId = table.Column(nullable: false), + OrganizationUnitId = table.Column(nullable: false), + CreationTime = table.Column(nullable: false), + CreatorId = table.Column(nullable: true), + TenantId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUserOrganizationUnits", x => new { x.OrganizationUnitId, x.UserId }); + table.ForeignKey( + name: "FK_AbpUserOrganizationUnits_AbpOrganizationUnits_OrganizationUnitId", + column: x => x.OrganizationUnitId, + principalTable: "AbpOrganizationUnits", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AbpUserOrganizationUnits_AbpUsers_UserId", + column: x => x.UserId, + principalTable: "AbpUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpUserRoles", + columns: table => new + { + UserId = table.Column(nullable: false), + RoleId = table.Column(nullable: false), + TenantId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AbpUserRoles_AbpRoles_RoleId", + column: x => x.RoleId, + principalTable: "AbpRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AbpUserRoles_AbpUsers_UserId", + column: x => x.UserId, + principalTable: "AbpUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpUserTokens", + columns: table => new + { + UserId = table.Column(nullable: false), + LoginProvider = table.Column(maxLength: 64, nullable: false), + Name = table.Column(maxLength: 128, nullable: false), + TenantId = table.Column(nullable: true), + Value = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AbpUserTokens_AbpUsers_UserId", + column: x => x.UserId, + principalTable: "AbpUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IdentityServerApiClaims", + columns: table => new + { + Type = table.Column(maxLength: 200, nullable: false), + ApiResourceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityServerApiClaims", x => new { x.ApiResourceId, x.Type }); + table.ForeignKey( + name: "FK_IdentityServerApiClaims_IdentityServerApiResources_ApiResourceId", + column: x => x.ApiResourceId, + principalTable: "IdentityServerApiResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IdentityServerApiScopes", + columns: table => new + { + ApiResourceId = table.Column(nullable: false), + Name = table.Column(maxLength: 200, nullable: false), + DisplayName = table.Column(maxLength: 200, nullable: true), + Description = table.Column(maxLength: 1000, nullable: true), + Required = table.Column(nullable: false), + Emphasize = table.Column(nullable: false), + ShowInDiscoveryDocument = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityServerApiScopes", x => new { x.ApiResourceId, x.Name }); + table.ForeignKey( + name: "FK_IdentityServerApiScopes_IdentityServerApiResources_ApiResourceId", + column: x => x.ApiResourceId, + principalTable: "IdentityServerApiResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IdentityServerApiSecrets", + columns: table => new + { + Type = table.Column(maxLength: 250, nullable: false), + Value = table.Column(maxLength: 4000, nullable: false), + ApiResourceId = table.Column(nullable: false), + Description = table.Column(maxLength: 2000, nullable: true), + Expiration = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityServerApiSecrets", x => new { x.ApiResourceId, x.Type, x.Value }); + table.ForeignKey( + name: "FK_IdentityServerApiSecrets_IdentityServerApiResources_ApiResourceId", + column: x => x.ApiResourceId, + principalTable: "IdentityServerApiResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IdentityServerClientClaims", + columns: table => new + { + ClientId = table.Column(nullable: false), + Type = table.Column(maxLength: 250, nullable: false), + Value = table.Column(maxLength: 250, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityServerClientClaims", x => new { x.ClientId, x.Type, x.Value }); + table.ForeignKey( + name: "FK_IdentityServerClientClaims_IdentityServerClients_ClientId", + column: x => x.ClientId, + principalTable: "IdentityServerClients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IdentityServerClientCorsOrigins", + columns: table => new + { + ClientId = table.Column(nullable: false), + Origin = table.Column(maxLength: 150, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityServerClientCorsOrigins", x => new { x.ClientId, x.Origin }); + table.ForeignKey( + name: "FK_IdentityServerClientCorsOrigins_IdentityServerClients_ClientId", + column: x => x.ClientId, + principalTable: "IdentityServerClients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IdentityServerClientGrantTypes", + columns: table => new + { + ClientId = table.Column(nullable: false), + GrantType = table.Column(maxLength: 250, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityServerClientGrantTypes", x => new { x.ClientId, x.GrantType }); + table.ForeignKey( + name: "FK_IdentityServerClientGrantTypes_IdentityServerClients_ClientId", + column: x => x.ClientId, + principalTable: "IdentityServerClients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IdentityServerClientIdPRestrictions", + columns: table => new + { + ClientId = table.Column(nullable: false), + Provider = table.Column(maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityServerClientIdPRestrictions", x => new { x.ClientId, x.Provider }); + table.ForeignKey( + name: "FK_IdentityServerClientIdPRestrictions_IdentityServerClients_ClientId", + column: x => x.ClientId, + principalTable: "IdentityServerClients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IdentityServerClientPostLogoutRedirectUris", + columns: table => new + { + ClientId = table.Column(nullable: false), + PostLogoutRedirectUri = table.Column(maxLength: 2000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityServerClientPostLogoutRedirectUris", x => new { x.ClientId, x.PostLogoutRedirectUri }); + table.ForeignKey( + name: "FK_IdentityServerClientPostLogoutRedirectUris_IdentityServerClients_ClientId", + column: x => x.ClientId, + principalTable: "IdentityServerClients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IdentityServerClientProperties", + columns: table => new + { + ClientId = table.Column(nullable: false), + Key = table.Column(maxLength: 250, nullable: false), + Value = table.Column(maxLength: 2000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityServerClientProperties", x => new { x.ClientId, x.Key }); + table.ForeignKey( + name: "FK_IdentityServerClientProperties_IdentityServerClients_ClientId", + column: x => x.ClientId, + principalTable: "IdentityServerClients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IdentityServerClientRedirectUris", + columns: table => new + { + ClientId = table.Column(nullable: false), + RedirectUri = table.Column(maxLength: 2000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityServerClientRedirectUris", x => new { x.ClientId, x.RedirectUri }); + table.ForeignKey( + name: "FK_IdentityServerClientRedirectUris_IdentityServerClients_ClientId", + column: x => x.ClientId, + principalTable: "IdentityServerClients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IdentityServerClientScopes", + columns: table => new + { + ClientId = table.Column(nullable: false), + Scope = table.Column(maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityServerClientScopes", x => new { x.ClientId, x.Scope }); + table.ForeignKey( + name: "FK_IdentityServerClientScopes_IdentityServerClients_ClientId", + column: x => x.ClientId, + principalTable: "IdentityServerClients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IdentityServerClientSecrets", + columns: table => new + { + Type = table.Column(maxLength: 250, nullable: false), + Value = table.Column(maxLength: 4000, nullable: false), + ClientId = table.Column(nullable: false), + Description = table.Column(maxLength: 2000, nullable: true), + Expiration = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityServerClientSecrets", x => new { x.ClientId, x.Type, x.Value }); + table.ForeignKey( + name: "FK_IdentityServerClientSecrets_IdentityServerClients_ClientId", + column: x => x.ClientId, + principalTable: "IdentityServerClients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IdentityServerIdentityClaims", + columns: table => new + { + Type = table.Column(maxLength: 200, nullable: false), + IdentityResourceId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityServerIdentityClaims", x => new { x.IdentityResourceId, x.Type }); + table.ForeignKey( + name: "FK_IdentityServerIdentityClaims_IdentityServerIdentityResources_IdentityResourceId", + column: x => x.IdentityResourceId, + principalTable: "IdentityServerIdentityResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AbpEntityPropertyChanges", + columns: table => new + { + Id = table.Column(nullable: false), + TenantId = table.Column(nullable: true), + EntityChangeId = table.Column(nullable: false), + NewValue = table.Column(maxLength: 512, nullable: true), + OriginalValue = table.Column(maxLength: 512, nullable: true), + PropertyName = table.Column(maxLength: 128, nullable: false), + PropertyTypeFullName = table.Column(maxLength: 64, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpEntityPropertyChanges", x => x.Id); + table.ForeignKey( + name: "FK_AbpEntityPropertyChanges_AbpEntityChanges_EntityChangeId", + column: x => x.EntityChangeId, + principalTable: "AbpEntityChanges", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IdentityServerApiScopeClaims", + columns: table => new + { + Type = table.Column(maxLength: 200, nullable: false), + ApiResourceId = table.Column(nullable: false), + Name = table.Column(maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityServerApiScopeClaims", x => new { x.ApiResourceId, x.Name, x.Type }); + table.ForeignKey( + name: "FK_IdentityServerApiScopeClaims_IdentityServerApiScopes_ApiResourceId_Name", + columns: x => new { x.ApiResourceId, x.Name }, + principalTable: "IdentityServerApiScopes", + principalColumns: new[] { "ApiResourceId", "Name" }, + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AbpAuditLogActions_AuditLogId", + table: "AbpAuditLogActions", + column: "AuditLogId"); + + migrationBuilder.CreateIndex( + name: "IX_AbpAuditLogActions_TenantId_ServiceName_MethodName_ExecutionTime", + table: "AbpAuditLogActions", + columns: new[] { "TenantId", "ServiceName", "MethodName", "ExecutionTime" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpAuditLogs_TenantId_ExecutionTime", + table: "AbpAuditLogs", + columns: new[] { "TenantId", "ExecutionTime" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpAuditLogs_TenantId_UserId_ExecutionTime", + table: "AbpAuditLogs", + columns: new[] { "TenantId", "UserId", "ExecutionTime" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpEntityChanges_AuditLogId", + table: "AbpEntityChanges", + column: "AuditLogId"); + + migrationBuilder.CreateIndex( + name: "IX_AbpEntityChanges_TenantId_EntityTypeFullName_EntityId", + table: "AbpEntityChanges", + columns: new[] { "TenantId", "EntityTypeFullName", "EntityId" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpEntityPropertyChanges_EntityChangeId", + table: "AbpEntityPropertyChanges", + column: "EntityChangeId"); + + migrationBuilder.CreateIndex( + name: "IX_AbpOrganizationUnitRoles_RoleId_OrganizationUnitId", + table: "AbpOrganizationUnitRoles", + columns: new[] { "RoleId", "OrganizationUnitId" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpOrganizationUnits_Code", + table: "AbpOrganizationUnits", + column: "Code"); + + migrationBuilder.CreateIndex( + name: "IX_AbpOrganizationUnits_ParentId", + table: "AbpOrganizationUnits", + column: "ParentId"); + + migrationBuilder.CreateIndex( + name: "IX_AbpPermissionGrants_Name_ProviderName_ProviderKey", + table: "AbpPermissionGrants", + columns: new[] { "Name", "ProviderName", "ProviderKey" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpRoleClaims_RoleId", + table: "AbpRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "IX_AbpRoles_NormalizedName", + table: "AbpRoles", + column: "NormalizedName"); + + migrationBuilder.CreateIndex( + name: "IX_AbpSettings_Name_ProviderName_ProviderKey", + table: "AbpSettings", + columns: new[] { "Name", "ProviderName", "ProviderKey" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpTenants_Name", + table: "AbpTenants", + column: "Name"); + + migrationBuilder.CreateIndex( + name: "IX_AbpUserClaims_UserId", + table: "AbpUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AbpUserLogins_LoginProvider_ProviderKey", + table: "AbpUserLogins", + columns: new[] { "LoginProvider", "ProviderKey" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpUserOrganizationUnits_UserId_OrganizationUnitId", + table: "AbpUserOrganizationUnits", + columns: new[] { "UserId", "OrganizationUnitId" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpUserRoles_RoleId_UserId", + table: "AbpUserRoles", + columns: new[] { "RoleId", "UserId" }); + + migrationBuilder.CreateIndex( + name: "IX_AbpUsers_Email", + table: "AbpUsers", + column: "Email"); + + migrationBuilder.CreateIndex( + name: "IX_AbpUsers_NormalizedEmail", + table: "AbpUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "IX_AbpUsers_NormalizedUserName", + table: "AbpUsers", + column: "NormalizedUserName"); + + migrationBuilder.CreateIndex( + name: "IX_AbpUsers_UserName", + table: "AbpUsers", + column: "UserName"); + + migrationBuilder.CreateIndex( + name: "IX_IdentityServerClients_ClientId", + table: "IdentityServerClients", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_IdentityServerDeviceFlowCodes_DeviceCode", + table: "IdentityServerDeviceFlowCodes", + column: "DeviceCode", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_IdentityServerDeviceFlowCodes_Expiration", + table: "IdentityServerDeviceFlowCodes", + column: "Expiration"); + + migrationBuilder.CreateIndex( + name: "IX_IdentityServerDeviceFlowCodes_UserCode", + table: "IdentityServerDeviceFlowCodes", + column: "UserCode", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_IdentityServerPersistedGrants_Expiration", + table: "IdentityServerPersistedGrants", + column: "Expiration"); + + migrationBuilder.CreateIndex( + name: "IX_IdentityServerPersistedGrants_SubjectId_ClientId_Type", + table: "IdentityServerPersistedGrants", + columns: new[] { "SubjectId", "ClientId", "Type" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AbpAuditLogActions"); + + migrationBuilder.DropTable( + name: "AbpClaimTypes"); + + migrationBuilder.DropTable( + name: "AbpEntityPropertyChanges"); + + migrationBuilder.DropTable( + name: "AbpOrganizationUnitRoles"); + + migrationBuilder.DropTable( + name: "AbpPermissionGrants"); + + migrationBuilder.DropTable( + name: "AbpRoleClaims"); + + migrationBuilder.DropTable( + name: "AbpSettings"); + + migrationBuilder.DropTable( + name: "AbpTenantConnectionStrings"); + + migrationBuilder.DropTable( + name: "AbpUserClaims"); + + migrationBuilder.DropTable( + name: "AbpUserLogins"); + + migrationBuilder.DropTable( + name: "AbpUserOrganizationUnits"); + + migrationBuilder.DropTable( + name: "AbpUserRoles"); + + migrationBuilder.DropTable( + name: "AbpUserTokens"); + + migrationBuilder.DropTable( + name: "IdentityServerApiClaims"); + + migrationBuilder.DropTable( + name: "IdentityServerApiScopeClaims"); + + migrationBuilder.DropTable( + name: "IdentityServerApiSecrets"); + + migrationBuilder.DropTable( + name: "IdentityServerClientClaims"); + + migrationBuilder.DropTable( + name: "IdentityServerClientCorsOrigins"); + + migrationBuilder.DropTable( + name: "IdentityServerClientGrantTypes"); + + migrationBuilder.DropTable( + name: "IdentityServerClientIdPRestrictions"); + + migrationBuilder.DropTable( + name: "IdentityServerClientPostLogoutRedirectUris"); + + migrationBuilder.DropTable( + name: "IdentityServerClientProperties"); + + migrationBuilder.DropTable( + name: "IdentityServerClientRedirectUris"); + + migrationBuilder.DropTable( + name: "IdentityServerClientScopes"); + + migrationBuilder.DropTable( + name: "IdentityServerClientSecrets"); + + migrationBuilder.DropTable( + name: "IdentityServerDeviceFlowCodes"); + + migrationBuilder.DropTable( + name: "IdentityServerIdentityClaims"); + + migrationBuilder.DropTable( + name: "IdentityServerPersistedGrants"); + + migrationBuilder.DropTable( + name: "AbpEntityChanges"); + + migrationBuilder.DropTable( + name: "AbpTenants"); + + migrationBuilder.DropTable( + name: "AbpOrganizationUnits"); + + migrationBuilder.DropTable( + name: "AbpRoles"); + + migrationBuilder.DropTable( + name: "AbpUsers"); + + migrationBuilder.DropTable( + name: "IdentityServerApiScopes"); + + migrationBuilder.DropTable( + name: "IdentityServerClients"); + + migrationBuilder.DropTable( + name: "IdentityServerIdentityResources"); + + migrationBuilder.DropTable( + name: "AbpAuditLogs"); + + migrationBuilder.DropTable( + name: "IdentityServerApiResources"); + } + } +} diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Migrations/IdentityServerHostMigrationsDbContextModelSnapshot.cs b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Migrations/IdentityServerHostMigrationsDbContextModelSnapshot.cs new file mode 100644 index 0000000000..b4249161b0 --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Migrations/IdentityServerHostMigrationsDbContextModelSnapshot.cs @@ -0,0 +1,1859 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Volo.Abp.EntityFrameworkCore; +using Volo.CmsKit.EntityFrameworkCore; + +namespace Volo.CmsKit.Migrations +{ + [DbContext(typeof(IdentityServerHostMigrationsDbContext))] + partial class IdentityServerHostMigrationsDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) + .HasAnnotation("ProductVersion", "3.1.6") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ApplicationName") + .HasColumnName("ApplicationName") + .HasColumnType("nvarchar(96)") + .HasMaxLength(96); + + b.Property("BrowserInfo") + .HasColumnName("BrowserInfo") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("ClientId") + .HasColumnName("ClientId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ClientIpAddress") + .HasColumnName("ClientIpAddress") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ClientName") + .HasColumnName("ClientName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Comments") + .HasColumnName("Comments") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CorrelationId") + .HasColumnName("CorrelationId") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Exceptions") + .HasColumnName("Exceptions") + .HasColumnType("nvarchar(4000)") + .HasMaxLength(4000); + + b.Property("ExecutionDuration") + .HasColumnName("ExecutionDuration") + .HasColumnType("int"); + + b.Property("ExecutionTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("HttpMethod") + .HasColumnName("HttpMethod") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("HttpStatusCode") + .HasColumnName("HttpStatusCode") + .HasColumnType("int"); + + b.Property("ImpersonatorTenantId") + .HasColumnName("ImpersonatorTenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("ImpersonatorUserId") + .HasColumnName("ImpersonatorUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantName") + .HasColumnType("nvarchar(max)"); + + b.Property("Url") + .HasColumnName("Url") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("UserId") + .HasColumnName("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .HasColumnName("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("TenantId", "ExecutionTime"); + + b.HasIndex("TenantId", "UserId", "ExecutionTime"); + + b.ToTable("AbpAuditLogs"); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLogAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuditLogId") + .HasColumnName("AuditLogId") + .HasColumnType("uniqueidentifier"); + + b.Property("ExecutionDuration") + .HasColumnName("ExecutionDuration") + .HasColumnType("int"); + + b.Property("ExecutionTime") + .HasColumnName("ExecutionTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("MethodName") + .HasColumnName("MethodName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Parameters") + .HasColumnName("Parameters") + .HasColumnType("nvarchar(2000)") + .HasMaxLength(2000); + + b.Property("ServiceName") + .HasColumnName("ServiceName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AuditLogId"); + + b.HasIndex("TenantId", "ServiceName", "MethodName", "ExecutionTime"); + + b.ToTable("AbpAuditLogActions"); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityChange", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuditLogId") + .HasColumnName("AuditLogId") + .HasColumnType("uniqueidentifier"); + + b.Property("ChangeTime") + .HasColumnName("ChangeTime") + .HasColumnType("datetime2"); + + b.Property("ChangeType") + .HasColumnName("ChangeType") + .HasColumnType("tinyint"); + + b.Property("EntityId") + .IsRequired() + .HasColumnName("EntityId") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("EntityTenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("EntityTypeFullName") + .IsRequired() + .HasColumnName("EntityTypeFullName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AuditLogId"); + + b.HasIndex("TenantId", "EntityTypeFullName", "EntityId"); + + b.ToTable("AbpEntityChanges"); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityPropertyChange", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EntityChangeId") + .HasColumnType("uniqueidentifier"); + + b.Property("NewValue") + .HasColumnName("NewValue") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("OriginalValue") + .HasColumnName("OriginalValue") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("PropertyName") + .IsRequired() + .HasColumnName("PropertyName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("PropertyTypeFullName") + .IsRequired() + .HasColumnName("PropertyTypeFullName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("EntityChangeId"); + + b.ToTable("AbpEntityPropertyChanges"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityClaimType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("Description") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsStatic") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("Regex") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("RegexDescription") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Required") + .HasColumnType("bit"); + + b.Property("ValueType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("AbpClaimTypes"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDefault") + .HasColumnName("IsDefault") + .HasColumnType("bit"); + + b.Property("IsPublic") + .HasColumnName("IsPublic") + .HasColumnType("bit"); + + b.Property("IsStatic") + .HasColumnName("IsStatic") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedName") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName"); + + b.ToTable("AbpRoles"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("ClaimType") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AbpRoleClaims"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccessFailedCount") + .ValueGeneratedOnAdd() + .HasColumnName("AccessFailedCount") + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasColumnName("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("EmailConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("LockoutEnabled") + .ValueGeneratedOnAdd() + .HasColumnName("LockoutEnabled") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("Name") + .HasColumnName("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("NormalizedEmail") + .IsRequired() + .HasColumnName("NormalizedEmail") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .IsRequired() + .HasColumnName("NormalizedUserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("PasswordHash") + .HasColumnName("PasswordHash") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("PhoneNumber") + .HasColumnName("PhoneNumber") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("PhoneNumberConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("PhoneNumberConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("SecurityStamp") + .IsRequired() + .HasColumnName("SecurityStamp") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("Surname") + .HasColumnName("Surname") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TwoFactorEnabled") + .ValueGeneratedOnAdd() + .HasColumnName("TwoFactorEnabled") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("UserName") + .IsRequired() + .HasColumnName("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("Email"); + + b.HasIndex("NormalizedEmail"); + + b.HasIndex("NormalizedUserName"); + + b.HasIndex("UserName"); + + b.ToTable("AbpUsers"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("ClaimType") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AbpUserClaims"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .IsRequired() + .HasColumnType("nvarchar(196)") + .HasMaxLength(196); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "LoginProvider"); + + b.HasIndex("LoginProvider", "ProviderKey"); + + b.ToTable("AbpUserLogins"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserOrganizationUnit", b => + { + b.Property("OrganizationUnitId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("OrganizationUnitId", "UserId"); + + b.HasIndex("UserId", "OrganizationUnitId"); + + b.ToTable("AbpUserOrganizationUnits"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId", "UserId"); + + b.ToTable("AbpUserRoles"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Name") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AbpUserTokens"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Code") + .IsRequired() + .HasColumnName("Code") + .HasColumnType("nvarchar(95)") + .HasMaxLength(95); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnName("DisplayName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("Code"); + + b.HasIndex("ParentId"); + + b.ToTable("AbpOrganizationUnits"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnitRole", b => + { + b.Property("OrganizationUnitId") + .HasColumnType("uniqueidentifier"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("OrganizationUnitId", "RoleId"); + + b.HasIndex("RoleId", "OrganizationUnitId"); + + b.ToTable("AbpOrganizationUnitRoles"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.ApiResources.ApiResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(1000)") + .HasMaxLength(1000); + + b.Property("DisplayName") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("Properties") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("IdentityServerApiResources"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.ApiResources.ApiResourceClaim", b => + { + b.Property("ApiResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Type") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.HasKey("ApiResourceId", "Type"); + + b.ToTable("IdentityServerApiClaims"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.ApiResources.ApiScope", b => + { + b.Property("ApiResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("Description") + .HasColumnType("nvarchar(1000)") + .HasMaxLength(1000); + + b.Property("DisplayName") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("Emphasize") + .HasColumnType("bit"); + + b.Property("Required") + .HasColumnType("bit"); + + b.Property("ShowInDiscoveryDocument") + .HasColumnType("bit"); + + b.HasKey("ApiResourceId", "Name"); + + b.ToTable("IdentityServerApiScopes"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.ApiResources.ApiScopeClaim", b => + { + b.Property("ApiResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("Type") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.HasKey("ApiResourceId", "Name", "Type"); + + b.ToTable("IdentityServerApiScopeClaims"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.ApiResources.ApiSecret", b => + { + b.Property("ApiResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Type") + .HasColumnType("nvarchar(250)") + .HasMaxLength(250); + + b.Property("Value") + .HasColumnType("nvarchar(4000)") + .HasMaxLength(4000); + + b.Property("Description") + .HasColumnType("nvarchar(2000)") + .HasMaxLength(2000); + + b.Property("Expiration") + .HasColumnType("datetime2"); + + b.HasKey("ApiResourceId", "Type", "Value"); + + b.ToTable("IdentityServerApiSecrets"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.Client", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AbsoluteRefreshTokenLifetime") + .HasColumnType("int"); + + b.Property("AccessTokenLifetime") + .HasColumnType("int"); + + b.Property("AccessTokenType") + .HasColumnType("int"); + + b.Property("AllowAccessTokensViaBrowser") + .HasColumnType("bit"); + + b.Property("AllowOfflineAccess") + .HasColumnType("bit"); + + b.Property("AllowPlainTextPkce") + .HasColumnType("bit"); + + b.Property("AllowRememberConsent") + .HasColumnType("bit"); + + b.Property("AlwaysIncludeUserClaimsInIdToken") + .HasColumnType("bit"); + + b.Property("AlwaysSendClientClaims") + .HasColumnType("bit"); + + b.Property("AuthorizationCodeLifetime") + .HasColumnType("int"); + + b.Property("BackChannelLogoutSessionRequired") + .HasColumnType("bit"); + + b.Property("BackChannelLogoutUri") + .HasColumnType("nvarchar(2000)") + .HasMaxLength(2000); + + b.Property("ClientClaimsPrefix") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("ClientName") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("ClientUri") + .HasColumnType("nvarchar(2000)") + .HasMaxLength(2000); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("ConsentLifetime") + .HasColumnType("int"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(1000)") + .HasMaxLength(1000); + + b.Property("DeviceCodeLifetime") + .HasColumnType("int"); + + b.Property("EnableLocalLogin") + .HasColumnType("bit"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("FrontChannelLogoutSessionRequired") + .HasColumnType("bit"); + + b.Property("FrontChannelLogoutUri") + .HasColumnType("nvarchar(2000)") + .HasMaxLength(2000); + + b.Property("IdentityTokenLifetime") + .HasColumnType("int"); + + b.Property("IncludeJwtId") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("LogoUri") + .HasColumnType("nvarchar(2000)") + .HasMaxLength(2000); + + b.Property("PairWiseSubjectSalt") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("ProtocolType") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("RefreshTokenExpiration") + .HasColumnType("int"); + + b.Property("RefreshTokenUsage") + .HasColumnType("int"); + + b.Property("RequireClientSecret") + .HasColumnType("bit"); + + b.Property("RequireConsent") + .HasColumnType("bit"); + + b.Property("RequirePkce") + .HasColumnType("bit"); + + b.Property("SlidingRefreshTokenLifetime") + .HasColumnType("int"); + + b.Property("UpdateAccessTokenClaimsOnRefresh") + .HasColumnType("bit"); + + b.Property("UserCodeType") + .HasColumnType("nvarchar(100)") + .HasMaxLength(100); + + b.Property("UserSsoLifetime") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("IdentityServerClients"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientClaim", b => + { + b.Property("ClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("Type") + .HasColumnType("nvarchar(250)") + .HasMaxLength(250); + + b.Property("Value") + .HasColumnType("nvarchar(250)") + .HasMaxLength(250); + + b.HasKey("ClientId", "Type", "Value"); + + b.ToTable("IdentityServerClientClaims"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientCorsOrigin", b => + { + b.Property("ClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("Origin") + .HasColumnType("nvarchar(150)") + .HasMaxLength(150); + + b.HasKey("ClientId", "Origin"); + + b.ToTable("IdentityServerClientCorsOrigins"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientGrantType", b => + { + b.Property("ClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("GrantType") + .HasColumnType("nvarchar(250)") + .HasMaxLength(250); + + b.HasKey("ClientId", "GrantType"); + + b.ToTable("IdentityServerClientGrantTypes"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientIdPRestriction", b => + { + b.Property("ClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("Provider") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.HasKey("ClientId", "Provider"); + + b.ToTable("IdentityServerClientIdPRestrictions"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientPostLogoutRedirectUri", b => + { + b.Property("ClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("PostLogoutRedirectUri") + .HasColumnType("nvarchar(2000)") + .HasMaxLength(2000); + + b.HasKey("ClientId", "PostLogoutRedirectUri"); + + b.ToTable("IdentityServerClientPostLogoutRedirectUris"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientProperty", b => + { + b.Property("ClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("Key") + .HasColumnType("nvarchar(250)") + .HasMaxLength(250); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(2000)") + .HasMaxLength(2000); + + b.HasKey("ClientId", "Key"); + + b.ToTable("IdentityServerClientProperties"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientRedirectUri", b => + { + b.Property("ClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("RedirectUri") + .HasColumnType("nvarchar(2000)") + .HasMaxLength(2000); + + b.HasKey("ClientId", "RedirectUri"); + + b.ToTable("IdentityServerClientRedirectUris"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientScope", b => + { + b.Property("ClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("Scope") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.HasKey("ClientId", "Scope"); + + b.ToTable("IdentityServerClientScopes"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientSecret", b => + { + b.Property("ClientId") + .HasColumnType("uniqueidentifier"); + + b.Property("Type") + .HasColumnType("nvarchar(250)") + .HasMaxLength(250); + + b.Property("Value") + .HasColumnType("nvarchar(4000)") + .HasMaxLength(4000); + + b.Property("Description") + .HasColumnType("nvarchar(2000)") + .HasMaxLength(2000); + + b.Property("Expiration") + .HasColumnType("datetime2"); + + b.HasKey("ClientId", "Type", "Value"); + + b.ToTable("IdentityServerClientSecrets"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Devices.DeviceFlowCodes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("Data") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasMaxLength(50000); + + b.Property("DeviceCode") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("Expiration") + .IsRequired() + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("SubjectId") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("UserCode") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("DeviceCode") + .IsUnique(); + + b.HasIndex("Expiration"); + + b.HasIndex("UserCode") + .IsUnique(); + + b.ToTable("IdentityServerDeviceFlowCodes"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Grants.PersistedGrant", b => + { + b.Property("Key") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("Data") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasMaxLength(50000); + + b.Property("Expiration") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("SubjectId") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("Type") + .IsRequired() + .HasColumnType("nvarchar(50)") + .HasMaxLength(50); + + b.HasKey("Key"); + + b.HasIndex("Expiration"); + + b.HasIndex("SubjectId", "ClientId", "Type"); + + b.ToTable("IdentityServerPersistedGrants"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.IdentityResources.IdentityClaim", b => + { + b.Property("IdentityResourceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Type") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.HasKey("IdentityResourceId", "Type"); + + b.ToTable("IdentityServerIdentityClaims"); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.IdentityResources.IdentityResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(1000)") + .HasMaxLength(1000); + + b.Property("DisplayName") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("Emphasize") + .HasColumnType("bit"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("Properties") + .HasColumnType("nvarchar(max)"); + + b.Property("Required") + .HasColumnType("bit"); + + b.Property("ShowInDiscoveryDocument") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.ToTable("IdentityServerIdentityResources"); + }); + + modelBuilder.Entity("Volo.Abp.PermissionManagement.PermissionGrant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderName") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("Name", "ProviderName", "ProviderKey"); + + b.ToTable("AbpPermissionGrants"); + }); + + modelBuilder.Entity("Volo.Abp.SettingManagement.Setting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(2048)") + .HasMaxLength(2048); + + b.HasKey("Id"); + + b.HasIndex("Name", "ProviderName", "ProviderKey"); + + b.ToTable("AbpSettings"); + }); + + modelBuilder.Entity("Volo.Abp.TenantManagement.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.ToTable("AbpTenants"); + }); + + modelBuilder.Entity("Volo.Abp.TenantManagement.TenantConnectionString", b => + { + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.HasKey("TenantId", "Name"); + + b.ToTable("AbpTenantConnectionStrings"); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.AuditLogAction", b => + { + b.HasOne("Volo.Abp.AuditLogging.AuditLog", null) + .WithMany("Actions") + .HasForeignKey("AuditLogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityChange", b => + { + b.HasOne("Volo.Abp.AuditLogging.AuditLog", null) + .WithMany("EntityChanges") + .HasForeignKey("AuditLogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.AuditLogging.EntityPropertyChange", b => + { + b.HasOne("Volo.Abp.AuditLogging.EntityChange", null) + .WithMany("PropertyChanges") + .HasForeignKey("EntityChangeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => + { + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany("Claims") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Claims") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Logins") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserOrganizationUnit", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany() + .HasForeignKey("OrganizationUnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("OrganizationUnits") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => + { + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Roles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Tokens") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnit", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany() + .HasForeignKey("ParentId"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.OrganizationUnitRole", b => + { + b.HasOne("Volo.Abp.Identity.OrganizationUnit", null) + .WithMany("Roles") + .HasForeignKey("OrganizationUnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.ApiResources.ApiResourceClaim", b => + { + b.HasOne("Volo.Abp.IdentityServer.ApiResources.ApiResource", null) + .WithMany("UserClaims") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.ApiResources.ApiScope", b => + { + b.HasOne("Volo.Abp.IdentityServer.ApiResources.ApiResource", null) + .WithMany("Scopes") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.ApiResources.ApiScopeClaim", b => + { + b.HasOne("Volo.Abp.IdentityServer.ApiResources.ApiScope", null) + .WithMany("UserClaims") + .HasForeignKey("ApiResourceId", "Name") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.ApiResources.ApiSecret", b => + { + b.HasOne("Volo.Abp.IdentityServer.ApiResources.ApiResource", null) + .WithMany("Secrets") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientClaim", b => + { + b.HasOne("Volo.Abp.IdentityServer.Clients.Client", null) + .WithMany("Claims") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientCorsOrigin", b => + { + b.HasOne("Volo.Abp.IdentityServer.Clients.Client", null) + .WithMany("AllowedCorsOrigins") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientGrantType", b => + { + b.HasOne("Volo.Abp.IdentityServer.Clients.Client", null) + .WithMany("AllowedGrantTypes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientIdPRestriction", b => + { + b.HasOne("Volo.Abp.IdentityServer.Clients.Client", null) + .WithMany("IdentityProviderRestrictions") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientPostLogoutRedirectUri", b => + { + b.HasOne("Volo.Abp.IdentityServer.Clients.Client", null) + .WithMany("PostLogoutRedirectUris") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientProperty", b => + { + b.HasOne("Volo.Abp.IdentityServer.Clients.Client", null) + .WithMany("Properties") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientRedirectUri", b => + { + b.HasOne("Volo.Abp.IdentityServer.Clients.Client", null) + .WithMany("RedirectUris") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientScope", b => + { + b.HasOne("Volo.Abp.IdentityServer.Clients.Client", null) + .WithMany("AllowedScopes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.Clients.ClientSecret", b => + { + b.HasOne("Volo.Abp.IdentityServer.Clients.Client", null) + .WithMany("ClientSecrets") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.IdentityServer.IdentityResources.IdentityClaim", b => + { + b.HasOne("Volo.Abp.IdentityServer.IdentityResources.IdentityResource", null) + .WithMany("UserClaims") + .HasForeignKey("IdentityResourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.TenantManagement.TenantConnectionString", b => + { + b.HasOne("Volo.Abp.TenantManagement.Tenant", null) + .WithMany("ConnectionStrings") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Pages/Index.cshtml b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Pages/Index.cshtml new file mode 100644 index 0000000000..522b6ebdf7 --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Pages/Index.cshtml @@ -0,0 +1,34 @@ +@page +@using Volo.CmsKit.Pages +@using Volo.Abp.Users +@model IndexModel +@inject ICurrentUser CurrentUser +@if (CurrentUser.IsAuthenticated) +{ +
    + + + + Logout + + +

    @CurrentUser.UserName

    +
    @CurrentUser.Email
    +
    + Roles: @CurrentUser.Roles.JoinAsString(", ") +
    + Claims:
    + @Html.Raw(CurrentUser.GetAllClaims().Select(c => $"{c.Type}={c.Value}").JoinAsString("
    ")) +
    +
    +
    +
    +} + +@if (!CurrentUser.IsAuthenticated) +{ +
    +

    + Login +
    +} diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Pages/Index.cshtml.cs b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Pages/Index.cshtml.cs new file mode 100644 index 0000000000..77ead3d91a --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Pages/Index.cshtml.cs @@ -0,0 +1,11 @@ +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace Volo.CmsKit.Pages +{ + public class IndexModel : AbpPageModel + { + public void OnGet() + { + } + } +} \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/Pages/_ViewImports.cshtml b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Pages/_ViewImports.cshtml similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/Pages/_ViewImports.cshtml rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/Pages/_ViewImports.cshtml diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Program.cs b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Program.cs new file mode 100644 index 0000000000..7349f8eb0c --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Program.cs @@ -0,0 +1,51 @@ +using System; +using System.IO; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; +using Serilog; +using Serilog.Events; + +namespace Volo.CmsKit +{ + public class Program + { + public static int Main(string[] args) + { + Log.Logger = new LoggerConfiguration() +#if DEBUG + .MinimumLevel.Debug() +#else + .MinimumLevel.Information() +#endif + .MinimumLevel.Override("Microsoft", LogEventLevel.Information) + .Enrich.FromLogContext() + .WriteTo.File("Logs/logs.txt") + .CreateLogger(); + + try + { + Log.Information("Starting web host."); + CreateHostBuilder(args).Build().Run(); + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Host terminated unexpectedly!"); + return 1; + } + finally + { + Log.CloseAndFlush(); + } + } + + internal static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }) + .UseAutofac() + .UseSerilog(); + } +} diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Properties/launchSettings.json b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Properties/launchSettings.json new file mode 100644 index 0000000000..d3b75ec67d --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "https://localhost:44318", + "sslPort": 44318 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IdentityServerHost": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "https://localhost:44318", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Startup.cs b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Startup.cs new file mode 100644 index 0000000000..3ae2eec7c7 --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Startup.cs @@ -0,0 +1,21 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Volo.CmsKit +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddApplication(); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) + { + app.InitializeApplication(); + } + } +} diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Volo.CmsKit.IdentityServer.csproj b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Volo.CmsKit.IdentityServer.csproj new file mode 100644 index 0000000000..da90915bf2 --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/Volo.CmsKit.IdentityServer.csproj @@ -0,0 +1,62 @@ + + + + netcoreapp3.1 + Volo.CmsKit + true + Volo.CmsKit-c2d31439-b723-48e2-b061-5ebd7aeb6010 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + Always + + + + diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/abp.resourcemapping.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/abp.resourcemapping.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/abp.resourcemapping.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/abp.resourcemapping.js diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/appsettings.json b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/appsettings.json new file mode 100644 index 0000000000..e0f3118a11 --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/appsettings.json @@ -0,0 +1,28 @@ +{ + "App": { + "SelfUrl": "https://localhost:44318/", + "CorsOrigins": "https://*.CmsKit.com,http://localhost:4200" + }, + "AppSelfUrl": "https://localhost:44318/", + "ConnectionStrings": { + "Default": "Server=localhost;Database=CmsKit_Main;Trusted_Connection=True;MultipleActiveResultSets=true" + }, + "Redis": { + "Configuration": "127.0.0.1" + }, + "AuthServer": { + "Authority": "https://localhost:44318/", + "ApiName": "CmsKit" + }, + "IdentityServer": { + "Clients": { + "CmsKit_Web": { + "ClientId": "CmsKit_Web", + "RootUrl": "https://localhost:44350/" + }, + "CmsKit_ConsoleTestApp": { + "ClientId": "CmsKit_ConsoleTestApp" + } + } + } +} diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/gulpfile.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/gulpfile.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/gulpfile.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/gulpfile.js diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/package.json b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/package.json new file mode 100644 index 0000000000..dc3297a937 --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/package.json @@ -0,0 +1,8 @@ +{ + "version": "1.0.0", + "name": "my-app-identityserver", + "private": true, + "dependencies": { + "@abp/aspnetcore.mvc.ui.theme.basic": "^3.0.3" + } +} \ No newline at end of file diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/tempkey.rsa b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/tempkey.rsa new file mode 100644 index 0000000000..aa07a3b6a0 --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/tempkey.rsa @@ -0,0 +1 @@ +{"KeyId":"0590816638b0f87bd230e19c32f7cec9","Parameters":{"D":"yIGe39uGuy/OUB5AurrPjG+bjZ9V+bt7m/yJjOd/1jtzq4rcxc8iz7ZLgXVkSyzjgbg6m1x/xMFoUGdwYHdauvcuM2ATuP4IMxJ4R3L0s5Xk6dqYaCyN+3LJ/1H6YlBz6e5NdljVwybjHJGxuJRSE1JhsoHx8f3S3ITvVVLKxknXyWcjIdtYo+x5qEnHyQd7/I0M2q7JkRP8e2pPfQsCNbUYmjWmv/Kr3hI0Jyn2x8QpcoOBwe0gX3tq0V771x6A+zqQYMaU8zltdvzjHBCeqkBJBgYTh/jKBcWDfcmk2yxHg000sCQ5lSMTS5gt5kYsv482S+TrsSu9pCuQ2Nw6BQ==","DP":"iLJNtCSxG77N6xWGbjaJCr99evuPs6SOtUSx/RR7zHQKITSuTq127kpESoaK9FbZyQMSGqj20FX72OdUqlJO/aSaUZA9yBKhpv2+hHpc3wsBaSbv+DadcWpe3c6MnadlV/WhRMWqGRNOr5iAroCbuDR3xR3isIEqZxnf86xla88=","DQ":"jkOQUtr0PSAtRexVQpu4bTVgJP2pfNK90o3NzG7rFscWJb94uBZC22clCUlmRJ+fFTkL4iO7rD7Uz0AYKLOtrw+WOvpEv4MBg7x/sWWblbbJr7alt7XwWoi2CT0R+pBS7VuQE+XhTUxFM+SQDWDMIpdGQ4/MBk9OXLZL99kipLc=","Exponent":"AQAB","InverseQ":"VSPcFjYbRq1lLOV8LfZnF2wdomOMH4uX2Va2b1Z0MCr+gDBx28EJNpUl2EGLY58ejA7dS0uapx0TyDehEM3ShS4cHFMnf8OoUB52EEgsF9msIDX5xvqJ+Bkt9mYiY7CWbXFCI4PZT94j/XdepO6JSr1/P6ZH3ncRRH2IJ6pPjDo=","Modulus":"z1wM2it4NloyS0MO9oZo/SP5N+BwsiP/yay3MsFJXz2/GBUznjTdtdwZnFga27VCmuOjeRYjWjtESlh/M6eTY8ztPOIr1ci7zVpYZ48Iy+9HE9poVy5ATdLuS19nivUANSOdmHB7X1oKwGHJRW43xUwAx3qkpmLnSE5J8Zpemyyto9tkvHqZhOAfcP/Gw7KszOvKHS1FEmaE9JFrqFtD5ODglAwF14uTfa6I6Wy0ws49LMm7ul6YZ7Ey1Y9kQ50TuJ+Z7BK/OkjOnTIuM0JsqwSMZ99Nq5po3ERo0iBxAZaYvO4SRjRxwL/tr7KLBtCp/4cEFRXRYz5k/hzGGfbDBQ==","P":"1ntEkTjBbqhZDYw1femyxVfV2RZq8fwUYkFwunUtuLgnZ420U/1ctgl9vX5oBqFdGp+OO5q7+5eQWSis2LBTWneGyF74W+/OPi472CrQFhzL7F1vwZL55uHnCCEpApFvC+PWwTCs4KvariSXbYzy4gZDfUq9x3QU6eHgZXkVhR8=","Q":"93/Z4QwtsdbEO5F4M5l10q9g43M12+qgDAoWUZEM7huxHxZvipBRxuOeEEkQaWZ02cL3/vbmOU35Vz4FKBKw1B2HecZwUUZO6p01FJDWDyulAl+70IlhwlDpyNqSJTcWz5kDszHoqHZDBSSKPL7cMH1tRf02AYgBqIRUf5pob1s="}} \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/css/all.css b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/css/all.css similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/css/all.css rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/css/all.css diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/css/v4-shims.css b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/css/v4-shims.css similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/css/v4-shims.css rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/css/v4-shims.css diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-brands-400.eot b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-brands-400.eot similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-brands-400.eot rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-brands-400.eot diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-brands-400.svg b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-brands-400.svg similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-brands-400.svg rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-brands-400.svg diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-brands-400.ttf b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-brands-400.ttf similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-brands-400.ttf rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-brands-400.ttf diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff2 b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff2 similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff2 rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff2 diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-regular-400.eot b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-regular-400.eot similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-regular-400.eot rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-regular-400.eot diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-regular-400.svg b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-regular-400.svg similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-regular-400.svg rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-regular-400.svg diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-regular-400.ttf b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-regular-400.ttf similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-regular-400.ttf rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-regular-400.ttf diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff2 b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff2 similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff2 rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff2 diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-solid-900.eot b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-solid-900.eot similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-solid-900.eot rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-solid-900.eot diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-solid-900.svg b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-solid-900.svg similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-solid-900.svg rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-solid-900.svg diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-solid-900.ttf b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-solid-900.ttf similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-solid-900.ttf rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-solid-900.ttf diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff2 b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff2 similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff2 rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff2 diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/abp/core/abp.css b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/abp/core/abp.css similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/abp/core/abp.css rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/abp/core/abp.css diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/abp/core/abp.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/abp/core/abp.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/abp/core/abp.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/abp/core/abp.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/abp/jquery/abp.jquery.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/abp/jquery/abp.jquery.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/abp/jquery/abp.jquery.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/abp/jquery/abp.jquery.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/abp/utils/abp-utils.umd.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/abp/utils/abp-utils.umd.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/abp/utils/abp-utils.umd.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/abp/utils/abp-utils.umd.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/abp/utils/abp-utils.umd.js.map b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/abp/utils/abp-utils.umd.js.map similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/abp/utils/abp-utils.umd.js.map rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/abp/utils/abp-utils.umd.js.map diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/abp/utils/abp-utils.umd.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/abp/utils/abp-utils.umd.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/abp/utils/abp-utils.umd.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/abp/utils/abp-utils.umd.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/abp/utils/abp-utils.umd.min.js.map b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/abp/utils/abp-utils.umd.min.js.map similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/abp/utils/abp-utils.umd.min.js.map rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/abp/utils/abp-utils.umd.min.js.map diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/bootstrap-datepicker.css.map b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/bootstrap-datepicker.css.map similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/bootstrap-datepicker.css.map rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/bootstrap-datepicker.css.map diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/bootstrap-datepicker.min.css b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/bootstrap-datepicker.min.css similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/bootstrap-datepicker.min.css rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/bootstrap-datepicker.min.css diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/bootstrap-datepicker.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/bootstrap-datepicker.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/bootstrap-datepicker.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/bootstrap-datepicker.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker-en-CA.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker-en-CA.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker-en-CA.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker-en-CA.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ar-tn.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ar-tn.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ar-tn.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ar-tn.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ar.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ar.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ar.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ar.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.az.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.az.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.az.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.az.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.bg.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.bg.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.bg.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.bg.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.bm.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.bm.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.bm.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.bm.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.bn.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.bn.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.bn.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.bn.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.br.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.br.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.br.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.br.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.bs.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.bs.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.bs.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.bs.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ca.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ca.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ca.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ca.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.cs.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.cs.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.cs.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.cs.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.cy.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.cy.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.cy.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.cy.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.da.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.da.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.da.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.da.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.de.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.de.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.de.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.de.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.el.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.el.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.el.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.el.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.en-AU.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.en-AU.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.en-AU.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.en-AU.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.en-CA.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.en-CA.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.en-CA.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.en-CA.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.en-GB.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.en-GB.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.en-GB.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.en-GB.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.en-IE.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.en-IE.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.en-IE.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.en-IE.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.en-NZ.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.en-NZ.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.en-NZ.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.en-NZ.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.en-ZA.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.en-ZA.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.en-ZA.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.en-ZA.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.eo.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.eo.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.eo.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.eo.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.es.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.es.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.es.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.es.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.et.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.et.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.et.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.et.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.eu.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.eu.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.eu.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.eu.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.fa.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.fa.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.fa.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.fa.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.fi.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.fi.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.fi.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.fi.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.fo.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.fo.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.fo.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.fo.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.fr-CH.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.fr-CH.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.fr-CH.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.fr-CH.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.fr.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.fr.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.fr.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.fr.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.gl.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.gl.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.gl.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.gl.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.he.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.he.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.he.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.he.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.hi.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.hi.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.hi.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.hi.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.hr.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.hr.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.hr.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.hr.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.hu.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.hu.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.hu.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.hu.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.hy.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.hy.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.hy.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.hy.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.id.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.id.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.id.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.id.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.is.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.is.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.is.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.is.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.it-CH.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.it-CH.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.it-CH.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.it-CH.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.it.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.it.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.it.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.it.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ja.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ja.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ja.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ja.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ka.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ka.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ka.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ka.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.kh.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.kh.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.kh.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.kh.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.kk.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.kk.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.kk.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.kk.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.km.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.km.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.km.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.km.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ko.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ko.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ko.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ko.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.kr.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.kr.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.kr.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.kr.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.lt.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.lt.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.lt.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.lt.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.lv.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.lv.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.lv.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.lv.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.me.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.me.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.me.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.me.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.mk.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.mk.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.mk.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.mk.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.mn.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.mn.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.mn.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.mn.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ms.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ms.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ms.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ms.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.nl-BE.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.nl-BE.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.nl-BE.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.nl-BE.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.nl.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.nl.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.nl.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.nl.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.no.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.no.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.no.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.no.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.oc.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.oc.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.oc.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.oc.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.pl.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.pl.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.pl.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.pl.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.pt-BR.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.pt-BR.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.pt-BR.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.pt-BR.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.pt.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.pt.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.pt.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.pt.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ro.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ro.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ro.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ro.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.rs-latin.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.rs-latin.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.rs-latin.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.rs-latin.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.rs.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.rs.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.rs.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.rs.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ru.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ru.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ru.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ru.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.si.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.si.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.si.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.si.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sk.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sk.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sk.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sk.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sl.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sl.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sl.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sl.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sq.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sq.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sq.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sq.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sr-latin.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sr-latin.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sr-latin.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sr-latin.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sr.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sr.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sr.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sr.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sv.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sv.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sv.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sv.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sw.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sw.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sw.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.sw.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ta.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ta.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ta.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.ta.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.tg.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.tg.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.tg.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.tg.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.th.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.th.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.th.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.th.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.tk.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.tk.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.tk.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.tk.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.tr.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.tr.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.tr.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.tr.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.uk.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.uk.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.uk.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.uk.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.uz-cyrl.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.uz-cyrl.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.uz-cyrl.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.uz-cyrl.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.uz-latn.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.uz-latn.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.uz-latn.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.uz-latn.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.vi.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.vi.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.vi.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.vi.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.zh-CN.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.zh-CN.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.zh-CN.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.zh-CN.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.zh-TW.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.zh-TW.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.zh-TW.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap-datepicker/locales/bootstrap-datepicker.zh-TW.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap/css/bootstrap-rtl.css b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap/css/bootstrap-rtl.css similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap/css/bootstrap-rtl.css rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap/css/bootstrap-rtl.css diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap/css/bootstrap-rtl.css.map b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap/css/bootstrap-rtl.css.map similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap/css/bootstrap-rtl.css.map rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap/css/bootstrap-rtl.css.map diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap/css/bootstrap-rtl.min.css b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap/css/bootstrap-rtl.min.css similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap/css/bootstrap-rtl.min.css rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap/css/bootstrap-rtl.min.css diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap/css/bootstrap-rtl.min.css.map b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap/css/bootstrap-rtl.min.css.map similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap/css/bootstrap-rtl.min.css.map rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap/css/bootstrap-rtl.min.css.map diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap/css/bootstrap.css b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap/css/bootstrap.css similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap/css/bootstrap.css rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap/css/bootstrap.css diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap/css/bootstrap.css.map b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap/css/bootstrap.css.map similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap/css/bootstrap.css.map rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap/css/bootstrap.css.map diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap/css/bootstrap.min.css b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap/css/bootstrap.min.css similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap/css/bootstrap.min.css rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap/css/bootstrap.min.css diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap/css/bootstrap.min.css.map b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap/css/bootstrap.min.css.map similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap/css/bootstrap.min.css.map rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap/css/bootstrap.min.css.map diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap/js/bootstrap.bundle.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap/js/bootstrap.bundle.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap/js/bootstrap.bundle.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap/js/bootstrap.bundle.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap/js/bootstrap.bundle.js.map b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap/js/bootstrap.bundle.js.map similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap/js/bootstrap.bundle.js.map rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap/js/bootstrap.bundle.js.map diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap/js/bootstrap.bundle.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap/js/bootstrap.bundle.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap/js/bootstrap.bundle.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap/js/bootstrap.bundle.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap/js/bootstrap.bundle.min.js.map b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap/js/bootstrap.bundle.min.js.map similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/bootstrap/js/bootstrap.bundle.min.js.map rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/bootstrap/js/bootstrap.bundle.min.js.map diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/datatables.net-bs4/css/dataTables.bootstrap4.css b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/datatables.net-bs4/css/dataTables.bootstrap4.css similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/datatables.net-bs4/css/dataTables.bootstrap4.css rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/datatables.net-bs4/css/dataTables.bootstrap4.css diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/datatables.net-bs4/js/dataTables.bootstrap4.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/datatables.net-bs4/js/dataTables.bootstrap4.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/datatables.net-bs4/js/dataTables.bootstrap4.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/datatables.net-bs4/js/dataTables.bootstrap4.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/datatables.net/js/jquery.dataTables.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/datatables.net/js/jquery.dataTables.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/datatables.net/js/jquery.dataTables.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/datatables.net/js/jquery.dataTables.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-form/jquery.form.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-form/jquery.form.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-form/jquery.form.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-form/jquery.form.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-form/jquery.form.min.js.map b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-form/jquery.form.min.js.map similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-form/jquery.form.min.js.map rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-form/jquery.form.min.js.map diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/jquery.validate.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/jquery.validate.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/jquery.validate.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/jquery.validate.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ar.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ar.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ar.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ar.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ar.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ar.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ar.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ar.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_az.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_az.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_az.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_az.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_az.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_az.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_az.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_az.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_bg.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_bg.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_bg.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_bg.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_bg.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_bg.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_bg.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_bg.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_bn_BD.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_bn_BD.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_bn_BD.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_bn_BD.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_bn_BD.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_bn_BD.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_bn_BD.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_bn_BD.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ca.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ca.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ca.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ca.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ca.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ca.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ca.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ca.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_cs.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_cs.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_cs.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_cs.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_cs.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_cs.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_cs.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_cs.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_da.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_da.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_da.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_da.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_da.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_da.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_da.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_da.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_de.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_de.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_de.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_de.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_de.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_de.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_de.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_de.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_el.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_el.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_el.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_el.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_el.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_el.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_el.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_el.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_es.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_es.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_es.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_es.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_es.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_es.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_es.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_es.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_es_AR.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_es_AR.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_es_AR.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_es_AR.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_es_AR.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_es_AR.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_es_AR.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_es_AR.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_es_PE.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_es_PE.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_es_PE.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_es_PE.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_es_PE.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_es_PE.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_es_PE.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_es_PE.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_et.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_et.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_et.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_et.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_et.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_et.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_et.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_et.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_eu.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_eu.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_eu.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_eu.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_eu.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_eu.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_eu.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_eu.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_fa.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_fa.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_fa.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_fa.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_fa.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_fa.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_fa.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_fa.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_fi.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_fi.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_fi.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_fi.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_fi.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_fi.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_fi.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_fi.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_fr.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_fr.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_fr.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_fr.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_fr.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_fr.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_fr.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_fr.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ge.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ge.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ge.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ge.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ge.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ge.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ge.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ge.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_gl.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_gl.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_gl.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_gl.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_gl.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_gl.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_gl.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_gl.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_he.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_he.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_he.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_he.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_he.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_he.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_he.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_he.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_hr.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_hr.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_hr.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_hr.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_hr.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_hr.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_hr.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_hr.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_hu.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_hu.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_hu.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_hu.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_hu.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_hu.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_hu.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_hu.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_hy_AM.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_hy_AM.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_hy_AM.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_hy_AM.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_hy_AM.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_hy_AM.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_hy_AM.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_hy_AM.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_id.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_id.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_id.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_id.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_id.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_id.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_id.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_id.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_is.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_is.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_is.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_is.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_is.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_is.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_is.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_is.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_it.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_it.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_it.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_it.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_it.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_it.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_it.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_it.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ja.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ja.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ja.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ja.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ja.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ja.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ja.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ja.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ka.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ka.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ka.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ka.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ka.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ka.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ka.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ka.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_kk.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_kk.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_kk.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_kk.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_kk.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_kk.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_kk.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_kk.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ko.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ko.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ko.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ko.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ko.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ko.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ko.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ko.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_lt.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_lt.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_lt.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_lt.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_lt.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_lt.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_lt.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_lt.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_lv.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_lv.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_lv.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_lv.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_lv.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_lv.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_lv.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_lv.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_mk.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_mk.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_mk.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_mk.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_mk.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_mk.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_mk.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_mk.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_my.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_my.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_my.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_my.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_my.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_my.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_my.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_my.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_nl.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_nl.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_nl.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_nl.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_nl.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_nl.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_nl.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_nl.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_no.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_no.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_no.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_no.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_no.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_no.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_no.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_no.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_pl.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_pl.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_pl.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_pl.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_pl.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_pl.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_pl.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_pl.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_pt_BR.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_pt_BR.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_pt_BR.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_pt_BR.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_pt_BR.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_pt_BR.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_pt_BR.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_pt_BR.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_pt_PT.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_pt_PT.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_pt_PT.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_pt_PT.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_pt_PT.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_pt_PT.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_pt_PT.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_pt_PT.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ro.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ro.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ro.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ro.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ro.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ro.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ro.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ro.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ru.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ru.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ru.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ru.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ru.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ru.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ru.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ru.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_sd.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_sd.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_sd.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_sd.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_sd.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_sd.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_sd.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_sd.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_si.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_si.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_si.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_si.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_si.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_si.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_si.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_si.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_sk.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_sk.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_sk.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_sk.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_sk.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_sk.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_sk.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_sk.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_sl.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_sl.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_sl.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_sl.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_sl.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_sl.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_sl.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_sl.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_sr.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_sr.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_sr.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_sr.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_sr.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_sr.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_sr.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_sr.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_sr_lat.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_sr_lat.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_sr_lat.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_sr_lat.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_sr_lat.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_sr_lat.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_sr_lat.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_sr_lat.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_sv.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_sv.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_sv.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_sv.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_sv.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_sv.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_sv.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_sv.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_th.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_th.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_th.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_th.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_th.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_th.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_th.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_th.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_tj.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_tj.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_tj.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_tj.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_tj.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_tj.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_tj.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_tj.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_tr.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_tr.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_tr.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_tr.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_tr.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_tr.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_tr.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_tr.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_uk.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_uk.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_uk.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_uk.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_uk.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_uk.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_uk.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_uk.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ur.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ur.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ur.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ur.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ur.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ur.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_ur.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_ur.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_vi.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_vi.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_vi.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_vi.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_vi.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_vi.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_vi.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_vi.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_zh.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_zh.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_zh.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_zh.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_zh.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_zh.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_zh.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_zh.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_zh_TW.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_zh_TW.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_zh_TW.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_zh_TW.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_zh_TW.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_zh_TW.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/messages_zh_TW.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/messages_zh_TW.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/methods_de.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/methods_de.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/methods_de.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/methods_de.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/methods_de.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/methods_de.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/methods_de.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/methods_de.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/methods_es_CL.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/methods_es_CL.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/methods_es_CL.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/methods_es_CL.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/methods_es_CL.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/methods_es_CL.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/methods_es_CL.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/methods_es_CL.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/methods_fi.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/methods_fi.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/methods_fi.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/methods_fi.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/methods_fi.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/methods_fi.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/methods_fi.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/methods_fi.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/methods_it.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/methods_it.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/methods_it.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/methods_it.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/methods_it.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/methods_it.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/methods_it.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/methods_it.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/methods_nl.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/methods_nl.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/methods_nl.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/methods_nl.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/methods_nl.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/methods_nl.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/methods_nl.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/methods_nl.min.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/methods_pt.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/methods_pt.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/methods_pt.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/methods_pt.js diff --git a/samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/methods_pt.min.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/methods_pt.min.js similarity index 100% rename from samples/MicroserviceDemo/applications/AuthServer.Host/wwwroot/libs/jquery-validation/localization/methods_pt.min.js rename to modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery-validation/localization/methods_pt.min.js diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery/jquery.js b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery/jquery.js new file mode 100644 index 0000000000..773ad95c56 --- /dev/null +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/jquery/jquery.js @@ -0,0 +1,10598 @@ +/*! + * jQuery JavaScript Library v3.4.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2019-05-01T21:04Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var document = window.document; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var concat = arr.concat; + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + return typeof obj === "function" && typeof obj.nodeType !== "number"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.4.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }, + + // Support: Android <=4.0 only + // Make sure we trim BOM and NBSP + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a global context + globalEval: function( code, options ) { + DOMEval( code, { nonce: options && options.nonce } ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // Support: Android <=4.0 only + trim: function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.4 + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://js.foundation/ + * + * Date: 2019-04-08 + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rhtml = /HTML$/i, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox<24 + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { + + // ID selector + if ( (m = match[1]) ) { + + // Document context + if ( nodeType === 9 ) { + if ( (elem = context.getElementById( m )) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && (elem = newContext.getElementById( m )) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( (m = match[3]) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !nonnativeSelectorCache[ selector + " " ] && + (!rbuggyQSA || !rbuggyQSA.test( selector )) && + + // Support: IE 8 only + // Exclude object elements + (nodeType !== 1 || context.nodeName.toLowerCase() !== "object") ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && rdescend.test( selector ) ) { + + // Capture the context ID, setting it first if necessary + if ( (nid = context.getAttribute( "id" )) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", (nid = expando) ); + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[i] = "#" + nid + " " + toSelector( groups[i] ); + } + newSelector = groups.join( "," ); + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement("fieldset"); + + try { + return !!fn( el ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + var namespace = elem.namespaceURI, + docElem = (elem.ownerDocument || elem).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9-11, Edge + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + if ( preferredDoc !== document && + (subWindow = document.defaultView) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert(function( el ) { + el.className = "i"; + return !el.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( el ) { + el.appendChild( document.createComment("") ); + return !el.getElementsByTagName("*").length; + }); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + }); + + // ID filter and find + if ( support.getById ) { + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode("id"); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( (elem = elems[i++]) ) { + node = elem.getAttributeNode("id"); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( el ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll("[msallowcapture^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push("~="); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push(".#.+[+~]"); + } + }); + + assert(function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement("input"); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll("[name=d]").length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll(":enabled").length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll(":disabled").length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( el ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + return -1; + } + if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + return a === document ? -1 : + b === document ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + if ( support.matchesSelector && documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch (e) { + nonnativeSelectorCache( expr, true ); + } + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return (sel + "").replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[6] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] ) { + match[2] = match[4] || match[5] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + // Use previously-cached element index if available + if ( useCache ) { + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + // Don't keep the element (issue #299) + input[0] = null; + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( (tokens = []) ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( (oldCache = uniqueCache[ key ]) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return (newCache[ 2 ] = oldCache[ 2 ]); + } else { + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; + + if ( outermost ) { + outermostContext = context === document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + if ( !context && elem.ownerDocument !== document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context || document, xml) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( (selector = compiled.selector || selector) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert(function( el ) { + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement("fieldset") ) & 1; +}); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert(function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert(function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert(function( el ) { + return el.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + null; + } + }); +} + +return Sizzle; + +})( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +}; +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( typeof elem.contentDocument !== "undefined" ) { + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); + } + + return master.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + +var swap = function( elem, options, callback, args ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.apply( elem, args || [] ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // Support: IE <=9 only + option: [ 1, "" ], + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
    " ], + col: [ 2, "", "
    " ], + tr: [ 2, "", "
    " ], + td: [ 3, "", "
    " ], + + _default: [ 0, "", "" ] +}; + +// Support: IE <=9 only +wrapMap.optgroup = wrapMap.option; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; +} )(); + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = {}; + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + // Make a writable jQuery.Event from the native event object + var event = jQuery.event.fix( nativeEvent ); + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + return result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + var button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + delegateType: delegateType + }; +} ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + /* eslint-disable max-len */ + + // See https://github.com/eslint/eslint/issues/3229 + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi, + + /* eslint-enable */ + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.access( src ); + pdataCur = dataPriv.set( dest, pdataOld ); + events = pdataOld.events; + + if ( events ) { + delete pdataCur.handle; + pdataCur.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = concat.apply( [], args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + } ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html.replace( rxhtmlTag, "<$1>" ); + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + // Support: IE 9-11 only + // Also use offsetWidth/offsetHeight for when box sizing is unreliable + // We use getClientRects() to check for hidden/disconnected. + // In those cases, the computed value can be trusted to be border-box + if ( ( !support.boxSizingReliable() && isBorderBox || + val === "auto" || + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue && type !== false ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( isValidValue ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = classesToArray( value ); + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +support.focusin = "onfocusin" in window; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + var doc = this.ownerDocument || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = Date.now(); + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) { + xml = undefined; + } + + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + + +jQuery._evalUrl = function( url, options ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options ); + } + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( "",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"meta",v:[{b:/<\?xml/,e:/\?>/,r:10},{b:/<\?\w+/,e:/\?>/}]},{cN:"tag",b:"",c:[{cN:"name",b:/[^\/><\s]+/,r:0},t]}]}});hljs.registerLanguage("markdown",function(e){return{aliases:["md","mkdown","mkd"],c:[{cN:"section",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"quote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"^```w*s*$",e:"^```s*$"},{b:"`.+?`"},{b:"^( {4}| )",e:"$",r:0}]},{b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].*?[\\)\\]]",rB:!0,c:[{cN:"string",b:"\\[",e:"\\]",eB:!0,rE:!0,r:0},{cN:"link",b:"\\]\\(",e:"\\)",eB:!0,eE:!0},{cN:"symbol",b:"\\]\\[",e:"\\]",eB:!0,eE:!0}],r:10},{b:/^\[[^\n]+\]:/,rB:!0,c:[{cN:"symbol",b:/\[/,e:/\]/,eB:!0,eE:!0},{cN:"link",b:/:\s*/,e:/$/,eB:!0}]}]}});hljs.registerLanguage("javascript",function(e){var r="[A-Za-z$_][0-9A-Za-z$_]*",t={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},a={cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},n={cN:"subst",b:"\\$\\{",e:"\\}",k:t,c:[]},c={cN:"string",b:"`",e:"`",c:[e.BE,n]};n.c=[e.ASM,e.QSM,c,a,e.RM];var s=n.c.concat([e.CBCM,e.CLCM]);return{aliases:["js","jsx"],k:t,c:[{cN:"meta",r:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,c,e.CLCM,e.CBCM,a,{b:/[{,]\s*/,r:0,c:[{b:r+"\\s*:",rB:!0,r:0,c:[{cN:"attr",b:r,r:0}]}]},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+r+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:r},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:s}]}]},{b://,sL:"xml",c:[{b:/<\w+\s*\/>/,skip:!0},{b:/<\w+/,e:/(\/\w+|\w+\/)>/,skip:!0,c:[{b:/<\w+\s*\/>/,skip:!0},"self"]}]}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:r}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:s}],i:/\[|%/},{b:/\$[(.]/},e.METHOD_GUARD,{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor",e:/\{/,eE:!0}],i:/#(?!!)/}});hljs.registerLanguage("powershell",function(e){var t={b:"`[\\s\\S]",r:0},o={cN:"variable",v:[{b:/\$[\w\d][\w\d_:]*/}]},r={cN:"literal",b:/\$(null|true|false)\b/},n={cN:"string",v:[{b:/"/,e:/"/},{b:/@"/,e:/^"@/}],c:[t,o,{cN:"variable",b:/\$[A-z]/,e:/[^A-z]/}]},a={cN:"string",v:[{b:/'/,e:/'/},{b:/@'/,e:/^'@/}]},i={cN:"doctag",v:[{b:/\.(synopsis|description|example|inputs|outputs|notes|link|component|role|functionality)/},{b:/\.(parameter|forwardhelptargetname|forwardhelpcategory|remotehelprunspace|externalhelp)\s+\S+/}]},s=e.inherit(e.C(null,null),{v:[{b:/#/,e:/$/},{b:/<#/,e:/#>/}],c:[i]});return{aliases:["ps"],l:/-?[A-z\.\-]+/,cI:!0,k:{keyword:"if else foreach return function do while until elseif begin for trap data dynamicparam end break throw param continue finally in switch exit filter try process catch",built_in:"Add-Computer Add-Content Add-History Add-JobTrigger Add-Member Add-PSSnapin Add-Type Checkpoint-Computer Clear-Content Clear-EventLog Clear-History Clear-Host Clear-Item Clear-ItemProperty Clear-Variable Compare-Object Complete-Transaction Connect-PSSession Connect-WSMan Convert-Path ConvertFrom-Csv ConvertFrom-Json ConvertFrom-SecureString ConvertFrom-StringData ConvertTo-Csv ConvertTo-Html ConvertTo-Json ConvertTo-SecureString ConvertTo-Xml Copy-Item Copy-ItemProperty Debug-Process Disable-ComputerRestore Disable-JobTrigger Disable-PSBreakpoint Disable-PSRemoting Disable-PSSessionConfiguration Disable-WSManCredSSP Disconnect-PSSession Disconnect-WSMan Disable-ScheduledJob Enable-ComputerRestore Enable-JobTrigger Enable-PSBreakpoint Enable-PSRemoting Enable-PSSessionConfiguration Enable-ScheduledJob Enable-WSManCredSSP Enter-PSSession Exit-PSSession Export-Alias Export-Clixml Export-Console Export-Counter Export-Csv Export-FormatData Export-ModuleMember Export-PSSession ForEach-Object Format-Custom Format-List Format-Table Format-Wide Get-Acl Get-Alias Get-AuthenticodeSignature Get-ChildItem Get-Command Get-ComputerRestorePoint Get-Content Get-ControlPanelItem Get-Counter Get-Credential Get-Culture Get-Date Get-Event Get-EventLog Get-EventSubscriber Get-ExecutionPolicy Get-FormatData Get-Host Get-HotFix Get-Help Get-History Get-IseSnippet Get-Item Get-ItemProperty Get-Job Get-JobTrigger Get-Location Get-Member Get-Module Get-PfxCertificate Get-Process Get-PSBreakpoint Get-PSCallStack Get-PSDrive Get-PSProvider Get-PSSession Get-PSSessionConfiguration Get-PSSnapin Get-Random Get-ScheduledJob Get-ScheduledJobOption Get-Service Get-TraceSource Get-Transaction Get-TypeData Get-UICulture Get-Unique Get-Variable Get-Verb Get-WinEvent Get-WmiObject Get-WSManCredSSP Get-WSManInstance Group-Object Import-Alias Import-Clixml Import-Counter Import-Csv Import-IseSnippet Import-LocalizedData Import-PSSession Import-Module Invoke-AsWorkflow Invoke-Command Invoke-Expression Invoke-History Invoke-Item Invoke-RestMethod Invoke-WebRequest Invoke-WmiMethod Invoke-WSManAction Join-Path Limit-EventLog Measure-Command Measure-Object Move-Item Move-ItemProperty New-Alias New-Event New-EventLog New-IseSnippet New-Item New-ItemProperty New-JobTrigger New-Object New-Module New-ModuleManifest New-PSDrive New-PSSession New-PSSessionConfigurationFile New-PSSessionOption New-PSTransportOption New-PSWorkflowExecutionOption New-PSWorkflowSession New-ScheduledJobOption New-Service New-TimeSpan New-Variable New-WebServiceProxy New-WinEvent New-WSManInstance New-WSManSessionOption Out-Default Out-File Out-GridView Out-Host Out-Null Out-Printer Out-String Pop-Location Push-Location Read-Host Receive-Job Register-EngineEvent Register-ObjectEvent Register-PSSessionConfiguration Register-ScheduledJob Register-WmiEvent Remove-Computer Remove-Event Remove-EventLog Remove-Item Remove-ItemProperty Remove-Job Remove-JobTrigger Remove-Module Remove-PSBreakpoint Remove-PSDrive Remove-PSSession Remove-PSSnapin Remove-TypeData Remove-Variable Remove-WmiObject Remove-WSManInstance Rename-Computer Rename-Item Rename-ItemProperty Reset-ComputerMachinePassword Resolve-Path Restart-Computer Restart-Service Restore-Computer Resume-Job Resume-Service Save-Help Select-Object Select-String Select-Xml Send-MailMessage Set-Acl Set-Alias Set-AuthenticodeSignature Set-Content Set-Date Set-ExecutionPolicy Set-Item Set-ItemProperty Set-JobTrigger Set-Location Set-PSBreakpoint Set-PSDebug Set-PSSessionConfiguration Set-ScheduledJob Set-ScheduledJobOption Set-Service Set-StrictMode Set-TraceSource Set-Variable Set-WmiInstance Set-WSManInstance Set-WSManQuickConfig Show-Command Show-ControlPanelItem Show-EventLog Sort-Object Split-Path Start-Job Start-Process Start-Service Start-Sleep Start-Transaction Start-Transcript Stop-Computer Stop-Job Stop-Process Stop-Service Stop-Transcript Suspend-Job Suspend-Service Tee-Object Test-ComputerSecureChannel Test-Connection Test-ModuleManifest Test-Path Test-PSSessionConfigurationFile Trace-Command Unblock-File Undo-Transaction Unregister-Event Unregister-PSSessionConfiguration Unregister-ScheduledJob Update-FormatData Update-Help Update-List Update-TypeData Use-Transaction Wait-Event Wait-Job Wait-Process Where-Object Write-Debug Write-Error Write-EventLog Write-Host Write-Output Write-Progress Write-Verbose Write-Warning Add-MDTPersistentDrive Disable-MDTMonitorService Enable-MDTMonitorService Get-MDTDeploymentShareStatistics Get-MDTMonitorData Get-MDTOperatingSystemCatalog Get-MDTPersistentDrive Import-MDTApplication Import-MDTDriver Import-MDTOperatingSystem Import-MDTPackage Import-MDTTaskSequence New-MDTDatabase Remove-MDTMonitorData Remove-MDTPersistentDrive Restore-MDTPersistentDrive Set-MDTMonitorData Test-MDTDeploymentShare Test-MDTMonitorData Update-MDTDatabaseSchema Update-MDTDeploymentShare Update-MDTLinkedDS Update-MDTMedia Update-MDTMedia Add-VamtProductKey Export-VamtData Find-VamtManagedMachine Get-VamtConfirmationId Get-VamtProduct Get-VamtProductKey Import-VamtData Initialize-VamtData Install-VamtConfirmationId Install-VamtProductActivation Install-VamtProductKey Update-VamtProduct",nomarkup:"-ne -eq -lt -gt -ge -le -not -like -notlike -match -notmatch -contains -notcontains -in -notin -replace"},c:[t,e.NM,n,a,r,o,s]}});hljs.registerLanguage("diff",function(e){return{aliases:["patch"],c:[{cN:"meta",r:10,v:[{b:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{b:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{b:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{cN:"comment",v:[{b:/Index: /,e:/$/},{b:/={3,}/,e:/$/},{b:/^\-{3}/,e:/$/},{b:/^\*{3} /,e:/$/},{b:/^\+{3}/,e:/$/},{b:/\*{5}/,e:/\*{5}$/}]},{cN:"addition",b:"^\\+",e:"$"},{cN:"deletion",b:"^\\-",e:"$"},{cN:"addition",b:"^\\!",e:"$"}]}});hljs.registerLanguage("java",function(e){var a="[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",t=a+"(<"+a+"(\\s*,\\s*"+a+")*>)?",r="false synchronized int abstract float private char boolean static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",s="\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",c={cN:"number",b:s,r:0};return{aliases:["jsp"],k:r,i:/<\/|#/,c:[e.C("/\\*\\*","\\*/",{r:0,c:[{b:/\w+@/,r:0},{cN:"doctag",b:"@[A-Za-z]+"}]}),e.CLCM,e.CBCM,e.ASM,e.QSM,{cN:"class",bK:"class interface",e:/[{;=]/,eE:!0,k:"class interface",i:/[:"\[\]]/,c:[{bK:"extends implements"},e.UTM]},{bK:"new throw return else",r:0},{cN:"function",b:"("+t+"\\s+)+"+e.UIR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:r,c:[{b:e.UIR+"\\s*\\(",rB:!0,r:0,c:[e.UTM]},{cN:"params",b:/\(/,e:/\)/,k:r,r:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]},c,{cN:"meta",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("vbnet",function(e){return{aliases:["vb"],cI:!0,k:{keyword:"addhandler addressof alias and andalso aggregate ansi as assembly auto binary by byref byval call case catch class compare const continue custom declare default delegate dim distinct do each equals else elseif end enum erase error event exit explicit finally for friend from function get global goto group handles if implements imports in inherits interface into is isfalse isnot istrue join key let lib like loop me mid mod module mustinherit mustoverride mybase myclass namespace narrowing new next not notinheritable notoverridable of off on operator option optional or order orelse overloads overridable overrides paramarray partial preserve private property protected public raiseevent readonly redim rem removehandler resume return select set shadows shared skip static step stop structure strict sub synclock take text then throw to try unicode until using when where while widening with withevents writeonly xor",built_in:"boolean byte cbool cbyte cchar cdate cdec cdbl char cint clng cobj csbyte cshort csng cstr ctype date decimal directcast double gettype getxmlnamespace iif integer long object sbyte short single string trycast typeof uinteger ulong ushort",literal:"true false nothing"},i:"//|{|}|endif|gosub|variant|wend",c:[e.inherit(e.QSM,{c:[{b:'""'}]}),e.C("'","$",{rB:!0,c:[{cN:"doctag",b:"'''|",c:[e.PWM]},{cN:"doctag",b:"",c:[e.PWM]}]}),e.CNM,{cN:"meta",b:"#",e:"$",k:{"meta-keyword":"if else elseif end region externalsource"}}]}});hljs.registerLanguage("cs",function(e){var i={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long nameof object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let on orderby partial remove select set value var where yield",literal:"null false true"},t={cN:"string",b:'@"',e:'"',c:[{b:'""'}]},r=e.inherit(t,{i:/\n/}),a={cN:"subst",b:"{",e:"}",k:i},c=e.inherit(a,{i:/\n/}),n={cN:"string",b:/\$"/,e:'"',i:/\n/,c:[{b:"{{"},{b:"}}"},e.BE,c]},s={cN:"string",b:/\$@"/,e:'"',c:[{b:"{{"},{b:"}}"},{b:'""'},a]},o=e.inherit(s,{i:/\n/,c:[{b:"{{"},{b:"}}"},{b:'""'},c]});a.c=[s,n,t,e.ASM,e.QSM,e.CNM,e.CBCM],c.c=[o,n,r,e.ASM,e.QSM,e.CNM,e.inherit(e.CBCM,{i:/\n/})];var l={v:[s,n,t,e.ASM,e.QSM]},b=e.IR+"(<"+e.IR+"(\\s*,\\s*"+e.IR+")*>)?(\\[\\])?";return{aliases:["csharp"],k:i,i:/::/,c:[e.C("///","$",{rB:!0,c:[{cN:"doctag",v:[{b:"///",r:0},{b:""},{b:""}]}]}),e.CLCM,e.CBCM,{cN:"meta",b:"#",e:"$",k:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},l,e.CNM,{bK:"class interface",e:/[{;=]/,i:/[^\s:]/,c:[e.TM,e.CLCM,e.CBCM]},{bK:"namespace",e:/[{;=]/,i:/[^\s:]/,c:[e.inherit(e.TM,{b:"[a-zA-Z](\\.?\\w)*"}),e.CLCM,e.CBCM]},{cN:"meta",b:"^\\s*\\[",eB:!0,e:"\\]",eE:!0,c:[{cN:"meta-string",b:/"/,e:/"/}]},{bK:"new return throw await else",r:0},{cN:"function",b:"("+b+"\\s+)+"+e.IR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:i,c:[{b:e.IR+"\\s*\\(",rB:!0,c:[e.TM],r:0},{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:i,r:0,c:[l,e.CNM,e.CBCM]},e.CLCM,e.CBCM]}]}});hljs.registerLanguage("go",function(e){var t={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{aliases:["golang"],k:t,i:"{}*#]/,c:[{bK:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment",e:/;/,eW:!0,l:/[\w\.]+/,k:{keyword:"abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias allocate allow alter always analyze ancillary and any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second section securefile security seed segment select self sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null",built_in:"array bigint binary bit blob boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text varchar varying void"},c:[{cN:"string",b:"'",e:"'",c:[e.BE,{b:"''"}]},{cN:"string",b:'"',e:'"',c:[e.BE,{b:'""'}]},{cN:"string",b:"`",e:"`",c:[e.BE]},e.CNM,e.CBCM,t]},e.CBCM,t]}});hljs.registerLanguage("vbscript",function(e){return{aliases:["vbs"],cI:!0,k:{keyword:"call class const dim do loop erase execute executeglobal exit for each next function if then else on error option explicit new private property let get public randomize redim rem select case set stop sub while wend with end to elseif is or xor and not class_initialize class_terminate default preserve in me byval byref step resume goto",built_in:"lcase month vartype instrrev ubound setlocale getobject rgb getref string weekdayname rnd dateadd monthname now day minute isarray cbool round formatcurrency conversions csng timevalue second year space abs clng timeserial fixs len asc isempty maths dateserial atn timer isobject filter weekday datevalue ccur isdate instr datediff formatdatetime replace isnull right sgn array snumeric log cdbl hex chr lbound msgbox ucase getlocale cos cdate cbyte rtrim join hour oct typename trim strcomp int createobject loadpicture tan formatnumber mid scriptenginebuildversion scriptengine split scriptengineminorversion cint sin datepart ltrim sqr scriptenginemajorversion time derived eval date formatpercent exp inputbox left ascw chrw regexp server response request cstr err",literal:"true false null nothing empty"},i:"//",c:[e.inherit(e.QSM,{c:[{b:'""'}]}),e.C(/'/,/$/,{r:0}),e.CNM]}});hljs.registerLanguage("vbscript-html",function(r){return{sL:"xml",c:[{b:"<%",e:"%>",sL:"vbscript"}]}});hljs.registerLanguage("fsharp",function(e){var t={b:"<",e:">",c:[e.inherit(e.TM,{b:/'[a-zA-Z0-9_]+/})]};return{aliases:["fs"],k:"abstract and as assert base begin class default delegate do done downcast downto elif else end exception extern false finally for fun function global if in inherit inline interface internal lazy let match member module mutable namespace new null of open or override private public rec return sig static struct then to true try type upcast use val void when while with yield",i:/\/\*/,c:[{cN:"keyword",b:/\b(yield|return|let|do)!/},{cN:"string",b:'@"',e:'"',c:[{b:'""'}]},{cN:"string",b:'"""',e:'"""'},e.C("\\(\\*","\\*\\)"),{cN:"class",bK:"type",e:"\\(|=|$",eE:!0,c:[e.UTM,t]},{cN:"meta",b:"\\[<",e:">\\]",r:10},{cN:"symbol",b:"\\B('[A-Za-z])\\b",c:[e.BE]},e.CLCM,e.inherit(e.QSM,{i:null}),e.CNM]}});hljs.registerLanguage("less",function(e){var r="[\\w-]+",t="("+r+"|@{"+r+"})",a=[],c=[],s=function(e){return{cN:"string",b:"~?"+e+".*?"+e}},b=function(e,r,t){return{cN:e,b:r,r:t}},n={b:"\\(",e:"\\)",c:c,r:0};c.push(e.CLCM,e.CBCM,s("'"),s('"'),e.CSSNM,{b:"(url|data-uri)\\(",starts:{cN:"string",e:"[\\)\\n]",eE:!0}},b("number","#[0-9A-Fa-f]+\\b"),n,b("variable","@@?"+r,10),b("variable","@{"+r+"}"),b("built_in","~?`[^`]*?`"),{cN:"attribute",b:r+"\\s*:",e:":",rB:!0,eE:!0},{cN:"meta",b:"!important"});var i=c.concat({b:"{",e:"}",c:a}),o={bK:"when",eW:!0,c:[{bK:"and not"}].concat(c)},u={b:t+"\\s*:",rB:!0,e:"[;}]",r:0,c:[{cN:"attribute",b:t,e:":",eE:!0,starts:{eW:!0,i:"[<=$]",r:0,c:c}}]},l={cN:"keyword",b:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{e:"[;{}]",rE:!0,c:c,r:0}},C={cN:"variable",v:[{b:"@"+r+"\\s*:",r:15},{b:"@"+r}],starts:{e:"[;}]",rE:!0,c:i}},p={v:[{b:"[\\.#:&\\[>]",e:"[;{}]"},{b:t,e:"{"}],rB:!0,rE:!0,i:"[<='$\"]",r:0,c:[e.CLCM,e.CBCM,o,b("keyword","all\\b"),b("variable","@{"+r+"}"),b("selector-tag",t+"%?",0),b("selector-id","#"+t),b("selector-class","\\."+t,0),b("selector-tag","&",0),{cN:"selector-attr",b:"\\[",e:"\\]"},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"\\(",e:"\\)",c:i},{b:"!important"}]};return a.push(e.CLCM,e.CBCM,l,C,u,p),{cI:!0,i:"[=>'/<($\"]",c:a}});hljs.registerLanguage("dos",function(e){var r=e.C(/^\s*@?rem\b/,/$/,{r:10}),t={cN:"symbol",b:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)",r:0};return{aliases:["bat","cmd"],cI:!0,i:/\/\*/,k:{keyword:"if else goto for in do call exit not exist errorlevel defined equ neq lss leq gtr geq",built_in:"prn nul lpt3 lpt2 lpt1 con com4 com3 com2 com1 aux shift cd dir echo setlocal endlocal set pause copy append assoc at attrib break cacls cd chcp chdir chkdsk chkntfs cls cmd color comp compact convert date dir diskcomp diskcopy doskey erase fs find findstr format ftype graftabl help keyb label md mkdir mode more move path pause print popd pushd promt rd recover rem rename replace restore rmdir shiftsort start subst time title tree type ver verify vol ping net ipconfig taskkill xcopy ren del"},c:[{cN:"variable",b:/%%[^ ]|%[^ ]+?%|![^ ]+?!/},{cN:"function",b:t.b,e:"goto:eof",c:[e.inherit(e.TM,{b:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),r]},{cN:"number",b:"\\b\\d+",r:0},r]}});hljs.registerLanguage("python",function(e){var r={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10 None True False",built_in:"Ellipsis NotImplemented"},b={cN:"meta",b:/^(>>>|\.\.\.) /},c={cN:"subst",b:/\{/,e:/\}/,k:r,i:/#/},a={cN:"string",c:[e.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[b],r:10},{b:/(u|b)?r?"""/,e:/"""/,c:[b],r:10},{b:/(fr|rf|f)'''/,e:/'''/,c:[b,c]},{b:/(fr|rf|f)"""/,e:/"""/,c:[b,c]},{b:/(u|r|ur)'/,e:/'/,r:10},{b:/(u|r|ur)"/,e:/"/,r:10},{b:/(b|br)'/,e:/'/},{b:/(b|br)"/,e:/"/},{b:/(fr|rf|f)'/,e:/'/,c:[c]},{b:/(fr|rf|f)"/,e:/"/,c:[c]},e.ASM,e.QSM]},s={cN:"number",r:0,v:[{b:e.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:e.CNR+"[lLjJ]?"}]},i={cN:"params",b:/\(/,e:/\)/,c:["self",b,s,a]};return c.c=[a,s,b],{aliases:["py","gyp"],k:r,i:/(<\/|->|\?)|=>/,c:[b,s,a,e.HCM,{v:[{cN:"function",bK:"def"},{cN:"class",bK:"class"}],e:/:/,i:/[${=;\n,]/,c:[e.UTM,i,{b:/->/,eW:!0,k:"None"}]},{cN:"meta",b:/^[\t ]*@/,e:/$/},{b:/\b(print|exec)\(/}]}});hljs.registerLanguage("css",function(e){var c="[a-zA-Z-][a-zA-Z0-9_-]*",t={b:/[A-Z\_\.\-]+\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{eW:!0,eE:!0,c:[{b:/[\w-]+\(/,rB:!0,c:[{cN:"built_in",b:/[\w-]+/},{b:/\(/,e:/\)/,c:[e.ASM,e.QSM]}]},e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"number",b:"#[0-9A-Fa-f]+"},{cN:"meta",b:"!important"}]}}]};return{cI:!0,i:/[=\/|'\$]/,c:[e.CBCM,{cN:"selector-id",b:/#[A-Za-z0-9_-]+/},{cN:"selector-class",b:/\.[A-Za-z0-9_-]+/},{cN:"selector-attr",b:/\[/,e:/\]/,i:"$"},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{b:"@",e:"[{;]",i:/:/,c:[{cN:"keyword",b:/\w+/},{b:/\s/,eW:!0,eE:!0,r:0,c:[e.ASM,e.QSM,e.CSSNM]}]},{cN:"selector-tag",b:c,r:0},{b:"{",e:"}",i:/\S/,c:[e.CBCM,t]}]}});hljs.registerLanguage("cpp",function(t){var e={cN:"keyword",b:"\\b[a-z\\d_]*_t\\b"},r={cN:"string",v:[{b:'(u8?|U)?L?"',e:'"',i:"\\n",c:[t.BE]},{b:'(u8?|U)?R"',e:'"',c:[t.BE]},{b:"'\\\\?.",e:"'",i:"."}]},s={cN:"number",v:[{b:"\\b(0b[01']+)"},{b:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{b:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],r:0},i={cN:"meta",b:/#\s*[a-z]+\b/,e:/$/,k:{"meta-keyword":"if else elif endif define undef warning error line pragma ifdef ifndef include"},c:[{b:/\\\n/,r:0},t.inherit(r,{cN:"meta-string"}),{cN:"meta-string",b:/<[^\n>]*>/,e:/$/,i:"\\n"},t.CLCM,t.CBCM]},a=t.IR+"\\s*\\(",c={keyword:"int float while private char catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignof constexpr decltype noexcept static_assert thread_local restrict _Bool complex _Complex _Imaginary atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and or not",built_in:"std string cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr",literal:"true false nullptr NULL"},n=[e,t.CLCM,t.CBCM,s,r];return{aliases:["c","cc","h","c++","h++","hpp"],k:c,i:"",k:c,c:["self",e]},{b:t.IR+"::",k:c},{v:[{b:/=/,e:/;/},{b:/\(/,e:/\)/},{bK:"new throw return else",e:/;/}],k:c,c:n.concat([{b:/\(/,e:/\)/,k:c,c:n.concat(["self"]),r:0}]),r:0},{cN:"function",b:"("+t.IR+"[\\*&\\s]+)+"+a,rB:!0,e:/[{;=]/,eE:!0,k:c,i:/[^\w\s\*&]/,c:[{b:a,rB:!0,c:[t.TM],r:0},{cN:"params",b:/\(/,e:/\)/,k:c,r:0,c:[t.CLCM,t.CBCM,r,s,e]},t.CLCM,t.CBCM,i]},{cN:"class",bK:"class struct",e:/[{;:]/,c:[{b://,c:["self"]},t.TM]}]),exports:{preprocessor:i,strings:r,k:c}}});hljs.registerLanguage("http",function(e){var t="HTTP/[0-9\\.]+";return{aliases:["https"],i:"\\S",c:[{b:"^"+t,e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{b:"^[A-Z]+ (.*?) "+t+"$",rB:!0,e:"$",c:[{cN:"string",b:" ",e:" ",eB:!0,eE:!0},{b:t},{cN:"keyword",b:"[A-Z]+"}]},{cN:"attribute",b:"^\\w",e:": ",eE:!0,i:"\\n|\\s|=",starts:{e:"$",r:0}},{b:"\\n\\n",starts:{sL:[],eW:!0}}]}});hljs.registerLanguage("json",function(e){var i={literal:"true false null"},n=[e.QSM,e.CNM],r={e:",",eW:!0,eE:!0,c:n,k:i},t={b:"{",e:"}",c:[{cN:"attr",b:/"/,e:/"/,c:[e.BE],i:"\\n"},e.inherit(r,{b:/:/})],i:"\\S"},c={b:"\\[",e:"\\]",c:[e.inherit(r)],i:"\\S"};return n.splice(n.length,0,t,c),{c:n,k:i,i:"\\S"}}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/agate.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/agate.css deleted file mode 100644 index 8d64547c58..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/agate.css +++ /dev/null @@ -1,108 +0,0 @@ -/*! - * Agate by Taufik Nurrohman - * ---------------------------------------------------- - * - * #ade5fc - * #a2fca2 - * #c6b4f0 - * #d36363 - * #fcc28c - * #fc9b9b - * #ffa - * #fff - * #333 - * #62c8f3 - * #888 - * - */ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #333; - color: white; -} - -.hljs-name, -.hljs-strong { - font-weight: bold; -} - -.hljs-code, -.hljs-emphasis { - font-style: italic; -} - -.hljs-tag { - color: #62c8f3; -} - -.hljs-variable, -.hljs-template-variable, -.hljs-selector-id, -.hljs-selector-class { - color: #ade5fc; -} - -.hljs-string, -.hljs-bullet { - color: #a2fca2; -} - -.hljs-type, -.hljs-title, -.hljs-section, -.hljs-attribute, -.hljs-quote, -.hljs-built_in, -.hljs-builtin-name { - color: #ffa; -} - -.hljs-number, -.hljs-symbol, -.hljs-bullet { - color: #d36363; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-literal { - color: #fcc28c; -} - -.hljs-comment, -.hljs-deletion, -.hljs-code { - color: #888; -} - -.hljs-regexp, -.hljs-link { - color: #c6b4f0; -} - -.hljs-meta { - color: #fc9b9b; -} - -.hljs-deletion { - background-color: #fc9b9b; - color: #333; -} - -.hljs-addition { - background-color: #a2fca2; - color: #333; -} - -.hljs a { - color: inherit; -} - -.hljs a:focus, -.hljs a:hover { - color: inherit; - text-decoration: underline; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/androidstudio.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/androidstudio.css deleted file mode 100644 index bc8e473b59..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/androidstudio.css +++ /dev/null @@ -1,66 +0,0 @@ -/* -Date: 24 Fev 2015 -Author: Pedro Oliveira -*/ - -.hljs { - color: #a9b7c6; - background: #282b2e; - display: block; - overflow-x: auto; - padding: 0.5em; -} - -.hljs-number, -.hljs-literal, -.hljs-symbol, -.hljs-bullet { - color: #6897BB; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-deletion { - color: #cc7832; -} - -.hljs-variable, -.hljs-template-variable, -.hljs-link { - color: #629755; -} - -.hljs-comment, -.hljs-quote { - color: #808080; -} - -.hljs-meta { - color: #bbb529; -} - -.hljs-string, -.hljs-attribute, -.hljs-addition { - color: #6A8759; -} - -.hljs-section, -.hljs-title, -.hljs-type { - color: #ffc66d; -} - -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #e8bf6a; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/arduino-light.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/arduino-light.css deleted file mode 100644 index 4b8b7fd3c9..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/arduino-light.css +++ /dev/null @@ -1,88 +0,0 @@ -/* - -Arduino® Light Theme - Stefania Mellai - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #FFFFFF; -} - -.hljs, -.hljs-subst { - color: #434f54; -} - -.hljs-keyword, -.hljs-attribute, -.hljs-selector-tag, -.hljs-doctag, -.hljs-name { - color: #00979D; -} - -.hljs-built_in, -.hljs-literal, -.hljs-bullet, -.hljs-code, -.hljs-addition { - color: #D35400; -} - -.hljs-regexp, -.hljs-symbol, -.hljs-variable, -.hljs-template-variable, -.hljs-link, -.hljs-selector-attr, -.hljs-selector-pseudo { - color: #00979D; -} - -.hljs-type, -.hljs-string, -.hljs-selector-id, -.hljs-selector-class, -.hljs-quote, -.hljs-template-tag, -.hljs-deletion { - color: #005C5F; -} - -.hljs-title, -.hljs-section { - color: #880000; - font-weight: bold; -} - -.hljs-comment { - color: rgba(149,165,166,.8); -} - -.hljs-meta-keyword { - color: #728E00; -} - -.hljs-meta { - color: #728E00; - color: #434f54; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} - -.hljs-function { - color: #728E00; -} - -.hljs-number { - color: #8A7B52; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/arta.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/arta.css deleted file mode 100644 index 75ef3a9e59..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/arta.css +++ /dev/null @@ -1,73 +0,0 @@ -/* -Date: 17.V.2011 -Author: pumbur -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #222; -} - -.hljs, -.hljs-subst { - color: #aaa; -} - -.hljs-section { - color: #fff; -} - -.hljs-comment, -.hljs-quote, -.hljs-meta { - color: #444; -} - -.hljs-string, -.hljs-symbol, -.hljs-bullet, -.hljs-regexp { - color: #ffcc33; -} - -.hljs-number, -.hljs-addition { - color: #00cc66; -} - -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-template-variable, -.hljs-attribute, -.hljs-link { - color: #32aaee; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #6644aa; -} - -.hljs-title, -.hljs-variable, -.hljs-deletion, -.hljs-template-tag { - color: #bb1166; -} - -.hljs-section, -.hljs-doctag, -.hljs-strong { - font-weight: bold; -} - -.hljs-emphasis { - font-style: italic; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/ascetic.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/ascetic.css deleted file mode 100644 index 48397e889d..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/ascetic.css +++ /dev/null @@ -1,45 +0,0 @@ -/* - -Original style from softwaremaniacs.org (c) Ivan Sagalaev - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: white; - color: black; -} - -.hljs-string, -.hljs-variable, -.hljs-template-variable, -.hljs-symbol, -.hljs-bullet, -.hljs-section, -.hljs-addition, -.hljs-attribute, -.hljs-link { - color: #888; -} - -.hljs-comment, -.hljs-quote, -.hljs-meta, -.hljs-deletion { - color: #ccc; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-section, -.hljs-name, -.hljs-type, -.hljs-strong { - font-weight: bold; -} - -.hljs-emphasis { - font-style: italic; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-cave-dark.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-cave-dark.css deleted file mode 100644 index 65428f3b12..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-cave-dark.css +++ /dev/null @@ -1,83 +0,0 @@ -/* Base16 Atelier Cave Dark - Theme */ -/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/cave) */ -/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ - -/* Atelier-Cave Comment */ -.hljs-comment, -.hljs-quote { - color: #7e7887; -} - -/* Atelier-Cave Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-regexp, -.hljs-link, -.hljs-tag, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #be4678; -} - -/* Atelier-Cave Orange */ -.hljs-number, -.hljs-meta, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #aa573c; -} - -/* Atelier-Cave Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet { - color: #2a9292; -} - -/* Atelier-Cave Blue */ -.hljs-title, -.hljs-section { - color: #576ddb; -} - -/* Atelier-Cave Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #955ae7; -} - -.hljs-deletion, -.hljs-addition { - color: #19171c; - display: inline-block; - width: 100%; -} - -.hljs-deletion { - background-color: #be4678; -} - -.hljs-addition { - background-color: #2a9292; -} - -.hljs { - display: block; - overflow-x: auto; - background: #19171c; - color: #8b8792; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-cave-light.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-cave-light.css deleted file mode 100644 index b419f9fd8f..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-cave-light.css +++ /dev/null @@ -1,85 +0,0 @@ -/* Base16 Atelier Cave Light - Theme */ -/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/cave) */ -/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ - -/* Atelier-Cave Comment */ -.hljs-comment, -.hljs-quote { - color: #655f6d; -} - -/* Atelier-Cave Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-tag, -.hljs-name, -.hljs-regexp, -.hljs-link, -.hljs-name, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #be4678; -} - -/* Atelier-Cave Orange */ -.hljs-number, -.hljs-meta, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #aa573c; -} - -/* Atelier-Cave Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet { - color: #2a9292; -} - -/* Atelier-Cave Blue */ -.hljs-title, -.hljs-section { - color: #576ddb; -} - -/* Atelier-Cave Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #955ae7; -} - -.hljs-deletion, -.hljs-addition { - color: #19171c; - display: inline-block; - width: 100%; -} - -.hljs-deletion { - background-color: #be4678; -} - -.hljs-addition { - background-color: #2a9292; -} - -.hljs { - display: block; - overflow-x: auto; - background: #efecf4; - color: #585260; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-dune-dark.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-dune-dark.css deleted file mode 100644 index 1684f5225a..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-dune-dark.css +++ /dev/null @@ -1,69 +0,0 @@ -/* Base16 Atelier Dune Dark - Theme */ -/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) */ -/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ - -/* Atelier-Dune Comment */ -.hljs-comment, -.hljs-quote { - color: #999580; -} - -/* Atelier-Dune Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-tag, -.hljs-name, -.hljs-regexp, -.hljs-link, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #d73737; -} - -/* Atelier-Dune Orange */ -.hljs-number, -.hljs-meta, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #b65611; -} - -/* Atelier-Dune Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet { - color: #60ac39; -} - -/* Atelier-Dune Blue */ -.hljs-title, -.hljs-section { - color: #6684e1; -} - -/* Atelier-Dune Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #b854d4; -} - -.hljs { - display: block; - overflow-x: auto; - background: #20201d; - color: #a6a28c; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-dune-light.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-dune-light.css deleted file mode 100644 index 547719de82..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-dune-light.css +++ /dev/null @@ -1,69 +0,0 @@ -/* Base16 Atelier Dune Light - Theme */ -/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) */ -/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ - -/* Atelier-Dune Comment */ -.hljs-comment, -.hljs-quote { - color: #7d7a68; -} - -/* Atelier-Dune Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-tag, -.hljs-name, -.hljs-regexp, -.hljs-link, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #d73737; -} - -/* Atelier-Dune Orange */ -.hljs-number, -.hljs-meta, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #b65611; -} - -/* Atelier-Dune Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet { - color: #60ac39; -} - -/* Atelier-Dune Blue */ -.hljs-title, -.hljs-section { - color: #6684e1; -} - -/* Atelier-Dune Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #b854d4; -} - -.hljs { - display: block; - overflow-x: auto; - background: #fefbec; - color: #6e6b5e; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-estuary-dark.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-estuary-dark.css deleted file mode 100644 index a5e507187e..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-estuary-dark.css +++ /dev/null @@ -1,84 +0,0 @@ -/* Base16 Atelier Estuary Dark - Theme */ -/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/estuary) */ -/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ - -/* Atelier-Estuary Comment */ -.hljs-comment, -.hljs-quote { - color: #878573; -} - -/* Atelier-Estuary Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-tag, -.hljs-name, -.hljs-regexp, -.hljs-link, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #ba6236; -} - -/* Atelier-Estuary Orange */ -.hljs-number, -.hljs-meta, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #ae7313; -} - -/* Atelier-Estuary Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet { - color: #7d9726; -} - -/* Atelier-Estuary Blue */ -.hljs-title, -.hljs-section { - color: #36a166; -} - -/* Atelier-Estuary Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #5f9182; -} - -.hljs-deletion, -.hljs-addition { - color: #22221b; - display: inline-block; - width: 100%; -} - -.hljs-deletion { - background-color: #ba6236; -} - -.hljs-addition { - background-color: #7d9726; -} - -.hljs { - display: block; - overflow-x: auto; - background: #22221b; - color: #929181; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-estuary-light.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-estuary-light.css deleted file mode 100644 index 1daee5d985..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-estuary-light.css +++ /dev/null @@ -1,84 +0,0 @@ -/* Base16 Atelier Estuary Light - Theme */ -/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/estuary) */ -/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ - -/* Atelier-Estuary Comment */ -.hljs-comment, -.hljs-quote { - color: #6c6b5a; -} - -/* Atelier-Estuary Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-tag, -.hljs-name, -.hljs-regexp, -.hljs-link, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #ba6236; -} - -/* Atelier-Estuary Orange */ -.hljs-number, -.hljs-meta, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #ae7313; -} - -/* Atelier-Estuary Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet { - color: #7d9726; -} - -/* Atelier-Estuary Blue */ -.hljs-title, -.hljs-section { - color: #36a166; -} - -/* Atelier-Estuary Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #5f9182; -} - -.hljs-deletion, -.hljs-addition { - color: #22221b; - display: inline-block; - width: 100%; -} - -.hljs-deletion { - background-color: #ba6236; -} - -.hljs-addition { - background-color: #7d9726; -} - -.hljs { - display: block; - overflow-x: auto; - background: #f4f3ec; - color: #5f5e4e; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-forest-dark.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-forest-dark.css deleted file mode 100644 index 0ef4fae317..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-forest-dark.css +++ /dev/null @@ -1,69 +0,0 @@ -/* Base16 Atelier Forest Dark - Theme */ -/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/forest) */ -/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ - -/* Atelier-Forest Comment */ -.hljs-comment, -.hljs-quote { - color: #9c9491; -} - -/* Atelier-Forest Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-tag, -.hljs-name, -.hljs-regexp, -.hljs-link, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #f22c40; -} - -/* Atelier-Forest Orange */ -.hljs-number, -.hljs-meta, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #df5320; -} - -/* Atelier-Forest Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet { - color: #7b9726; -} - -/* Atelier-Forest Blue */ -.hljs-title, -.hljs-section { - color: #407ee7; -} - -/* Atelier-Forest Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #6666ea; -} - -.hljs { - display: block; - overflow-x: auto; - background: #1b1918; - color: #a8a19f; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-forest-light.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-forest-light.css deleted file mode 100644 index bbedde18a0..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-forest-light.css +++ /dev/null @@ -1,69 +0,0 @@ -/* Base16 Atelier Forest Light - Theme */ -/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/forest) */ -/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ - -/* Atelier-Forest Comment */ -.hljs-comment, -.hljs-quote { - color: #766e6b; -} - -/* Atelier-Forest Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-tag, -.hljs-name, -.hljs-regexp, -.hljs-link, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #f22c40; -} - -/* Atelier-Forest Orange */ -.hljs-number, -.hljs-meta, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #df5320; -} - -/* Atelier-Forest Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet { - color: #7b9726; -} - -/* Atelier-Forest Blue */ -.hljs-title, -.hljs-section { - color: #407ee7; -} - -/* Atelier-Forest Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #6666ea; -} - -.hljs { - display: block; - overflow-x: auto; - background: #f1efee; - color: #68615e; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-heath-dark.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-heath-dark.css deleted file mode 100644 index fe01ff721b..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-heath-dark.css +++ /dev/null @@ -1,69 +0,0 @@ -/* Base16 Atelier Heath Dark - Theme */ -/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/heath) */ -/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ - -/* Atelier-Heath Comment */ -.hljs-comment, -.hljs-quote { - color: #9e8f9e; -} - -/* Atelier-Heath Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-tag, -.hljs-name, -.hljs-regexp, -.hljs-link, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #ca402b; -} - -/* Atelier-Heath Orange */ -.hljs-number, -.hljs-meta, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #a65926; -} - -/* Atelier-Heath Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet { - color: #918b3b; -} - -/* Atelier-Heath Blue */ -.hljs-title, -.hljs-section { - color: #516aec; -} - -/* Atelier-Heath Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #7b59c0; -} - -.hljs { - display: block; - overflow-x: auto; - background: #1b181b; - color: #ab9bab; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-heath-light.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-heath-light.css deleted file mode 100644 index ee43786d12..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-heath-light.css +++ /dev/null @@ -1,69 +0,0 @@ -/* Base16 Atelier Heath Light - Theme */ -/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/heath) */ -/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ - -/* Atelier-Heath Comment */ -.hljs-comment, -.hljs-quote { - color: #776977; -} - -/* Atelier-Heath Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-tag, -.hljs-name, -.hljs-regexp, -.hljs-link, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #ca402b; -} - -/* Atelier-Heath Orange */ -.hljs-number, -.hljs-meta, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #a65926; -} - -/* Atelier-Heath Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet { - color: #918b3b; -} - -/* Atelier-Heath Blue */ -.hljs-title, -.hljs-section { - color: #516aec; -} - -/* Atelier-Heath Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #7b59c0; -} - -.hljs { - display: block; - overflow-x: auto; - background: #f7f3f7; - color: #695d69; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-lakeside-dark.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-lakeside-dark.css deleted file mode 100644 index a937d3bf5f..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-lakeside-dark.css +++ /dev/null @@ -1,69 +0,0 @@ -/* Base16 Atelier Lakeside Dark - Theme */ -/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/lakeside) */ -/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ - -/* Atelier-Lakeside Comment */ -.hljs-comment, -.hljs-quote { - color: #7195a8; -} - -/* Atelier-Lakeside Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-tag, -.hljs-name, -.hljs-regexp, -.hljs-link, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #d22d72; -} - -/* Atelier-Lakeside Orange */ -.hljs-number, -.hljs-meta, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #935c25; -} - -/* Atelier-Lakeside Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet { - color: #568c3b; -} - -/* Atelier-Lakeside Blue */ -.hljs-title, -.hljs-section { - color: #257fad; -} - -/* Atelier-Lakeside Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #6b6bb8; -} - -.hljs { - display: block; - overflow-x: auto; - background: #161b1d; - color: #7ea2b4; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-lakeside-light.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-lakeside-light.css deleted file mode 100644 index 6c7e8f9ef2..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-lakeside-light.css +++ /dev/null @@ -1,69 +0,0 @@ -/* Base16 Atelier Lakeside Light - Theme */ -/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/lakeside) */ -/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ - -/* Atelier-Lakeside Comment */ -.hljs-comment, -.hljs-quote { - color: #5a7b8c; -} - -/* Atelier-Lakeside Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-tag, -.hljs-name, -.hljs-regexp, -.hljs-link, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #d22d72; -} - -/* Atelier-Lakeside Orange */ -.hljs-number, -.hljs-meta, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #935c25; -} - -/* Atelier-Lakeside Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet { - color: #568c3b; -} - -/* Atelier-Lakeside Blue */ -.hljs-title, -.hljs-section { - color: #257fad; -} - -/* Atelier-Lakeside Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #6b6bb8; -} - -.hljs { - display: block; - overflow-x: auto; - background: #ebf8ff; - color: #516d7b; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-plateau-dark.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-plateau-dark.css deleted file mode 100644 index 3bb052693c..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-plateau-dark.css +++ /dev/null @@ -1,84 +0,0 @@ -/* Base16 Atelier Plateau Dark - Theme */ -/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/plateau) */ -/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ - -/* Atelier-Plateau Comment */ -.hljs-comment, -.hljs-quote { - color: #7e7777; -} - -/* Atelier-Plateau Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-tag, -.hljs-name, -.hljs-regexp, -.hljs-link, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #ca4949; -} - -/* Atelier-Plateau Orange */ -.hljs-number, -.hljs-meta, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #b45a3c; -} - -/* Atelier-Plateau Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet { - color: #4b8b8b; -} - -/* Atelier-Plateau Blue */ -.hljs-title, -.hljs-section { - color: #7272ca; -} - -/* Atelier-Plateau Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #8464c4; -} - -.hljs-deletion, -.hljs-addition { - color: #1b1818; - display: inline-block; - width: 100%; -} - -.hljs-deletion { - background-color: #ca4949; -} - -.hljs-addition { - background-color: #4b8b8b; -} - -.hljs { - display: block; - overflow-x: auto; - background: #1b1818; - color: #8a8585; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-plateau-light.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-plateau-light.css deleted file mode 100644 index 5f0222bec1..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-plateau-light.css +++ /dev/null @@ -1,84 +0,0 @@ -/* Base16 Atelier Plateau Light - Theme */ -/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/plateau) */ -/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ - -/* Atelier-Plateau Comment */ -.hljs-comment, -.hljs-quote { - color: #655d5d; -} - -/* Atelier-Plateau Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-tag, -.hljs-name, -.hljs-regexp, -.hljs-link, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #ca4949; -} - -/* Atelier-Plateau Orange */ -.hljs-number, -.hljs-meta, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #b45a3c; -} - -/* Atelier-Plateau Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet { - color: #4b8b8b; -} - -/* Atelier-Plateau Blue */ -.hljs-title, -.hljs-section { - color: #7272ca; -} - -/* Atelier-Plateau Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #8464c4; -} - -.hljs-deletion, -.hljs-addition { - color: #1b1818; - display: inline-block; - width: 100%; -} - -.hljs-deletion { - background-color: #ca4949; -} - -.hljs-addition { - background-color: #4b8b8b; -} - -.hljs { - display: block; - overflow-x: auto; - background: #f4ecec; - color: #585050; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-savanna-dark.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-savanna-dark.css deleted file mode 100644 index 38f831431c..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-savanna-dark.css +++ /dev/null @@ -1,84 +0,0 @@ -/* Base16 Atelier Savanna Dark - Theme */ -/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/savanna) */ -/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ - -/* Atelier-Savanna Comment */ -.hljs-comment, -.hljs-quote { - color: #78877d; -} - -/* Atelier-Savanna Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-tag, -.hljs-name, -.hljs-regexp, -.hljs-link, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #b16139; -} - -/* Atelier-Savanna Orange */ -.hljs-number, -.hljs-meta, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #9f713c; -} - -/* Atelier-Savanna Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet { - color: #489963; -} - -/* Atelier-Savanna Blue */ -.hljs-title, -.hljs-section { - color: #478c90; -} - -/* Atelier-Savanna Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #55859b; -} - -.hljs-deletion, -.hljs-addition { - color: #171c19; - display: inline-block; - width: 100%; -} - -.hljs-deletion { - background-color: #b16139; -} - -.hljs-addition { - background-color: #489963; -} - -.hljs { - display: block; - overflow-x: auto; - background: #171c19; - color: #87928a; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-savanna-light.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-savanna-light.css deleted file mode 100644 index 1ccd7c6858..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-savanna-light.css +++ /dev/null @@ -1,84 +0,0 @@ -/* Base16 Atelier Savanna Light - Theme */ -/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/savanna) */ -/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ - -/* Atelier-Savanna Comment */ -.hljs-comment, -.hljs-quote { - color: #5f6d64; -} - -/* Atelier-Savanna Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-tag, -.hljs-name, -.hljs-regexp, -.hljs-link, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #b16139; -} - -/* Atelier-Savanna Orange */ -.hljs-number, -.hljs-meta, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #9f713c; -} - -/* Atelier-Savanna Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet { - color: #489963; -} - -/* Atelier-Savanna Blue */ -.hljs-title, -.hljs-section { - color: #478c90; -} - -/* Atelier-Savanna Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #55859b; -} - -.hljs-deletion, -.hljs-addition { - color: #171c19; - display: inline-block; - width: 100%; -} - -.hljs-deletion { - background-color: #b16139; -} - -.hljs-addition { - background-color: #489963; -} - -.hljs { - display: block; - overflow-x: auto; - background: #ecf4ee; - color: #526057; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-seaside-dark.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-seaside-dark.css deleted file mode 100644 index df29949c69..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-seaside-dark.css +++ /dev/null @@ -1,69 +0,0 @@ -/* Base16 Atelier Seaside Dark - Theme */ -/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/seaside) */ -/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ - -/* Atelier-Seaside Comment */ -.hljs-comment, -.hljs-quote { - color: #809980; -} - -/* Atelier-Seaside Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-tag, -.hljs-name, -.hljs-regexp, -.hljs-link, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #e6193c; -} - -/* Atelier-Seaside Orange */ -.hljs-number, -.hljs-meta, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #87711d; -} - -/* Atelier-Seaside Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet { - color: #29a329; -} - -/* Atelier-Seaside Blue */ -.hljs-title, -.hljs-section { - color: #3d62f5; -} - -/* Atelier-Seaside Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #ad2bee; -} - -.hljs { - display: block; - overflow-x: auto; - background: #131513; - color: #8ca68c; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-seaside-light.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-seaside-light.css deleted file mode 100644 index 9d960f29f3..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-seaside-light.css +++ /dev/null @@ -1,69 +0,0 @@ -/* Base16 Atelier Seaside Light - Theme */ -/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/seaside) */ -/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ - -/* Atelier-Seaside Comment */ -.hljs-comment, -.hljs-quote { - color: #687d68; -} - -/* Atelier-Seaside Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-tag, -.hljs-name, -.hljs-regexp, -.hljs-link, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #e6193c; -} - -/* Atelier-Seaside Orange */ -.hljs-number, -.hljs-meta, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #87711d; -} - -/* Atelier-Seaside Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet { - color: #29a329; -} - -/* Atelier-Seaside Blue */ -.hljs-title, -.hljs-section { - color: #3d62f5; -} - -/* Atelier-Seaside Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #ad2bee; -} - -.hljs { - display: block; - overflow-x: auto; - background: #f4fbf4; - color: #5e6e5e; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-sulphurpool-dark.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-sulphurpool-dark.css deleted file mode 100644 index c2ab7938d8..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-sulphurpool-dark.css +++ /dev/null @@ -1,69 +0,0 @@ -/* Base16 Atelier Sulphurpool Dark - Theme */ -/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/sulphurpool) */ -/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ - -/* Atelier-Sulphurpool Comment */ -.hljs-comment, -.hljs-quote { - color: #898ea4; -} - -/* Atelier-Sulphurpool Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-tag, -.hljs-name, -.hljs-regexp, -.hljs-link, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #c94922; -} - -/* Atelier-Sulphurpool Orange */ -.hljs-number, -.hljs-meta, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #c76b29; -} - -/* Atelier-Sulphurpool Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet { - color: #ac9739; -} - -/* Atelier-Sulphurpool Blue */ -.hljs-title, -.hljs-section { - color: #3d8fd1; -} - -/* Atelier-Sulphurpool Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #6679cc; -} - -.hljs { - display: block; - overflow-x: auto; - background: #202746; - color: #979db4; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-sulphurpool-light.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-sulphurpool-light.css deleted file mode 100644 index 96c47d0860..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atelier-sulphurpool-light.css +++ /dev/null @@ -1,69 +0,0 @@ -/* Base16 Atelier Sulphurpool Light - Theme */ -/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/sulphurpool) */ -/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ - -/* Atelier-Sulphurpool Comment */ -.hljs-comment, -.hljs-quote { - color: #6b7394; -} - -/* Atelier-Sulphurpool Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-tag, -.hljs-name, -.hljs-regexp, -.hljs-link, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #c94922; -} - -/* Atelier-Sulphurpool Orange */ -.hljs-number, -.hljs-meta, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #c76b29; -} - -/* Atelier-Sulphurpool Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet { - color: #ac9739; -} - -/* Atelier-Sulphurpool Blue */ -.hljs-title, -.hljs-section { - color: #3d8fd1; -} - -/* Atelier-Sulphurpool Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #6679cc; -} - -.hljs { - display: block; - overflow-x: auto; - background: #f5f7ff; - color: #5e6687; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atom-one-dark.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atom-one-dark.css deleted file mode 100644 index 1616aafe31..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atom-one-dark.css +++ /dev/null @@ -1,96 +0,0 @@ -/* - -Atom One Dark by Daniel Gamage -Original One Dark Syntax theme from https://github.com/atom/one-dark-syntax - -base: #282c34 -mono-1: #abb2bf -mono-2: #818896 -mono-3: #5c6370 -hue-1: #56b6c2 -hue-2: #61aeee -hue-3: #c678dd -hue-4: #98c379 -hue-5: #e06c75 -hue-5-2: #be5046 -hue-6: #d19a66 -hue-6-2: #e6c07b - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - color: #abb2bf; - background: #282c34; -} - -.hljs-comment, -.hljs-quote { - color: #5c6370; - font-style: italic; -} - -.hljs-doctag, -.hljs-keyword, -.hljs-formula { - color: #c678dd; -} - -.hljs-section, -.hljs-name, -.hljs-selector-tag, -.hljs-deletion, -.hljs-subst { - color: #e06c75; -} - -.hljs-literal { - color: #56b6c2; -} - -.hljs-string, -.hljs-regexp, -.hljs-addition, -.hljs-attribute, -.hljs-meta-string { - color: #98c379; -} - -.hljs-built_in, -.hljs-class .hljs-title { - color: #e6c07b; -} - -.hljs-attr, -.hljs-variable, -.hljs-template-variable, -.hljs-type, -.hljs-selector-class, -.hljs-selector-attr, -.hljs-selector-pseudo, -.hljs-number { - color: #d19a66; -} - -.hljs-symbol, -.hljs-bullet, -.hljs-link, -.hljs-meta, -.hljs-selector-id, -.hljs-title { - color: #61aeee; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} - -.hljs-link { - text-decoration: underline; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atom-one-light.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atom-one-light.css deleted file mode 100644 index d5bd1d2a9a..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/atom-one-light.css +++ /dev/null @@ -1,96 +0,0 @@ -/* - -Atom One Light by Daniel Gamage -Original One Light Syntax theme from https://github.com/atom/one-light-syntax - -base: #fafafa -mono-1: #383a42 -mono-2: #686b77 -mono-3: #a0a1a7 -hue-1: #0184bb -hue-2: #4078f2 -hue-3: #a626a4 -hue-4: #50a14f -hue-5: #e45649 -hue-5-2: #c91243 -hue-6: #986801 -hue-6-2: #c18401 - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - color: #383a42; - background: #fafafa; -} - -.hljs-comment, -.hljs-quote { - color: #a0a1a7; - font-style: italic; -} - -.hljs-doctag, -.hljs-keyword, -.hljs-formula { - color: #a626a4; -} - -.hljs-section, -.hljs-name, -.hljs-selector-tag, -.hljs-deletion, -.hljs-subst { - color: #e45649; -} - -.hljs-literal { - color: #0184bb; -} - -.hljs-string, -.hljs-regexp, -.hljs-addition, -.hljs-attribute, -.hljs-meta-string { - color: #50a14f; -} - -.hljs-built_in, -.hljs-class .hljs-title { - color: #c18401; -} - -.hljs-attr, -.hljs-variable, -.hljs-template-variable, -.hljs-type, -.hljs-selector-class, -.hljs-selector-attr, -.hljs-selector-pseudo, -.hljs-number { - color: #986801; -} - -.hljs-symbol, -.hljs-bullet, -.hljs-link, -.hljs-meta, -.hljs-selector-id, -.hljs-title { - color: #4078f2; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} - -.hljs-link { - text-decoration: underline; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/brown-paper.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/brown-paper.css deleted file mode 100644 index f0197b924c..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/brown-paper.css +++ /dev/null @@ -1,64 +0,0 @@ -/* - -Brown Paper style from goldblog.com.ua (c) Zaripov Yura - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background:#b7a68e url(./brown-papersq.png); -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-literal { - color:#005599; - font-weight:bold; -} - -.hljs, -.hljs-subst { - color: #363c69; -} - -.hljs-string, -.hljs-title, -.hljs-section, -.hljs-type, -.hljs-attribute, -.hljs-symbol, -.hljs-bullet, -.hljs-built_in, -.hljs-addition, -.hljs-variable, -.hljs-template-tag, -.hljs-template-variable, -.hljs-link, -.hljs-name { - color: #2c009f; -} - -.hljs-comment, -.hljs-quote, -.hljs-meta, -.hljs-deletion { - color: #802022; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-literal, -.hljs-doctag, -.hljs-title, -.hljs-section, -.hljs-type, -.hljs-name, -.hljs-strong { - font-weight: bold; -} - -.hljs-emphasis { - font-style: italic; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/brown-papersq.png b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/brown-papersq.png deleted file mode 100644 index 3813903dbf..0000000000 Binary files a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/brown-papersq.png and /dev/null differ diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/codepen-embed.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/codepen-embed.css deleted file mode 100644 index 195c4a0784..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/codepen-embed.css +++ /dev/null @@ -1,60 +0,0 @@ -/* - codepen.io Embed Theme - Author: Justin Perry - Original theme - https://github.com/chriskempson/tomorrow-theme -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #222; - color: #fff; -} - -.hljs-comment, -.hljs-quote { - color: #777; -} - -.hljs-variable, -.hljs-template-variable, -.hljs-tag, -.hljs-regexp, -.hljs-meta, -.hljs-number, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-params, -.hljs-symbol, -.hljs-bullet, -.hljs-link, -.hljs-deletion { - color: #ab875d; -} - -.hljs-section, -.hljs-title, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class, -.hljs-type, -.hljs-attribute { - color: #9b869b; -} - -.hljs-string, -.hljs-keyword, -.hljs-selector-tag, -.hljs-addition { - color: #8f9c6c; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/color-brewer.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/color-brewer.css deleted file mode 100644 index 7934d986a7..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/color-brewer.css +++ /dev/null @@ -1,71 +0,0 @@ -/* - -Colorbrewer theme -Original: https://github.com/mbostock/colorbrewer-theme (c) Mike Bostock -Ported by Fabrício Tavares de Oliveira - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #fff; -} - -.hljs, -.hljs-subst { - color: #000; -} - -.hljs-string, -.hljs-meta, -.hljs-symbol, -.hljs-template-tag, -.hljs-template-variable, -.hljs-addition { - color: #756bb1; -} - -.hljs-comment, -.hljs-quote { - color: #636363; -} - -.hljs-number, -.hljs-regexp, -.hljs-literal, -.hljs-bullet, -.hljs-link { - color: #31a354; -} - -.hljs-deletion, -.hljs-variable { - color: #88f; -} - - - -.hljs-keyword, -.hljs-selector-tag, -.hljs-title, -.hljs-section, -.hljs-built_in, -.hljs-doctag, -.hljs-type, -.hljs-tag, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class, -.hljs-strong { - color: #3182bd; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-attribute { - color: #e6550d; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/darcula.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/darcula.css deleted file mode 100644 index be182d0b50..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/darcula.css +++ /dev/null @@ -1,77 +0,0 @@ -/* - -Darcula color scheme from the JetBrains family of IDEs - -*/ - - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #2b2b2b; -} - -.hljs { - color: #bababa; -} - -.hljs-strong, -.hljs-emphasis { - color: #a8a8a2; -} - -.hljs-bullet, -.hljs-quote, -.hljs-link, -.hljs-number, -.hljs-regexp, -.hljs-literal { - color: #6896ba; -} - -.hljs-code, -.hljs-selector-class { - color: #a6e22e; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-section, -.hljs-attribute, -.hljs-name, -.hljs-variable { - color: #cb7832; -} - -.hljs-params { - color: #b9b9b9; -} - -.hljs-string { - color: #6a8759; -} - -.hljs-subst, -.hljs-type, -.hljs-built_in, -.hljs-builtin-name, -.hljs-symbol, -.hljs-selector-id, -.hljs-selector-attr, -.hljs-selector-pseudo, -.hljs-template-tag, -.hljs-template-variable, -.hljs-addition { - color: #e0c46c; -} - -.hljs-comment, -.hljs-deletion, -.hljs-meta { - color: #7f7f7f; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/dark.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/dark.css deleted file mode 100644 index b4724f5f50..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/dark.css +++ /dev/null @@ -1,63 +0,0 @@ -/* - -Dark style from softwaremaniacs.org (c) Ivan Sagalaev - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #444; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-literal, -.hljs-section, -.hljs-link { - color: white; -} - -.hljs, -.hljs-subst { - color: #ddd; -} - -.hljs-string, -.hljs-title, -.hljs-name, -.hljs-type, -.hljs-attribute, -.hljs-symbol, -.hljs-bullet, -.hljs-built_in, -.hljs-addition, -.hljs-variable, -.hljs-template-tag, -.hljs-template-variable { - color: #d88; -} - -.hljs-comment, -.hljs-quote, -.hljs-deletion, -.hljs-meta { - color: #777; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-literal, -.hljs-title, -.hljs-section, -.hljs-doctag, -.hljs-type, -.hljs-name, -.hljs-strong { - font-weight: bold; -} - -.hljs-emphasis { - font-style: italic; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/darkula.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/darkula.css deleted file mode 100644 index f4646c3c5d..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/darkula.css +++ /dev/null @@ -1,6 +0,0 @@ -/* - Deprecated due to a typo in the name and left here for compatibility purpose only. - Please use darcula.css instead. -*/ - -@import url('darcula.css'); diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/default.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/default.css deleted file mode 100644 index f1bfade31e..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/default.css +++ /dev/null @@ -1,99 +0,0 @@ -/* - -Original highlight.js style (c) Ivan Sagalaev - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #F0F0F0; -} - - -/* Base color: saturation 0; */ - -.hljs, -.hljs-subst { - color: #444; -} - -.hljs-comment { - color: #888888; -} - -.hljs-keyword, -.hljs-attribute, -.hljs-selector-tag, -.hljs-meta-keyword, -.hljs-doctag, -.hljs-name { - font-weight: bold; -} - - -/* User color: hue: 0 */ - -.hljs-type, -.hljs-string, -.hljs-number, -.hljs-selector-id, -.hljs-selector-class, -.hljs-quote, -.hljs-template-tag, -.hljs-deletion { - color: #880000; -} - -.hljs-title, -.hljs-section { - color: #880000; - font-weight: bold; -} - -.hljs-regexp, -.hljs-symbol, -.hljs-variable, -.hljs-template-variable, -.hljs-link, -.hljs-selector-attr, -.hljs-selector-pseudo { - color: #BC6060; -} - - -/* Language color: hue: 90; */ - -.hljs-literal { - color: #78A960; -} - -.hljs-built_in, -.hljs-bullet, -.hljs-code, -.hljs-addition { - color: #397300; -} - - -/* Meta color: hue: 200 */ - -.hljs-meta { - color: #1f7199; -} - -.hljs-meta-string { - color: #4d99bf; -} - - -/* Misc effects */ - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/docco.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/docco.css deleted file mode 100644 index db366be372..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/docco.css +++ /dev/null @@ -1,97 +0,0 @@ -/* -Docco style used in http://jashkenas.github.com/docco/ converted by Simon Madine (@thingsinjars) -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - color: #000; - background: #f8f8ff; -} - -.hljs-comment, -.hljs-quote { - color: #408080; - font-style: italic; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-literal, -.hljs-subst { - color: #954121; -} - -.hljs-number { - color: #40a070; -} - -.hljs-string, -.hljs-doctag { - color: #219161; -} - -.hljs-selector-id, -.hljs-selector-class, -.hljs-section, -.hljs-type { - color: #19469d; -} - -.hljs-params { - color: #00f; -} - -.hljs-title { - color: #458; - font-weight: bold; -} - -.hljs-tag, -.hljs-name, -.hljs-attribute { - color: #000080; - font-weight: normal; -} - -.hljs-variable, -.hljs-template-variable { - color: #008080; -} - -.hljs-regexp, -.hljs-link { - color: #b68; -} - -.hljs-symbol, -.hljs-bullet { - color: #990073; -} - -.hljs-built_in, -.hljs-builtin-name { - color: #0086b3; -} - -.hljs-meta { - color: #999; - font-weight: bold; -} - -.hljs-deletion { - background: #fdd; -} - -.hljs-addition { - background: #dfd; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/dracula.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/dracula.css deleted file mode 100644 index d591db6801..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/dracula.css +++ /dev/null @@ -1,76 +0,0 @@ -/* - -Dracula Theme v1.2.0 - -https://github.com/zenorocha/dracula-theme - -Copyright 2015, All rights reserved - -Code licensed under the MIT license -http://zenorocha.mit-license.org - -@author Éverton Ribeiro -@author Zeno Rocha - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #282a36; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-literal, -.hljs-section, -.hljs-link { - color: #8be9fd; -} - -.hljs-function .hljs-keyword { - color: #ff79c6; -} - -.hljs, -.hljs-subst { - color: #f8f8f2; -} - -.hljs-string, -.hljs-title, -.hljs-name, -.hljs-type, -.hljs-attribute, -.hljs-symbol, -.hljs-bullet, -.hljs-addition, -.hljs-variable, -.hljs-template-tag, -.hljs-template-variable { - color: #f1fa8c; -} - -.hljs-comment, -.hljs-quote, -.hljs-deletion, -.hljs-meta { - color: #6272a4; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-literal, -.hljs-title, -.hljs-section, -.hljs-doctag, -.hljs-type, -.hljs-name, -.hljs-strong { - font-weight: bold; -} - -.hljs-emphasis { - font-style: italic; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/far.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/far.css deleted file mode 100644 index 2b3f87b562..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/far.css +++ /dev/null @@ -1,71 +0,0 @@ -/* - -FAR Style (c) MajestiC - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #000080; -} - -.hljs, -.hljs-subst { - color: #0ff; -} - -.hljs-string, -.hljs-attribute, -.hljs-symbol, -.hljs-bullet, -.hljs-built_in, -.hljs-builtin-name, -.hljs-template-tag, -.hljs-template-variable, -.hljs-addition { - color: #ff0; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-section, -.hljs-type, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class, -.hljs-variable { - color: #fff; -} - -.hljs-comment, -.hljs-quote, -.hljs-doctag, -.hljs-deletion { - color: #888; -} - -.hljs-number, -.hljs-regexp, -.hljs-literal, -.hljs-link { - color: #0f0; -} - -.hljs-meta { - color: #008080; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-title, -.hljs-section, -.hljs-name, -.hljs-strong { - font-weight: bold; -} - -.hljs-emphasis { - font-style: italic; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/foundation.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/foundation.css deleted file mode 100644 index f1fe64b377..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/foundation.css +++ /dev/null @@ -1,88 +0,0 @@ -/* -Description: Foundation 4 docs style for highlight.js -Author: Dan Allen -Website: http://foundation.zurb.com/docs/ -Version: 1.0 -Date: 2013-04-02 -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #eee; color: black; -} - -.hljs-link, -.hljs-emphasis, -.hljs-attribute, -.hljs-addition { - color: #070; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong, -.hljs-string, -.hljs-deletion { - color: #d14; -} - -.hljs-strong { - font-weight: bold; -} - -.hljs-quote, -.hljs-comment { - color: #998; - font-style: italic; -} - -.hljs-section, -.hljs-title { - color: #900; -} - -.hljs-class .hljs-title, -.hljs-type { - color: #458; -} - -.hljs-variable, -.hljs-template-variable { - color: #336699; -} - -.hljs-bullet { - color: #997700; -} - -.hljs-meta { - color: #3344bb; -} - -.hljs-code, -.hljs-number, -.hljs-literal, -.hljs-keyword, -.hljs-selector-tag { - color: #099; -} - -.hljs-regexp { - background-color: #fff0ff; - color: #880088; -} - -.hljs-symbol { - color: #990073; -} - -.hljs-tag, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #007700; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/github-gist.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/github-gist.css deleted file mode 100644 index 155f0b9160..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/github-gist.css +++ /dev/null @@ -1,71 +0,0 @@ -/** - * GitHub Gist Theme - * Author : Louis Barranqueiro - https://github.com/LouisBarranqueiro - */ - -.hljs { - display: block; - background: white; - padding: 0.5em; - color: #333333; - overflow-x: auto; -} - -.hljs-comment, -.hljs-meta { - color: #969896; -} - -.hljs-string, -.hljs-variable, -.hljs-template-variable, -.hljs-strong, -.hljs-emphasis, -.hljs-quote { - color: #df5000; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-type { - color: #a71d5d; -} - -.hljs-literal, -.hljs-symbol, -.hljs-bullet, -.hljs-attribute { - color: #0086b3; -} - -.hljs-section, -.hljs-name { - color: #63a35c; -} - -.hljs-tag { - color: #333333; -} - -.hljs-title, -.hljs-attr, -.hljs-selector-id, -.hljs-selector-class, -.hljs-selector-attr, -.hljs-selector-pseudo { - color: #795da3; -} - -.hljs-addition { - color: #55a532; - background-color: #eaffea; -} - -.hljs-deletion { - color: #bd2c00; - background-color: #ffecec; -} - -.hljs-link { - text-decoration: underline; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/github.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/github.css deleted file mode 100644 index 791932b87e..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/github.css +++ /dev/null @@ -1,99 +0,0 @@ -/* - -github.com style (c) Vasily Polovnyov - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - color: #333; - background: #f8f8f8; -} - -.hljs-comment, -.hljs-quote { - color: #998; - font-style: italic; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-subst { - color: #333; - font-weight: bold; -} - -.hljs-number, -.hljs-literal, -.hljs-variable, -.hljs-template-variable, -.hljs-tag .hljs-attr { - color: #008080; -} - -.hljs-string, -.hljs-doctag { - color: #d14; -} - -.hljs-title, -.hljs-section, -.hljs-selector-id { - color: #900; - font-weight: bold; -} - -.hljs-subst { - font-weight: normal; -} - -.hljs-type, -.hljs-class .hljs-title { - color: #458; - font-weight: bold; -} - -.hljs-tag, -.hljs-name, -.hljs-attribute { - color: #000080; - font-weight: normal; -} - -.hljs-regexp, -.hljs-link { - color: #009926; -} - -.hljs-symbol, -.hljs-bullet { - color: #990073; -} - -.hljs-built_in, -.hljs-builtin-name { - color: #0086b3; -} - -.hljs-meta { - color: #999; - font-weight: bold; -} - -.hljs-deletion { - background: #fdd; -} - -.hljs-addition { - background: #dfd; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/googlecode.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/googlecode.css deleted file mode 100644 index 884ad63538..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/googlecode.css +++ /dev/null @@ -1,89 +0,0 @@ -/* - -Google Code style (c) Aahan Krish - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: white; - color: black; -} - -.hljs-comment, -.hljs-quote { - color: #800; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-section, -.hljs-title, -.hljs-name { - color: #008; -} - -.hljs-variable, -.hljs-template-variable { - color: #660; -} - -.hljs-string, -.hljs-selector-attr, -.hljs-selector-pseudo, -.hljs-regexp { - color: #080; -} - -.hljs-literal, -.hljs-symbol, -.hljs-bullet, -.hljs-meta, -.hljs-number, -.hljs-link { - color: #066; -} - -.hljs-title, -.hljs-doctag, -.hljs-type, -.hljs-attr, -.hljs-built_in, -.hljs-builtin-name, -.hljs-params { - color: #606; -} - -.hljs-attribute, -.hljs-subst { - color: #000; -} - -.hljs-formula { - background-color: #eee; - font-style: italic; -} - -.hljs-selector-id, -.hljs-selector-class { - color: #9B703F -} - -.hljs-addition { - background-color: #baeeba; -} - -.hljs-deletion { - background-color: #ffc8bd; -} - -.hljs-doctag, -.hljs-strong { - font-weight: bold; -} - -.hljs-emphasis { - font-style: italic; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/grayscale.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/grayscale.css deleted file mode 100644 index 5376f34064..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/grayscale.css +++ /dev/null @@ -1,101 +0,0 @@ -/* - -grayscale style (c) MY Sun - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - color: #333; - background: #fff; -} - -.hljs-comment, -.hljs-quote { - color: #777; - font-style: italic; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-subst { - color: #333; - font-weight: bold; -} - -.hljs-number, -.hljs-literal { - color: #777; -} - -.hljs-string, -.hljs-doctag, -.hljs-formula { - color: #333; - background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAJ0lEQVQIW2O8e/fufwYGBgZBQUEQxcCIIfDu3Tuwivfv30NUoAsAALHpFMMLqZlPAAAAAElFTkSuQmCC) repeat; -} - -.hljs-title, -.hljs-section, -.hljs-selector-id { - color: #000; - font-weight: bold; -} - -.hljs-subst { - font-weight: normal; -} - -.hljs-class .hljs-title, -.hljs-type, -.hljs-name { - color: #333; - font-weight: bold; -} - -.hljs-tag { - color: #333; -} - -.hljs-regexp { - color: #333; - background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAICAYAAADA+m62AAAAPUlEQVQYV2NkQAN37979r6yszIgujiIAU4RNMVwhuiQ6H6wQl3XI4oy4FMHcCJPHcDS6J2A2EqUQpJhohQDexSef15DBCwAAAABJRU5ErkJggg==) repeat; -} - -.hljs-symbol, -.hljs-bullet, -.hljs-link { - color: #000; - background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAKElEQVQIW2NkQAO7d+/+z4gsBhJwdXVlhAvCBECKwIIwAbhKZBUwBQA6hBpm5efZsgAAAABJRU5ErkJggg==) repeat; -} - -.hljs-built_in, -.hljs-builtin-name { - color: #000; - text-decoration: underline; -} - -.hljs-meta { - color: #999; - font-weight: bold; -} - -.hljs-deletion { - color: #fff; - background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAADCAYAAABS3WWCAAAAE0lEQVQIW2MMDQ39zzhz5kwIAQAyxweWgUHd1AAAAABJRU5ErkJggg==) repeat; -} - -.hljs-addition { - color: #000; - background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAALUlEQVQYV2N89+7dfwYk8P79ewZBQUFkIQZGOiu6e/cuiptQHAPl0NtNxAQBAM97Oejj3Dg7AAAAAElFTkSuQmCC) repeat; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/gruvbox-dark.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/gruvbox-dark.css deleted file mode 100644 index f563811a86..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/gruvbox-dark.css +++ /dev/null @@ -1,108 +0,0 @@ -/* - -Gruvbox style (dark) (c) Pavel Pertsev (original style at https://github.com/morhetz/gruvbox) - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #282828; -} - -.hljs, -.hljs-subst { - color: #ebdbb2; -} - -/* Gruvbox Red */ -.hljs-deletion, -.hljs-formula, -.hljs-keyword, -.hljs-link, -.hljs-selector-tag { - color: #fb4934; -} - -/* Gruvbox Blue */ -.hljs-built_in, -.hljs-emphasis, -.hljs-name, -.hljs-quote, -.hljs-strong, -.hljs-title, -.hljs-variable { - color: #83a598; -} - -/* Gruvbox Yellow */ -.hljs-attr, -.hljs-params, -.hljs-template-tag, -.hljs-type { - color: #fabd2f; -} - -/* Gruvbox Purple */ -.hljs-builtin-name, -.hljs-doctag, -.hljs-literal, -.hljs-number { - color: #8f3f71; -} - -/* Gruvbox Orange */ -.hljs-code, -.hljs-meta, -.hljs-regexp, -.hljs-selector-id, -.hljs-template-variable { - color: #fe8019; -} - -/* Gruvbox Green */ -.hljs-addition, -.hljs-meta-string, -.hljs-section, -.hljs-selector-attr, -.hljs-selector-class, -.hljs-string, -.hljs-symbol { - color: #b8bb26; -} - -/* Gruvbox Aqua */ -.hljs-attribute, -.hljs-bullet, -.hljs-class, -.hljs-function, -.hljs-function .hljs-keyword, -.hljs-meta-keyword, -.hljs-selector-pseudo, -.hljs-tag { - color: #8ec07c; -} - -/* Gruvbox Gray */ -.hljs-comment { - color: #928374; -} - -/* Gruvbox Purple */ -.hljs-link_label, -.hljs-literal, -.hljs-number { - color: #d3869b; -} - -.hljs-comment, -.hljs-emphasis { - font-style: italic; -} - -.hljs-section, -.hljs-strong, -.hljs-tag { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/gruvbox-light.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/gruvbox-light.css deleted file mode 100644 index ff45468eb2..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/gruvbox-light.css +++ /dev/null @@ -1,108 +0,0 @@ -/* - -Gruvbox style (light) (c) Pavel Pertsev (original style at https://github.com/morhetz/gruvbox) - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #fbf1c7; -} - -.hljs, -.hljs-subst { - color: #3c3836; -} - -/* Gruvbox Red */ -.hljs-deletion, -.hljs-formula, -.hljs-keyword, -.hljs-link, -.hljs-selector-tag { - color: #9d0006; -} - -/* Gruvbox Blue */ -.hljs-built_in, -.hljs-emphasis, -.hljs-name, -.hljs-quote, -.hljs-strong, -.hljs-title, -.hljs-variable { - color: #076678; -} - -/* Gruvbox Yellow */ -.hljs-attr, -.hljs-params, -.hljs-template-tag, -.hljs-type { - color: #b57614; -} - -/* Gruvbox Purple */ -.hljs-builtin-name, -.hljs-doctag, -.hljs-literal, -.hljs-number { - color: #8f3f71; -} - -/* Gruvbox Orange */ -.hljs-code, -.hljs-meta, -.hljs-regexp, -.hljs-selector-id, -.hljs-template-variable { - color: #af3a03; -} - -/* Gruvbox Green */ -.hljs-addition, -.hljs-meta-string, -.hljs-section, -.hljs-selector-attr, -.hljs-selector-class, -.hljs-string, -.hljs-symbol { - color: #79740e; -} - -/* Gruvbox Aqua */ -.hljs-attribute, -.hljs-bullet, -.hljs-class, -.hljs-function, -.hljs-function .hljs-keyword, -.hljs-meta-keyword, -.hljs-selector-pseudo, -.hljs-tag { - color: #427b58; -} - -/* Gruvbox Gray */ -.hljs-comment { - color: #928374; -} - -/* Gruvbox Purple */ -.hljs-link_label, -.hljs-literal, -.hljs-number { - color: #8f3f71; -} - -.hljs-comment, -.hljs-emphasis { - font-style: italic; -} - -.hljs-section, -.hljs-strong, -.hljs-tag { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/hopscotch.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/hopscotch.css deleted file mode 100644 index 32e60d230a..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/hopscotch.css +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Hopscotch - * by Jan T. Sott - * https://github.com/idleberg/Hopscotch - * - * This work is licensed under the Creative Commons CC0 1.0 Universal License - */ - -/* Comment */ -.hljs-comment, -.hljs-quote { - color: #989498; -} - -/* Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-tag, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class, -.hljs-regexp, -.hljs-link, -.hljs-deletion { - color: #dd464c; -} - -/* Orange */ -.hljs-number, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #fd8b19; -} - -/* Yellow */ -.hljs-class .hljs-title { - color: #fdcc59; -} - -/* Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet, -.hljs-addition { - color: #8fc13e; -} - -/* Aqua */ -.hljs-meta { - color: #149b93; -} - -/* Blue */ -.hljs-function, -.hljs-section, -.hljs-title { - color: #1290bf; -} - -/* Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #c85e7c; -} - -.hljs { - display: block; - background: #322931; - color: #b9b5b8; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/hybrid.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/hybrid.css deleted file mode 100644 index 29735a1890..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/hybrid.css +++ /dev/null @@ -1,102 +0,0 @@ -/* - -vim-hybrid theme by w0ng (https://github.com/w0ng/vim-hybrid) - -*/ - -/*background color*/ -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #1d1f21; -} - -/*selection color*/ -.hljs::selection, -.hljs span::selection { - background: #373b41; -} - -.hljs::-moz-selection, -.hljs span::-moz-selection { - background: #373b41; -} - -/*foreground color*/ -.hljs { - color: #c5c8c6; -} - -/*color: fg_yellow*/ -.hljs-title, -.hljs-name { - color: #f0c674; -} - -/*color: fg_comment*/ -.hljs-comment, -.hljs-meta, -.hljs-meta .hljs-keyword { - color: #707880; -} - -/*color: fg_red*/ -.hljs-number, -.hljs-symbol, -.hljs-literal, -.hljs-deletion, -.hljs-link { - color: #cc6666 -} - -/*color: fg_green*/ -.hljs-string, -.hljs-doctag, -.hljs-addition, -.hljs-regexp, -.hljs-selector-attr, -.hljs-selector-pseudo { - color: #b5bd68; -} - -/*color: fg_purple*/ -.hljs-attribute, -.hljs-code, -.hljs-selector-id { - color: #b294bb; -} - -/*color: fg_blue*/ -.hljs-keyword, -.hljs-selector-tag, -.hljs-bullet, -.hljs-tag { - color: #81a2be; -} - -/*color: fg_aqua*/ -.hljs-subst, -.hljs-variable, -.hljs-template-tag, -.hljs-template-variable { - color: #8abeb7; -} - -/*color: fg_orange*/ -.hljs-type, -.hljs-built_in, -.hljs-builtin-name, -.hljs-quote, -.hljs-section, -.hljs-selector-class { - color: #de935f; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/idea.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/idea.css deleted file mode 100644 index 3bf1892bd4..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/idea.css +++ /dev/null @@ -1,97 +0,0 @@ -/* - -Intellij Idea-like styling (c) Vasily Polovnyov - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - color: #000; - background: #fff; -} - -.hljs-subst, -.hljs-title { - font-weight: normal; - color: #000; -} - -.hljs-comment, -.hljs-quote { - color: #808080; - font-style: italic; -} - -.hljs-meta { - color: #808000; -} - -.hljs-tag { - background: #efefef; -} - -.hljs-section, -.hljs-name, -.hljs-literal, -.hljs-keyword, -.hljs-selector-tag, -.hljs-type, -.hljs-selector-id, -.hljs-selector-class { - font-weight: bold; - color: #000080; -} - -.hljs-attribute, -.hljs-number, -.hljs-regexp, -.hljs-link { - font-weight: bold; - color: #0000ff; -} - -.hljs-number, -.hljs-regexp, -.hljs-link { - font-weight: normal; -} - -.hljs-string { - color: #008000; - font-weight: bold; -} - -.hljs-symbol, -.hljs-bullet, -.hljs-formula { - color: #000; - background: #d0eded; - font-style: italic; -} - -.hljs-doctag { - text-decoration: underline; -} - -.hljs-variable, -.hljs-template-variable { - color: #660e7a; -} - -.hljs-addition { - background: #baeeba; -} - -.hljs-deletion { - background: #ffc8bd; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/ir-black.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/ir-black.css deleted file mode 100644 index bd4c755ed8..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/ir-black.css +++ /dev/null @@ -1,73 +0,0 @@ -/* - IR_Black style (c) Vasily Mikhailitchenko -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #000; - color: #f8f8f8; -} - -.hljs-comment, -.hljs-quote, -.hljs-meta { - color: #7c7c7c; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-tag, -.hljs-name { - color: #96cbfe; -} - -.hljs-attribute, -.hljs-selector-id { - color: #ffffb6; -} - -.hljs-string, -.hljs-selector-attr, -.hljs-selector-pseudo, -.hljs-addition { - color: #a8ff60; -} - -.hljs-subst { - color: #daefa3; -} - -.hljs-regexp, -.hljs-link { - color: #e9c062; -} - -.hljs-title, -.hljs-section, -.hljs-type, -.hljs-doctag { - color: #ffffb6; -} - -.hljs-symbol, -.hljs-bullet, -.hljs-variable, -.hljs-template-variable, -.hljs-literal { - color: #c6c5fe; -} - -.hljs-number, -.hljs-deletion { - color:#ff73fd; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/kimbie.dark.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/kimbie.dark.css deleted file mode 100644 index d139cb5d0c..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/kimbie.dark.css +++ /dev/null @@ -1,74 +0,0 @@ -/* - Name: Kimbie (dark) - Author: Jan T. Sott - License: Creative Commons Attribution-ShareAlike 4.0 Unported License - URL: https://github.com/idleberg/Kimbie-highlight.js -*/ - -/* Kimbie Comment */ -.hljs-comment, -.hljs-quote { - color: #d6baad; -} - -/* Kimbie Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-tag, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class, -.hljs-regexp, -.hljs-meta { - color: #dc3958; -} - -/* Kimbie Orange */ -.hljs-number, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params, -.hljs-deletion, -.hljs-link { - color: #f79a32; -} - -/* Kimbie Yellow */ -.hljs-title, -.hljs-section, -.hljs-attribute { - color: #f06431; -} - -/* Kimbie Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet, -.hljs-addition { - color: #889b4a; -} - -/* Kimbie Purple */ -.hljs-keyword, -.hljs-selector-tag, -.hljs-function { - color: #98676a; -} - -.hljs { - display: block; - overflow-x: auto; - background: #221a0f; - color: #d3af86; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/kimbie.light.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/kimbie.light.css deleted file mode 100644 index 04ff6ed3a2..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/kimbie.light.css +++ /dev/null @@ -1,74 +0,0 @@ -/* - Name: Kimbie (light) - Author: Jan T. Sott - License: Creative Commons Attribution-ShareAlike 4.0 Unported License - URL: https://github.com/idleberg/Kimbie-highlight.js -*/ - -/* Kimbie Comment */ -.hljs-comment, -.hljs-quote { - color: #a57a4c; -} - -/* Kimbie Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-tag, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class, -.hljs-regexp, -.hljs-meta { - color: #dc3958; -} - -/* Kimbie Orange */ -.hljs-number, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params, -.hljs-deletion, -.hljs-link { - color: #f79a32; -} - -/* Kimbie Yellow */ -.hljs-title, -.hljs-section, -.hljs-attribute { - color: #f06431; -} - -/* Kimbie Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet, -.hljs-addition { - color: #889b4a; -} - -/* Kimbie Purple */ -.hljs-keyword, -.hljs-selector-tag, -.hljs-function { - color: #98676a; -} - -.hljs { - display: block; - overflow-x: auto; - background: #fbebd4; - color: #84613d; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/magula.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/magula.css deleted file mode 100644 index 44dee5e8e1..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/magula.css +++ /dev/null @@ -1,70 +0,0 @@ -/* -Description: Magula style for highligh.js -Author: Ruslan Keba -Website: http://rukeba.com/ -Version: 1.0 -Date: 2009-01-03 -Music: Aphex Twin / Xtal -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background-color: #f4f4f4; -} - -.hljs, -.hljs-subst { - color: black; -} - -.hljs-string, -.hljs-title, -.hljs-symbol, -.hljs-bullet, -.hljs-attribute, -.hljs-addition, -.hljs-variable, -.hljs-template-tag, -.hljs-template-variable { - color: #050; -} - -.hljs-comment, -.hljs-quote { - color: #777; -} - -.hljs-number, -.hljs-regexp, -.hljs-literal, -.hljs-type, -.hljs-link { - color: #800; -} - -.hljs-deletion, -.hljs-meta { - color: #00e; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-doctag, -.hljs-title, -.hljs-section, -.hljs-built_in, -.hljs-tag, -.hljs-name { - font-weight: bold; - color: navy; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/mono-blue.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/mono-blue.css deleted file mode 100644 index 884c97c767..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/mono-blue.css +++ /dev/null @@ -1,59 +0,0 @@ -/* - Five-color theme from a single blue hue. -*/ -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #eaeef3; -} - -.hljs { - color: #00193a; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-title, -.hljs-section, -.hljs-doctag, -.hljs-name, -.hljs-strong { - font-weight: bold; -} - -.hljs-comment { - color: #738191; -} - -.hljs-string, -.hljs-title, -.hljs-section, -.hljs-built_in, -.hljs-literal, -.hljs-type, -.hljs-addition, -.hljs-tag, -.hljs-quote, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #0048ab; -} - -.hljs-meta, -.hljs-subst, -.hljs-symbol, -.hljs-regexp, -.hljs-attribute, -.hljs-deletion, -.hljs-variable, -.hljs-template-variable, -.hljs-link, -.hljs-bullet { - color: #4c81c9; -} - -.hljs-emphasis { - font-style: italic; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/monokai-sublime.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/monokai-sublime.css deleted file mode 100644 index 2864170daf..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/monokai-sublime.css +++ /dev/null @@ -1,83 +0,0 @@ -/* - -Monokai Sublime style. Derived from Monokai by noformnocontent http://nn.mit-license.org/ - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #23241f; -} - -.hljs, -.hljs-tag, -.hljs-subst { - color: #f8f8f2; -} - -.hljs-strong, -.hljs-emphasis { - color: #a8a8a2; -} - -.hljs-bullet, -.hljs-quote, -.hljs-number, -.hljs-regexp, -.hljs-literal, -.hljs-link { - color: #ae81ff; -} - -.hljs-code, -.hljs-title, -.hljs-section, -.hljs-selector-class { - color: #a6e22e; -} - -.hljs-strong { - font-weight: bold; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-name, -.hljs-attr { - color: #f92672; -} - -.hljs-symbol, -.hljs-attribute { - color: #66d9ef; -} - -.hljs-params, -.hljs-class .hljs-title { - color: #f8f8f2; -} - -.hljs-string, -.hljs-type, -.hljs-built_in, -.hljs-builtin-name, -.hljs-selector-id, -.hljs-selector-attr, -.hljs-selector-pseudo, -.hljs-addition, -.hljs-variable, -.hljs-template-variable { - color: #e6db74; -} - -.hljs-comment, -.hljs-deletion, -.hljs-meta { - color: #75715e; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/monokai.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/monokai.css deleted file mode 100644 index 775d53f91a..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/monokai.css +++ /dev/null @@ -1,70 +0,0 @@ -/* -Monokai style - ported by Luigi Maselli - http://grigio.org -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #272822; color: #ddd; -} - -.hljs-tag, -.hljs-keyword, -.hljs-selector-tag, -.hljs-literal, -.hljs-strong, -.hljs-name { - color: #f92672; -} - -.hljs-code { - color: #66d9ef; -} - -.hljs-class .hljs-title { - color: white; -} - -.hljs-attribute, -.hljs-symbol, -.hljs-regexp, -.hljs-link { - color: #bf79db; -} - -.hljs-string, -.hljs-bullet, -.hljs-subst, -.hljs-title, -.hljs-section, -.hljs-emphasis, -.hljs-type, -.hljs-built_in, -.hljs-builtin-name, -.hljs-selector-attr, -.hljs-selector-pseudo, -.hljs-addition, -.hljs-variable, -.hljs-template-tag, -.hljs-template-variable { - color: #a6e22e; -} - -.hljs-comment, -.hljs-quote, -.hljs-deletion, -.hljs-meta { - color: #75715e; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-literal, -.hljs-doctag, -.hljs-title, -.hljs-section, -.hljs-type, -.hljs-selector-id { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/obsidian.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/obsidian.css deleted file mode 100644 index 356630fa23..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/obsidian.css +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Obsidian style - * ported by Alexander Marenin (http://github.com/ioncreature) - */ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #282b2e; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-literal, -.hljs-selector-id { - color: #93c763; -} - -.hljs-number { - color: #ffcd22; -} - -.hljs { - color: #e0e2e4; -} - -.hljs-attribute { - color: #668bb0; -} - -.hljs-code, -.hljs-class .hljs-title, -.hljs-section { - color: white; -} - -.hljs-regexp, -.hljs-link { - color: #d39745; -} - -.hljs-meta { - color: #557182; -} - -.hljs-tag, -.hljs-name, -.hljs-bullet, -.hljs-subst, -.hljs-emphasis, -.hljs-type, -.hljs-built_in, -.hljs-selector-attr, -.hljs-selector-pseudo, -.hljs-addition, -.hljs-variable, -.hljs-template-tag, -.hljs-template-variable { - color: #8cbbad; -} - -.hljs-string, -.hljs-symbol { - color: #ec7600; -} - -.hljs-comment, -.hljs-quote, -.hljs-deletion { - color: #818e96; -} - -.hljs-selector-class { - color: #A082BD -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-literal, -.hljs-doctag, -.hljs-title, -.hljs-section, -.hljs-type, -.hljs-name, -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/ocean.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/ocean.css deleted file mode 100644 index 5901581b40..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/ocean.css +++ /dev/null @@ -1,74 +0,0 @@ -/* Ocean Dark Theme */ -/* https://github.com/gavsiu */ -/* Original theme - https://github.com/chriskempson/base16 */ - -/* Ocean Comment */ -.hljs-comment, -.hljs-quote { - color: #65737e; -} - -/* Ocean Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-tag, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class, -.hljs-regexp, -.hljs-deletion { - color: #bf616a; -} - -/* Ocean Orange */ -.hljs-number, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params, -.hljs-meta, -.hljs-link { - color: #d08770; -} - -/* Ocean Yellow */ -.hljs-attribute { - color: #ebcb8b; -} - -/* Ocean Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet, -.hljs-addition { - color: #a3be8c; -} - -/* Ocean Blue */ -.hljs-title, -.hljs-section { - color: #8fa1b3; -} - -/* Ocean Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #b48ead; -} - -.hljs { - display: block; - overflow-x: auto; - background: #2b303b; - color: #c0c5ce; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/paraiso-dark.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/paraiso-dark.css deleted file mode 100644 index e7292401c6..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/paraiso-dark.css +++ /dev/null @@ -1,72 +0,0 @@ -/* - Paraíso (dark) - Created by Jan T. Sott (http://github.com/idleberg) - Inspired by the art of Rubens LP (http://www.rubenslp.com.br) -*/ - -/* Paraíso Comment */ -.hljs-comment, -.hljs-quote { - color: #8d8687; -} - -/* Paraíso Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-tag, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class, -.hljs-regexp, -.hljs-link, -.hljs-meta { - color: #ef6155; -} - -/* Paraíso Orange */ -.hljs-number, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params, -.hljs-deletion { - color: #f99b15; -} - -/* Paraíso Yellow */ -.hljs-title, -.hljs-section, -.hljs-attribute { - color: #fec418; -} - -/* Paraíso Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet, -.hljs-addition { - color: #48b685; -} - -/* Paraíso Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #815ba4; -} - -.hljs { - display: block; - overflow-x: auto; - background: #2f1e2e; - color: #a39e9b; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/paraiso-light.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/paraiso-light.css deleted file mode 100644 index 944857cd8d..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/paraiso-light.css +++ /dev/null @@ -1,72 +0,0 @@ -/* - Paraíso (light) - Created by Jan T. Sott (http://github.com/idleberg) - Inspired by the art of Rubens LP (http://www.rubenslp.com.br) -*/ - -/* Paraíso Comment */ -.hljs-comment, -.hljs-quote { - color: #776e71; -} - -/* Paraíso Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-tag, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class, -.hljs-regexp, -.hljs-link, -.hljs-meta { - color: #ef6155; -} - -/* Paraíso Orange */ -.hljs-number, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params, -.hljs-deletion { - color: #f99b15; -} - -/* Paraíso Yellow */ -.hljs-title, -.hljs-section, -.hljs-attribute { - color: #fec418; -} - -/* Paraíso Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet, -.hljs-addition { - color: #48b685; -} - -/* Paraíso Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #815ba4; -} - -.hljs { - display: block; - overflow-x: auto; - background: #e7e9db; - color: #4f424c; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/pojoaque.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/pojoaque.css deleted file mode 100644 index 2e07847b2b..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/pojoaque.css +++ /dev/null @@ -1,83 +0,0 @@ -/* - -Pojoaque Style by Jason Tate -http://web-cms-designs.com/ftopict-10-pojoaque-style-for-highlight-js-code-highlighter.html -Based on Solarized Style from http://ethanschoonover.com/solarized - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - color: #dccf8f; - background: url(./pojoaque.jpg) repeat scroll left top #181914; -} - -.hljs-comment, -.hljs-quote { - color: #586e75; - font-style: italic; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-literal, -.hljs-addition { - color: #b64926; -} - -.hljs-number, -.hljs-string, -.hljs-doctag, -.hljs-regexp { - color: #468966; -} - -.hljs-title, -.hljs-section, -.hljs-built_in, -.hljs-name { - color: #ffb03b; -} - -.hljs-variable, -.hljs-template-variable, -.hljs-class .hljs-title, -.hljs-type, -.hljs-tag { - color: #b58900; -} - -.hljs-attribute { - color: #b89859; -} - -.hljs-symbol, -.hljs-bullet, -.hljs-link, -.hljs-subst, -.hljs-meta { - color: #cb4b16; -} - -.hljs-deletion { - color: #dc322f; -} - -.hljs-selector-id, -.hljs-selector-class { - color: #d3a60c; -} - -.hljs-formula { - background: #073642; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/pojoaque.jpg b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/pojoaque.jpg deleted file mode 100644 index 9c07d4ab40..0000000000 Binary files a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/pojoaque.jpg and /dev/null differ diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/purebasic.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/purebasic.css deleted file mode 100644 index 5ce9b9e071..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/purebasic.css +++ /dev/null @@ -1,96 +0,0 @@ -/* - -PureBASIC native IDE style ( version 1.0 - April 2016 ) - -by Tristano Ajmone - -Public Domain - -NOTE_1: PureBASIC code syntax highlighting only applies the following classes: - .hljs-comment - .hljs-function - .hljs-keywords - .hljs-string - .hljs-symbol - - Other classes are added here for the benefit of styling other languages with the look and feel of PureBASIC native IDE style. - If you need to customize a stylesheet for PureBASIC only, remove all non-relevant classes -- PureBASIC-related classes are followed by - a "--- used for PureBASIC ... ---" comment on same line. - -NOTE_2: Color names provided in comments were derived using "Name that Color" online tool: - http://chir.ag/projects/name-that-color -*/ - -.hljs { /* Common set of rules required by highlight.js (don'r remove!) */ - display: block; - overflow-x: auto; - padding: 0.5em; - background: #FFFFDF; /* Half and Half (approx.) */ -/* --- Uncomment to add PureBASIC native IDE styled font! - font-family: Consolas; -*/ -} - -.hljs, /* --- used for PureBASIC base color --- */ -.hljs-type, /* --- used for PureBASIC Procedures return type --- */ -.hljs-function, /* --- used for wrapping PureBASIC Procedures definitions --- */ -.hljs-name, -.hljs-number, -.hljs-attr, -.hljs-params, -.hljs-subst { - color: #000000; /* Black */ -} - -.hljs-comment, /* --- used for PureBASIC Comments --- */ -.hljs-regexp, -.hljs-section, -.hljs-selector-pseudo, -.hljs-addition { - color: #00AAAA; /* Persian Green (approx.) */ -} - -.hljs-title, /* --- used for PureBASIC Procedures Names --- */ -.hljs-tag, -.hljs-variable, -.hljs-code { - color: #006666; /* Blue Stone (approx.) */ -} - -.hljs-keyword, /* --- used for PureBASIC Keywords --- */ -.hljs-class, -.hljs-meta-keyword, -.hljs-selector-class, -.hljs-built_in, -.hljs-builtin-name { - color: #006666; /* Blue Stone (approx.) */ - font-weight: bold; -} - -.hljs-string, /* --- used for PureBASIC Strings --- */ -.hljs-selector-attr { - color: #0080FF; /* Azure Radiance (approx.) */ -} - -.hljs-symbol, /* --- used for PureBASIC Constants --- */ -.hljs-link, -.hljs-deletion, -.hljs-attribute { - color: #924B72; /* Cannon Pink (approx.) */ -} - -.hljs-meta, -.hljs-literal, -.hljs-selector-id { - color: #924B72; /* Cannon Pink (approx.) */ - font-weight: bold; -} - -.hljs-strong, -.hljs-name { - font-weight: bold; -} - -.hljs-emphasis { - font-style: italic; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/qtcreator_dark.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/qtcreator_dark.css deleted file mode 100644 index 7aa56a3655..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/qtcreator_dark.css +++ /dev/null @@ -1,83 +0,0 @@ -/* - -Qt Creator dark color scheme - -*/ - - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #000000; -} - -.hljs, -.hljs-subst, -.hljs-tag, -.hljs-title { - color: #aaaaaa; -} - -.hljs-strong, -.hljs-emphasis { - color: #a8a8a2; -} - -.hljs-bullet, -.hljs-quote, -.hljs-number, -.hljs-regexp, -.hljs-literal { - color: #ff55ff; -} - -.hljs-code -.hljs-selector-class { - color: #aaaaff; -} - -.hljs-emphasis, -.hljs-stronge, -.hljs-type { - font-style: italic; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-function, -.hljs-section, -.hljs-symbol, -.hljs-name { - color: #ffff55; -} - -.hljs-attribute { - color: #ff5555; -} - -.hljs-variable, -.hljs-params, -.hljs-class .hljs-title { - color: #8888ff; -} - -.hljs-string, -.hljs-selector-id, -.hljs-selector-attr, -.hljs-selector-pseudo, -.hljs-type, -.hljs-built_in, -.hljs-builtin-name, -.hljs-template-tag, -.hljs-template-variable, -.hljs-addition, -.hljs-link { - color: #ff55ff; -} - -.hljs-comment, -.hljs-meta, -.hljs-deletion { - color: #55ffff; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/qtcreator_light.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/qtcreator_light.css deleted file mode 100644 index 1efa2c660f..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/qtcreator_light.css +++ /dev/null @@ -1,83 +0,0 @@ -/* - -Qt Creator light color scheme - -*/ - - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #ffffff; -} - -.hljs, -.hljs-subst, -.hljs-tag, -.hljs-title { - color: #000000; -} - -.hljs-strong, -.hljs-emphasis { - color: #000000; -} - -.hljs-bullet, -.hljs-quote, -.hljs-number, -.hljs-regexp, -.hljs-literal { - color: #000080; -} - -.hljs-code -.hljs-selector-class { - color: #800080; -} - -.hljs-emphasis, -.hljs-stronge, -.hljs-type { - font-style: italic; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-function, -.hljs-section, -.hljs-symbol, -.hljs-name { - color: #808000; -} - -.hljs-attribute { - color: #800000; -} - -.hljs-variable, -.hljs-params, -.hljs-class .hljs-title { - color: #0055AF; -} - -.hljs-string, -.hljs-selector-id, -.hljs-selector-attr, -.hljs-selector-pseudo, -.hljs-type, -.hljs-built_in, -.hljs-builtin-name, -.hljs-template-tag, -.hljs-template-variable, -.hljs-addition, -.hljs-link { - color: #008000; -} - -.hljs-comment, -.hljs-meta, -.hljs-deletion { - color: #008000; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/railscasts.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/railscasts.css deleted file mode 100644 index 008cdc5bf1..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/railscasts.css +++ /dev/null @@ -1,106 +0,0 @@ -/* - -Railscasts-like style (c) Visoft, Inc. (Damien White) - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #232323; - color: #e6e1dc; -} - -.hljs-comment, -.hljs-quote { - color: #bc9458; - font-style: italic; -} - -.hljs-keyword, -.hljs-selector-tag { - color: #c26230; -} - -.hljs-string, -.hljs-number, -.hljs-regexp, -.hljs-variable, -.hljs-template-variable { - color: #a5c261; -} - -.hljs-subst { - color: #519f50; -} - -.hljs-tag, -.hljs-name { - color: #e8bf6a; -} - -.hljs-type { - color: #da4939; -} - - -.hljs-symbol, -.hljs-bullet, -.hljs-built_in, -.hljs-builtin-name, -.hljs-attr, -.hljs-link { - color: #6d9cbe; -} - -.hljs-params { - color: #d0d0ff; -} - -.hljs-attribute { - color: #cda869; -} - -.hljs-meta { - color: #9b859d; -} - -.hljs-title, -.hljs-section { - color: #ffc66d; -} - -.hljs-addition { - background-color: #144212; - color: #e6e1dc; - display: inline-block; - width: 100%; -} - -.hljs-deletion { - background-color: #600; - color: #e6e1dc; - display: inline-block; - width: 100%; -} - -.hljs-selector-class { - color: #9b703f; -} - -.hljs-selector-id { - color: #8b98ab; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} - -.hljs-link { - text-decoration: underline; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/rainbow.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/rainbow.css deleted file mode 100644 index 905eb8ef18..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/rainbow.css +++ /dev/null @@ -1,85 +0,0 @@ -/* - -Style with support for rainbow parens - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #474949; - color: #d1d9e1; -} - - -.hljs-comment, -.hljs-quote { - color: #969896; - font-style: italic; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-literal, -.hljs-type, -.hljs-addition { - color: #cc99cc; -} - -.hljs-number, -.hljs-selector-attr, -.hljs-selector-pseudo { - color: #f99157; -} - -.hljs-string, -.hljs-doctag, -.hljs-regexp { - color: #8abeb7; -} - -.hljs-title, -.hljs-name, -.hljs-section, -.hljs-built_in { - color: #b5bd68; -} - -.hljs-variable, -.hljs-template-variable, -.hljs-selector-id, -.hljs-class .hljs-title { - color: #ffcc66; -} - -.hljs-section, -.hljs-name, -.hljs-strong { - font-weight: bold; -} - -.hljs-symbol, -.hljs-bullet, -.hljs-subst, -.hljs-meta, -.hljs-link { - color: #f99157; -} - -.hljs-deletion { - color: #dc322f; -} - -.hljs-formula { - background: #eee8d5; -} - -.hljs-attr, -.hljs-attribute { - color: #81a2be; -} - -.hljs-emphasis { - font-style: italic; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/routeros.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/routeros.css deleted file mode 100644 index ebe23990da..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/routeros.css +++ /dev/null @@ -1,108 +0,0 @@ -/* - - highlight.js style for Microtik RouterOS script - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #F0F0F0; -} - -/* Base color: saturation 0; */ - -.hljs, -.hljs-subst { - color: #444; -} - -.hljs-comment { - color: #888888; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-meta-keyword, -.hljs-doctag, -.hljs-name { - font-weight: bold; -} - -.hljs-attribute { - color: #0E9A00; -} - -.hljs-function { - color: #99069A; -} - -.hljs-builtin-name { - color: #99069A; -} - -/* User color: hue: 0 */ - -.hljs-type, -.hljs-string, -.hljs-number, -.hljs-selector-id, -.hljs-selector-class, -.hljs-quote, -.hljs-template-tag, -.hljs-deletion { - color: #880000; -} - -.hljs-title, -.hljs-section { - color: #880000; - font-weight: bold; -} - -.hljs-regexp, -.hljs-symbol, -.hljs-variable, -.hljs-template-variable, -.hljs-link, -.hljs-selector-attr, -.hljs-selector-pseudo { - color: #BC6060; -} - - -/* Language color: hue: 90; */ - -.hljs-literal { - color: #78A960; -} - -.hljs-built_in, -.hljs-bullet, -.hljs-code, -.hljs-addition { - color: #0C9A9A; -} - - -/* Meta color: hue: 200 */ - -.hljs-meta { - color: #1f7199; -} - -.hljs-meta-string { - color: #4d99bf; -} - - -/* Misc effects */ - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/school-book.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/school-book.css deleted file mode 100644 index 964b51d841..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/school-book.css +++ /dev/null @@ -1,72 +0,0 @@ -/* - -School Book style from goldblog.com.ua (c) Zaripov Yura - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 15px 0.5em 0.5em 30px; - font-size: 11px; - line-height:16px; -} - -pre{ - background:#f6f6ae url(./school-book.png); - border-top: solid 2px #d2e8b9; - border-bottom: solid 1px #d2e8b9; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-literal { - color:#005599; - font-weight:bold; -} - -.hljs, -.hljs-subst { - color: #3e5915; -} - -.hljs-string, -.hljs-title, -.hljs-section, -.hljs-type, -.hljs-symbol, -.hljs-bullet, -.hljs-attribute, -.hljs-built_in, -.hljs-builtin-name, -.hljs-addition, -.hljs-variable, -.hljs-template-tag, -.hljs-template-variable, -.hljs-link { - color: #2c009f; -} - -.hljs-comment, -.hljs-quote, -.hljs-deletion, -.hljs-meta { - color: #e60415; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-literal, -.hljs-doctag, -.hljs-title, -.hljs-section, -.hljs-type, -.hljs-name, -.hljs-selector-id, -.hljs-strong { - font-weight: bold; -} - -.hljs-emphasis { - font-style: italic; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/school-book.png b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/school-book.png deleted file mode 100644 index 956e9790a0..0000000000 Binary files a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/school-book.png and /dev/null differ diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/solarized-dark.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/solarized-dark.css deleted file mode 100644 index b4c0da1f78..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/solarized-dark.css +++ /dev/null @@ -1,84 +0,0 @@ -/* - -Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #002b36; - color: #839496; -} - -.hljs-comment, -.hljs-quote { - color: #586e75; -} - -/* Solarized Green */ -.hljs-keyword, -.hljs-selector-tag, -.hljs-addition { - color: #859900; -} - -/* Solarized Cyan */ -.hljs-number, -.hljs-string, -.hljs-meta .hljs-meta-string, -.hljs-literal, -.hljs-doctag, -.hljs-regexp { - color: #2aa198; -} - -/* Solarized Blue */ -.hljs-title, -.hljs-section, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #268bd2; -} - -/* Solarized Yellow */ -.hljs-attribute, -.hljs-attr, -.hljs-variable, -.hljs-template-variable, -.hljs-class .hljs-title, -.hljs-type { - color: #b58900; -} - -/* Solarized Orange */ -.hljs-symbol, -.hljs-bullet, -.hljs-subst, -.hljs-meta, -.hljs-meta .hljs-keyword, -.hljs-selector-attr, -.hljs-selector-pseudo, -.hljs-link { - color: #cb4b16; -} - -/* Solarized Red */ -.hljs-built_in, -.hljs-deletion { - color: #dc322f; -} - -.hljs-formula { - background: #073642; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/solarized-light.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/solarized-light.css deleted file mode 100644 index fdcfcc72c4..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/solarized-light.css +++ /dev/null @@ -1,84 +0,0 @@ -/* - -Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #fdf6e3; - color: #657b83; -} - -.hljs-comment, -.hljs-quote { - color: #93a1a1; -} - -/* Solarized Green */ -.hljs-keyword, -.hljs-selector-tag, -.hljs-addition { - color: #859900; -} - -/* Solarized Cyan */ -.hljs-number, -.hljs-string, -.hljs-meta .hljs-meta-string, -.hljs-literal, -.hljs-doctag, -.hljs-regexp { - color: #2aa198; -} - -/* Solarized Blue */ -.hljs-title, -.hljs-section, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #268bd2; -} - -/* Solarized Yellow */ -.hljs-attribute, -.hljs-attr, -.hljs-variable, -.hljs-template-variable, -.hljs-class .hljs-title, -.hljs-type { - color: #b58900; -} - -/* Solarized Orange */ -.hljs-symbol, -.hljs-bullet, -.hljs-subst, -.hljs-meta, -.hljs-meta .hljs-keyword, -.hljs-selector-attr, -.hljs-selector-pseudo, -.hljs-link { - color: #cb4b16; -} - -/* Solarized Red */ -.hljs-built_in, -.hljs-deletion { - color: #dc322f; -} - -.hljs-formula { - background: #eee8d5; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/sunburst.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/sunburst.css deleted file mode 100644 index f56dd5e9b6..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/sunburst.css +++ /dev/null @@ -1,102 +0,0 @@ -/* - -Sunburst-like style (c) Vasily Polovnyov - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #000; - color: #f8f8f8; -} - -.hljs-comment, -.hljs-quote { - color: #aeaeae; - font-style: italic; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-type { - color: #e28964; -} - -.hljs-string { - color: #65b042; -} - -.hljs-subst { - color: #daefa3; -} - -.hljs-regexp, -.hljs-link { - color: #e9c062; -} - -.hljs-title, -.hljs-section, -.hljs-tag, -.hljs-name { - color: #89bdff; -} - -.hljs-class .hljs-title, -.hljs-doctag { - text-decoration: underline; -} - -.hljs-symbol, -.hljs-bullet, -.hljs-number { - color: #3387cc; -} - -.hljs-params, -.hljs-variable, -.hljs-template-variable { - color: #3e87e3; -} - -.hljs-attribute { - color: #cda869; -} - -.hljs-meta { - color: #8996a8; -} - -.hljs-formula { - background-color: #0e2231; - color: #f8f8f8; - font-style: italic; -} - -.hljs-addition { - background-color: #253b22; - color: #f8f8f8; -} - -.hljs-deletion { - background-color: #420e09; - color: #f8f8f8; -} - -.hljs-selector-class { - color: #9b703f; -} - -.hljs-selector-id { - color: #8b98ab; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/tomorrow-night-blue.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/tomorrow-night-blue.css deleted file mode 100644 index 78e59cc8cb..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/tomorrow-night-blue.css +++ /dev/null @@ -1,75 +0,0 @@ -/* Tomorrow Night Blue Theme */ -/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ -/* Original theme - https://github.com/chriskempson/tomorrow-theme */ -/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ - -/* Tomorrow Comment */ -.hljs-comment, -.hljs-quote { - color: #7285b7; -} - -/* Tomorrow Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-tag, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class, -.hljs-regexp, -.hljs-deletion { - color: #ff9da4; -} - -/* Tomorrow Orange */ -.hljs-number, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params, -.hljs-meta, -.hljs-link { - color: #ffc58f; -} - -/* Tomorrow Yellow */ -.hljs-attribute { - color: #ffeead; -} - -/* Tomorrow Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet, -.hljs-addition { - color: #d1f1a9; -} - -/* Tomorrow Blue */ -.hljs-title, -.hljs-section { - color: #bbdaff; -} - -/* Tomorrow Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #ebbbff; -} - -.hljs { - display: block; - overflow-x: auto; - background: #002451; - color: white; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/tomorrow-night-bright.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/tomorrow-night-bright.css deleted file mode 100644 index e05af8ae24..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/tomorrow-night-bright.css +++ /dev/null @@ -1,74 +0,0 @@ -/* Tomorrow Night Bright Theme */ -/* Original theme - https://github.com/chriskempson/tomorrow-theme */ -/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ - -/* Tomorrow Comment */ -.hljs-comment, -.hljs-quote { - color: #969896; -} - -/* Tomorrow Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-tag, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class, -.hljs-regexp, -.hljs-deletion { - color: #d54e53; -} - -/* Tomorrow Orange */ -.hljs-number, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params, -.hljs-meta, -.hljs-link { - color: #e78c45; -} - -/* Tomorrow Yellow */ -.hljs-attribute { - color: #e7c547; -} - -/* Tomorrow Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet, -.hljs-addition { - color: #b9ca4a; -} - -/* Tomorrow Blue */ -.hljs-title, -.hljs-section { - color: #7aa6da; -} - -/* Tomorrow Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #c397d8; -} - -.hljs { - display: block; - overflow-x: auto; - background: black; - color: #eaeaea; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/tomorrow-night-eighties.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/tomorrow-night-eighties.css deleted file mode 100644 index 08fd51c742..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/tomorrow-night-eighties.css +++ /dev/null @@ -1,74 +0,0 @@ -/* Tomorrow Night Eighties Theme */ -/* Original theme - https://github.com/chriskempson/tomorrow-theme */ -/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ - -/* Tomorrow Comment */ -.hljs-comment, -.hljs-quote { - color: #999999; -} - -/* Tomorrow Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-tag, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class, -.hljs-regexp, -.hljs-deletion { - color: #f2777a; -} - -/* Tomorrow Orange */ -.hljs-number, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params, -.hljs-meta, -.hljs-link { - color: #f99157; -} - -/* Tomorrow Yellow */ -.hljs-attribute { - color: #ffcc66; -} - -/* Tomorrow Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet, -.hljs-addition { - color: #99cc99; -} - -/* Tomorrow Blue */ -.hljs-title, -.hljs-section { - color: #6699cc; -} - -/* Tomorrow Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #cc99cc; -} - -.hljs { - display: block; - overflow-x: auto; - background: #2d2d2d; - color: #cccccc; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/tomorrow-night.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/tomorrow-night.css deleted file mode 100644 index ddd270a4e7..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/tomorrow-night.css +++ /dev/null @@ -1,75 +0,0 @@ -/* Tomorrow Night Theme */ -/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ -/* Original theme - https://github.com/chriskempson/tomorrow-theme */ -/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ - -/* Tomorrow Comment */ -.hljs-comment, -.hljs-quote { - color: #969896; -} - -/* Tomorrow Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-tag, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class, -.hljs-regexp, -.hljs-deletion { - color: #cc6666; -} - -/* Tomorrow Orange */ -.hljs-number, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params, -.hljs-meta, -.hljs-link { - color: #de935f; -} - -/* Tomorrow Yellow */ -.hljs-attribute { - color: #f0c674; -} - -/* Tomorrow Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet, -.hljs-addition { - color: #b5bd68; -} - -/* Tomorrow Blue */ -.hljs-title, -.hljs-section { - color: #81a2be; -} - -/* Tomorrow Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #b294bb; -} - -.hljs { - display: block; - overflow-x: auto; - background: #1d1f21; - color: #c5c8c6; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/tomorrow.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/tomorrow.css deleted file mode 100644 index 026a62fe3b..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/tomorrow.css +++ /dev/null @@ -1,72 +0,0 @@ -/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ - -/* Tomorrow Comment */ -.hljs-comment, -.hljs-quote { - color: #8e908c; -} - -/* Tomorrow Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-tag, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class, -.hljs-regexp, -.hljs-deletion { - color: #c82829; -} - -/* Tomorrow Orange */ -.hljs-number, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params, -.hljs-meta, -.hljs-link { - color: #f5871f; -} - -/* Tomorrow Yellow */ -.hljs-attribute { - color: #eab700; -} - -/* Tomorrow Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet, -.hljs-addition { - color: #718c00; -} - -/* Tomorrow Blue */ -.hljs-title, -.hljs-section { - color: #4271ae; -} - -/* Tomorrow Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #8959a8; -} - -.hljs { - display: block; - overflow-x: auto; - background: white; - color: #4d4d4c; - padding: 0.5em; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/vs.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/vs.css deleted file mode 100644 index c5d07d3115..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/vs.css +++ /dev/null @@ -1,68 +0,0 @@ -/* - -Visual Studio-like style based on original C# coloring by Jason Diamond - -*/ -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: white; - color: black; -} - -.hljs-comment, -.hljs-quote, -.hljs-variable { - color: #008000; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-built_in, -.hljs-name, -.hljs-tag { - color: #00f; -} - -.hljs-string, -.hljs-title, -.hljs-section, -.hljs-attribute, -.hljs-literal, -.hljs-template-tag, -.hljs-template-variable, -.hljs-type, -.hljs-addition { - color: #a31515; -} - -.hljs-deletion, -.hljs-selector-attr, -.hljs-selector-pseudo, -.hljs-meta { - color: #2b91af; -} - -.hljs-doctag { - color: #808080; -} - -.hljs-attr { - color: #f00; -} - -.hljs-symbol, -.hljs-bullet, -.hljs-link { - color: #00b0e8; -} - - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/vs2015.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/vs2015.css deleted file mode 100644 index d1d9be3caa..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/vs2015.css +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Visual Studio 2015 dark style - * Author: Nicolas LLOBERA - */ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #1E1E1E; - color: #DCDCDC; -} - -.hljs-keyword, -.hljs-literal, -.hljs-symbol, -.hljs-name { - color: #569CD6; -} -.hljs-link { - color: #569CD6; - text-decoration: underline; -} - -.hljs-built_in, -.hljs-type { - color: #4EC9B0; -} - -.hljs-number, -.hljs-class { - color: #B8D7A3; -} - -.hljs-string, -.hljs-meta-string { - color: #D69D85; -} - -.hljs-regexp, -.hljs-template-tag { - color: #9A5334; -} - -.hljs-subst, -.hljs-function, -.hljs-title, -.hljs-params, -.hljs-formula { - color: #DCDCDC; -} - -.hljs-comment, -.hljs-quote { - color: #57A64A; - font-style: italic; -} - -.hljs-doctag { - color: #608B4E; -} - -.hljs-meta, -.hljs-meta-keyword, -.hljs-tag { - color: #9B9B9B; -} - -.hljs-variable, -.hljs-template-variable { - color: #BD63C5; -} - -.hljs-attr, -.hljs-attribute, -.hljs-builtin-name { - color: #9CDCFE; -} - -.hljs-section { - color: gold; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} - -/*.hljs-code { - font-family:'Monospace'; -}*/ - -.hljs-bullet, -.hljs-selector-tag, -.hljs-selector-id, -.hljs-selector-class, -.hljs-selector-attr, -.hljs-selector-pseudo { - color: #D7BA7D; -} - -.hljs-addition { - background-color: #144212; - display: inline-block; - width: 100%; -} - -.hljs-deletion { - background-color: #600; - display: inline-block; - width: 100%; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/xcode.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/xcode.css deleted file mode 100644 index 43dddad84d..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/xcode.css +++ /dev/null @@ -1,93 +0,0 @@ -/* - -XCode style (c) Angel Garcia - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #fff; - color: black; -} - -.hljs-comment, -.hljs-quote { - color: #006a00; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-literal { - color: #aa0d91; -} - -.hljs-name { - color: #008; -} - -.hljs-variable, -.hljs-template-variable { - color: #660; -} - -.hljs-string { - color: #c41a16; -} - -.hljs-regexp, -.hljs-link { - color: #080; -} - -.hljs-title, -.hljs-tag, -.hljs-symbol, -.hljs-bullet, -.hljs-number, -.hljs-meta { - color: #1c00cf; -} - -.hljs-section, -.hljs-class .hljs-title, -.hljs-type, -.hljs-attr, -.hljs-built_in, -.hljs-builtin-name, -.hljs-params { - color: #5c2699; -} - -.hljs-attribute, -.hljs-subst { - color: #000; -} - -.hljs-formula { - background-color: #eee; - font-style: italic; -} - -.hljs-addition { - background-color: #baeeba; -} - -.hljs-deletion { - background-color: #ffc8bd; -} - -.hljs-selector-id, -.hljs-selector-class { - color: #9b703f; -} - -.hljs-doctag, -.hljs-strong { - font-weight: bold; -} - -.hljs-emphasis { - font-style: italic; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/xt256.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/xt256.css deleted file mode 100644 index 58df82cb75..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/xt256.css +++ /dev/null @@ -1,92 +0,0 @@ - -/* - xt256.css - - Contact: initbar [at] protonmail [dot] ch - : github.com/initbar -*/ - -.hljs { - display: block; - overflow-x: auto; - color: #eaeaea; - background: #000; - padding: 0.5; -} - -.hljs-subst { - color: #eaeaea; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} - -.hljs-builtin-name, -.hljs-type { - color: #eaeaea; -} - -.hljs-params { - color: #da0000; -} - -.hljs-literal, -.hljs-number, -.hljs-name { - color: #ff0000; - font-weight: bolder; -} - -.hljs-comment { - color: #969896; -} - -.hljs-selector-id, -.hljs-quote { - color: #00ffff; -} - -.hljs-template-variable, -.hljs-variable, -.hljs-title { - color: #00ffff; - font-weight: bold; -} - -.hljs-selector-class, -.hljs-keyword, -.hljs-symbol { - color: #fff000; -} - -.hljs-string, -.hljs-bullet { - color: #00ff00; -} - -.hljs-tag, -.hljs-section { - color: #000fff; -} - -.hljs-selector-tag { - color: #000fff; - font-weight: bold; -} - -.hljs-attribute, -.hljs-built_in, -.hljs-regexp, -.hljs-link { - color: #ff00ff; -} - -.hljs-meta { - color: #fff; - font-weight: bolder; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/zenburn.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/zenburn.css deleted file mode 100644 index 07be502016..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/highlight.js/styles/zenburn.css +++ /dev/null @@ -1,80 +0,0 @@ -/* - -Zenburn style from voldmar.ru (c) Vladimir Epifanov -based on dark.css by Ivan Sagalaev - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - background: #3f3f3f; - color: #dcdcdc; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-tag { - color: #e3ceab; -} - -.hljs-template-tag { - color: #dcdcdc; -} - -.hljs-number { - color: #8cd0d3; -} - -.hljs-variable, -.hljs-template-variable, -.hljs-attribute { - color: #efdcbc; -} - -.hljs-literal { - color: #efefaf; -} - -.hljs-subst { - color: #8f8f8f; -} - -.hljs-title, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class, -.hljs-section, -.hljs-type { - color: #efef8f; -} - -.hljs-symbol, -.hljs-bullet, -.hljs-link { - color: #dca3a3; -} - -.hljs-deletion, -.hljs-string, -.hljs-built_in, -.hljs-builtin-name { - color: #cc9393; -} - -.hljs-addition, -.hljs-comment, -.hljs-quote, -.hljs-meta { - color: #7f9f7f; -} - - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/jquery.validate.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/jquery.validate.js deleted file mode 100644 index f526d50252..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/jquery.validate.js +++ /dev/null @@ -1,1650 +0,0 @@ -/*! - * jQuery Validation Plugin v1.19.0 - * - * https://jqueryvalidation.org/ - * - * Copyright (c) 2018 Jörn Zaefferer - * Released under the MIT license - */ -(function( factory ) { - if ( typeof define === "function" && define.amd ) { - define( ["jquery"], factory ); - } else if (typeof module === "object" && module.exports) { - module.exports = factory( require( "jquery" ) ); - } else { - factory( jQuery ); - } -}(function( $ ) { - -$.extend( $.fn, { - - // https://jqueryvalidation.org/validate/ - validate: function( options ) { - - // If nothing is selected, return nothing; can't chain anyway - if ( !this.length ) { - if ( options && options.debug && window.console ) { - console.warn( "Nothing selected, can't validate, returning nothing." ); - } - return; - } - - // Check if a validator for this form was already created - var validator = $.data( this[ 0 ], "validator" ); - if ( validator ) { - return validator; - } - - // Add novalidate tag if HTML5. - this.attr( "novalidate", "novalidate" ); - - validator = new $.validator( options, this[ 0 ] ); - $.data( this[ 0 ], "validator", validator ); - - if ( validator.settings.onsubmit ) { - - this.on( "click.validate", ":submit", function( event ) { - - // Track the used submit button to properly handle scripted - // submits later. - validator.submitButton = event.currentTarget; - - // Allow suppressing validation by adding a cancel class to the submit button - if ( $( this ).hasClass( "cancel" ) ) { - validator.cancelSubmit = true; - } - - // Allow suppressing validation by adding the html5 formnovalidate attribute to the submit button - if ( $( this ).attr( "formnovalidate" ) !== undefined ) { - validator.cancelSubmit = true; - } - } ); - - // Validate the form on submit - this.on( "submit.validate", function( event ) { - if ( validator.settings.debug ) { - - // Prevent form submit to be able to see console output - event.preventDefault(); - } - - function handle() { - var hidden, result; - - // Insert a hidden input as a replacement for the missing submit button - // The hidden input is inserted in two cases: - // - A user defined a `submitHandler` - // - There was a pending request due to `remote` method and `stopRequest()` - // was called to submit the form in case it's valid - if ( validator.submitButton && ( validator.settings.submitHandler || validator.formSubmitted ) ) { - hidden = $( "" ) - .attr( "name", validator.submitButton.name ) - .val( $( validator.submitButton ).val() ) - .appendTo( validator.currentForm ); - } - - if ( validator.settings.submitHandler && !validator.settings.debug ) { - result = validator.settings.submitHandler.call( validator, validator.currentForm, event ); - if ( hidden ) { - - // And clean up afterwards; thanks to no-block-scope, hidden can be referenced - hidden.remove(); - } - if ( result !== undefined ) { - return result; - } - return false; - } - return true; - } - - // Prevent submit for invalid forms or custom submit handlers - if ( validator.cancelSubmit ) { - validator.cancelSubmit = false; - return handle(); - } - if ( validator.form() ) { - if ( validator.pendingRequest ) { - validator.formSubmitted = true; - return false; - } - return handle(); - } else { - validator.focusInvalid(); - return false; - } - } ); - } - - return validator; - }, - - // https://jqueryvalidation.org/valid/ - valid: function() { - var valid, validator, errorList; - - if ( $( this[ 0 ] ).is( "form" ) ) { - valid = this.validate().form(); - } else { - errorList = []; - valid = true; - validator = $( this[ 0 ].form ).validate(); - this.each( function() { - valid = validator.element( this ) && valid; - if ( !valid ) { - errorList = errorList.concat( validator.errorList ); - } - } ); - validator.errorList = errorList; - } - return valid; - }, - - // https://jqueryvalidation.org/rules/ - rules: function( command, argument ) { - var element = this[ 0 ], - isContentEditable = typeof this.attr( "contenteditable" ) !== "undefined" && this.attr( "contenteditable" ) !== "false", - settings, staticRules, existingRules, data, param, filtered; - - // If nothing is selected, return empty object; can't chain anyway - if ( element == null ) { - return; - } - - if ( !element.form && isContentEditable ) { - element.form = this.closest( "form" )[ 0 ]; - element.name = this.attr( "name" ); - } - - if ( element.form == null ) { - return; - } - - if ( command ) { - settings = $.data( element.form, "validator" ).settings; - staticRules = settings.rules; - existingRules = $.validator.staticRules( element ); - switch ( command ) { - case "add": - $.extend( existingRules, $.validator.normalizeRule( argument ) ); - - // Remove messages from rules, but allow them to be set separately - delete existingRules.messages; - staticRules[ element.name ] = existingRules; - if ( argument.messages ) { - settings.messages[ element.name ] = $.extend( settings.messages[ element.name ], argument.messages ); - } - break; - case "remove": - if ( !argument ) { - delete staticRules[ element.name ]; - return existingRules; - } - filtered = {}; - $.each( argument.split( /\s/ ), function( index, method ) { - filtered[ method ] = existingRules[ method ]; - delete existingRules[ method ]; - } ); - return filtered; - } - } - - data = $.validator.normalizeRules( - $.extend( - {}, - $.validator.classRules( element ), - $.validator.attributeRules( element ), - $.validator.dataRules( element ), - $.validator.staticRules( element ) - ), element ); - - // Make sure required is at front - if ( data.required ) { - param = data.required; - delete data.required; - data = $.extend( { required: param }, data ); - } - - // Make sure remote is at back - if ( data.remote ) { - param = data.remote; - delete data.remote; - data = $.extend( data, { remote: param } ); - } - - return data; - } -} ); - -// Custom selectors -$.extend( $.expr.pseudos || $.expr[ ":" ], { // '|| $.expr[ ":" ]' here enables backwards compatibility to jQuery 1.7. Can be removed when dropping jQ 1.7.x support - - // https://jqueryvalidation.org/blank-selector/ - blank: function( a ) { - return !$.trim( "" + $( a ).val() ); - }, - - // https://jqueryvalidation.org/filled-selector/ - filled: function( a ) { - var val = $( a ).val(); - return val !== null && !!$.trim( "" + val ); - }, - - // https://jqueryvalidation.org/unchecked-selector/ - unchecked: function( a ) { - return !$( a ).prop( "checked" ); - } -} ); - -// Constructor for validator -$.validator = function( options, form ) { - this.settings = $.extend( true, {}, $.validator.defaults, options ); - this.currentForm = form; - this.init(); -}; - -// https://jqueryvalidation.org/jQuery.validator.format/ -$.validator.format = function( source, params ) { - if ( arguments.length === 1 ) { - return function() { - var args = $.makeArray( arguments ); - args.unshift( source ); - return $.validator.format.apply( this, args ); - }; - } - if ( params === undefined ) { - return source; - } - if ( arguments.length > 2 && params.constructor !== Array ) { - params = $.makeArray( arguments ).slice( 1 ); - } - if ( params.constructor !== Array ) { - params = [ params ]; - } - $.each( params, function( i, n ) { - source = source.replace( new RegExp( "\\{" + i + "\\}", "g" ), function() { - return n; - } ); - } ); - return source; -}; - -$.extend( $.validator, { - - defaults: { - messages: {}, - groups: {}, - rules: {}, - errorClass: "error", - pendingClass: "pending", - validClass: "valid", - errorElement: "label", - focusCleanup: false, - focusInvalid: true, - errorContainer: $( [] ), - errorLabelContainer: $( [] ), - onsubmit: true, - ignore: ":hidden", - ignoreTitle: false, - onfocusin: function( element ) { - this.lastActive = element; - - // Hide error label and remove error class on focus if enabled - if ( this.settings.focusCleanup ) { - if ( this.settings.unhighlight ) { - this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass ); - } - this.hideThese( this.errorsFor( element ) ); - } - }, - onfocusout: function( element ) { - if ( !this.checkable( element ) && ( element.name in this.submitted || !this.optional( element ) ) ) { - this.element( element ); - } - }, - onkeyup: function( element, event ) { - - // Avoid revalidate the field when pressing one of the following keys - // Shift => 16 - // Ctrl => 17 - // Alt => 18 - // Caps lock => 20 - // End => 35 - // Home => 36 - // Left arrow => 37 - // Up arrow => 38 - // Right arrow => 39 - // Down arrow => 40 - // Insert => 45 - // Num lock => 144 - // AltGr key => 225 - var excludedKeys = [ - 16, 17, 18, 20, 35, 36, 37, - 38, 39, 40, 45, 144, 225 - ]; - - if ( event.which === 9 && this.elementValue( element ) === "" || $.inArray( event.keyCode, excludedKeys ) !== -1 ) { - return; - } else if ( element.name in this.submitted || element.name in this.invalid ) { - this.element( element ); - } - }, - onclick: function( element ) { - - // Click on selects, radiobuttons and checkboxes - if ( element.name in this.submitted ) { - this.element( element ); - - // Or option elements, check parent select in that case - } else if ( element.parentNode.name in this.submitted ) { - this.element( element.parentNode ); - } - }, - highlight: function( element, errorClass, validClass ) { - if ( element.type === "radio" ) { - this.findByName( element.name ).addClass( errorClass ).removeClass( validClass ); - } else { - $( element ).addClass( errorClass ).removeClass( validClass ); - } - }, - unhighlight: function( element, errorClass, validClass ) { - if ( element.type === "radio" ) { - this.findByName( element.name ).removeClass( errorClass ).addClass( validClass ); - } else { - $( element ).removeClass( errorClass ).addClass( validClass ); - } - } - }, - - // https://jqueryvalidation.org/jQuery.validator.setDefaults/ - setDefaults: function( settings ) { - $.extend( $.validator.defaults, settings ); - }, - - messages: { - required: "This field is required.", - remote: "Please fix this field.", - email: "Please enter a valid email address.", - url: "Please enter a valid URL.", - date: "Please enter a valid date.", - dateISO: "Please enter a valid date (ISO).", - number: "Please enter a valid number.", - digits: "Please enter only digits.", - equalTo: "Please enter the same value again.", - maxlength: $.validator.format( "Please enter no more than {0} characters." ), - minlength: $.validator.format( "Please enter at least {0} characters." ), - rangelength: $.validator.format( "Please enter a value between {0} and {1} characters long." ), - range: $.validator.format( "Please enter a value between {0} and {1}." ), - max: $.validator.format( "Please enter a value less than or equal to {0}." ), - min: $.validator.format( "Please enter a value greater than or equal to {0}." ), - step: $.validator.format( "Please enter a multiple of {0}." ) - }, - - autoCreateRanges: false, - - prototype: { - - init: function() { - this.labelContainer = $( this.settings.errorLabelContainer ); - this.errorContext = this.labelContainer.length && this.labelContainer || $( this.currentForm ); - this.containers = $( this.settings.errorContainer ).add( this.settings.errorLabelContainer ); - this.submitted = {}; - this.valueCache = {}; - this.pendingRequest = 0; - this.pending = {}; - this.invalid = {}; - this.reset(); - - var currentForm = this.currentForm, - groups = ( this.groups = {} ), - rules; - $.each( this.settings.groups, function( key, value ) { - if ( typeof value === "string" ) { - value = value.split( /\s/ ); - } - $.each( value, function( index, name ) { - groups[ name ] = key; - } ); - } ); - rules = this.settings.rules; - $.each( rules, function( key, value ) { - rules[ key ] = $.validator.normalizeRule( value ); - } ); - - function delegate( event ) { - var isContentEditable = typeof $( this ).attr( "contenteditable" ) !== "undefined" && $( this ).attr( "contenteditable" ) !== "false"; - - // Set form expando on contenteditable - if ( !this.form && isContentEditable ) { - this.form = $( this ).closest( "form" )[ 0 ]; - this.name = $( this ).attr( "name" ); - } - - // Ignore the element if it belongs to another form. This will happen mainly - // when setting the `form` attribute of an input to the id of another form. - if ( currentForm !== this.form ) { - return; - } - - var validator = $.data( this.form, "validator" ), - eventType = "on" + event.type.replace( /^validate/, "" ), - settings = validator.settings; - if ( settings[ eventType ] && !$( this ).is( settings.ignore ) ) { - settings[ eventType ].call( validator, this, event ); - } - } - - $( this.currentForm ) - .on( "focusin.validate focusout.validate keyup.validate", - ":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'], " + - "[type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], " + - "[type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], " + - "[type='radio'], [type='checkbox'], [contenteditable], [type='button']", delegate ) - - // Support: Chrome, oldIE - // "select" is provided as event.target when clicking a option - .on( "click.validate", "select, option, [type='radio'], [type='checkbox']", delegate ); - - if ( this.settings.invalidHandler ) { - $( this.currentForm ).on( "invalid-form.validate", this.settings.invalidHandler ); - } - }, - - // https://jqueryvalidation.org/Validator.form/ - form: function() { - this.checkForm(); - $.extend( this.submitted, this.errorMap ); - this.invalid = $.extend( {}, this.errorMap ); - if ( !this.valid() ) { - $( this.currentForm ).triggerHandler( "invalid-form", [ this ] ); - } - this.showErrors(); - return this.valid(); - }, - - checkForm: function() { - this.prepareForm(); - for ( var i = 0, elements = ( this.currentElements = this.elements() ); elements[ i ]; i++ ) { - this.check( elements[ i ] ); - } - return this.valid(); - }, - - // https://jqueryvalidation.org/Validator.element/ - element: function( element ) { - var cleanElement = this.clean( element ), - checkElement = this.validationTargetFor( cleanElement ), - v = this, - result = true, - rs, group; - - if ( checkElement === undefined ) { - delete this.invalid[ cleanElement.name ]; - } else { - this.prepareElement( checkElement ); - this.currentElements = $( checkElement ); - - // If this element is grouped, then validate all group elements already - // containing a value - group = this.groups[ checkElement.name ]; - if ( group ) { - $.each( this.groups, function( name, testgroup ) { - if ( testgroup === group && name !== checkElement.name ) { - cleanElement = v.validationTargetFor( v.clean( v.findByName( name ) ) ); - if ( cleanElement && cleanElement.name in v.invalid ) { - v.currentElements.push( cleanElement ); - result = v.check( cleanElement ) && result; - } - } - } ); - } - - rs = this.check( checkElement ) !== false; - result = result && rs; - if ( rs ) { - this.invalid[ checkElement.name ] = false; - } else { - this.invalid[ checkElement.name ] = true; - } - - if ( !this.numberOfInvalids() ) { - - // Hide error containers on last error - this.toHide = this.toHide.add( this.containers ); - } - this.showErrors(); - - // Add aria-invalid status for screen readers - $( element ).attr( "aria-invalid", !rs ); - } - - return result; - }, - - // https://jqueryvalidation.org/Validator.showErrors/ - showErrors: function( errors ) { - if ( errors ) { - var validator = this; - - // Add items to error list and map - $.extend( this.errorMap, errors ); - this.errorList = $.map( this.errorMap, function( message, name ) { - return { - message: message, - element: validator.findByName( name )[ 0 ] - }; - } ); - - // Remove items from success list - this.successList = $.grep( this.successList, function( element ) { - return !( element.name in errors ); - } ); - } - if ( this.settings.showErrors ) { - this.settings.showErrors.call( this, this.errorMap, this.errorList ); - } else { - this.defaultShowErrors(); - } - }, - - // https://jqueryvalidation.org/Validator.resetForm/ - resetForm: function() { - if ( $.fn.resetForm ) { - $( this.currentForm ).resetForm(); - } - this.invalid = {}; - this.submitted = {}; - this.prepareForm(); - this.hideErrors(); - var elements = this.elements() - .removeData( "previousValue" ) - .removeAttr( "aria-invalid" ); - - this.resetElements( elements ); - }, - - resetElements: function( elements ) { - var i; - - if ( this.settings.unhighlight ) { - for ( i = 0; elements[ i ]; i++ ) { - this.settings.unhighlight.call( this, elements[ i ], - this.settings.errorClass, "" ); - this.findByName( elements[ i ].name ).removeClass( this.settings.validClass ); - } - } else { - elements - .removeClass( this.settings.errorClass ) - .removeClass( this.settings.validClass ); - } - }, - - numberOfInvalids: function() { - return this.objectLength( this.invalid ); - }, - - objectLength: function( obj ) { - /* jshint unused: false */ - var count = 0, - i; - for ( i in obj ) { - - // This check allows counting elements with empty error - // message as invalid elements - if ( obj[ i ] !== undefined && obj[ i ] !== null && obj[ i ] !== false ) { - count++; - } - } - return count; - }, - - hideErrors: function() { - this.hideThese( this.toHide ); - }, - - hideThese: function( errors ) { - errors.not( this.containers ).text( "" ); - this.addWrapper( errors ).hide(); - }, - - valid: function() { - return this.size() === 0; - }, - - size: function() { - return this.errorList.length; - }, - - focusInvalid: function() { - if ( this.settings.focusInvalid ) { - try { - $( this.findLastActive() || this.errorList.length && this.errorList[ 0 ].element || [] ) - .filter( ":visible" ) - .focus() - - // Manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find - .trigger( "focusin" ); - } catch ( e ) { - - // Ignore IE throwing errors when focusing hidden elements - } - } - }, - - findLastActive: function() { - var lastActive = this.lastActive; - return lastActive && $.grep( this.errorList, function( n ) { - return n.element.name === lastActive.name; - } ).length === 1 && lastActive; - }, - - elements: function() { - var validator = this, - rulesCache = {}; - - // Select all valid inputs inside the form (no submit or reset buttons) - return $( this.currentForm ) - .find( "input, select, textarea, [contenteditable]" ) - .not( ":submit, :reset, :image, :disabled" ) - .not( this.settings.ignore ) - .filter( function() { - var name = this.name || $( this ).attr( "name" ); // For contenteditable - var isContentEditable = typeof $( this ).attr( "contenteditable" ) !== "undefined" && $( this ).attr( "contenteditable" ) !== "false"; - - if ( !name && validator.settings.debug && window.console ) { - console.error( "%o has no name assigned", this ); - } - - // Set form expando on contenteditable - if ( isContentEditable ) { - this.form = $( this ).closest( "form" )[ 0 ]; - this.name = name; - } - - // Ignore elements that belong to other/nested forms - if ( this.form !== validator.currentForm ) { - return false; - } - - // Select only the first element for each name, and only those with rules specified - if ( name in rulesCache || !validator.objectLength( $( this ).rules() ) ) { - return false; - } - - rulesCache[ name ] = true; - return true; - } ); - }, - - clean: function( selector ) { - return $( selector )[ 0 ]; - }, - - errors: function() { - var errorClass = this.settings.errorClass.split( " " ).join( "." ); - return $( this.settings.errorElement + "." + errorClass, this.errorContext ); - }, - - resetInternals: function() { - this.successList = []; - this.errorList = []; - this.errorMap = {}; - this.toShow = $( [] ); - this.toHide = $( [] ); - }, - - reset: function() { - this.resetInternals(); - this.currentElements = $( [] ); - }, - - prepareForm: function() { - this.reset(); - this.toHide = this.errors().add( this.containers ); - }, - - prepareElement: function( element ) { - this.reset(); - this.toHide = this.errorsFor( element ); - }, - - elementValue: function( element ) { - var $element = $( element ), - type = element.type, - isContentEditable = typeof $element.attr( "contenteditable" ) !== "undefined" && $element.attr( "contenteditable" ) !== "false", - val, idx; - - if ( type === "radio" || type === "checkbox" ) { - return this.findByName( element.name ).filter( ":checked" ).val(); - } else if ( type === "number" && typeof element.validity !== "undefined" ) { - return element.validity.badInput ? "NaN" : $element.val(); - } - - if ( isContentEditable ) { - val = $element.text(); - } else { - val = $element.val(); - } - - if ( type === "file" ) { - - // Modern browser (chrome & safari) - if ( val.substr( 0, 12 ) === "C:\\fakepath\\" ) { - return val.substr( 12 ); - } - - // Legacy browsers - // Unix-based path - idx = val.lastIndexOf( "/" ); - if ( idx >= 0 ) { - return val.substr( idx + 1 ); - } - - // Windows-based path - idx = val.lastIndexOf( "\\" ); - if ( idx >= 0 ) { - return val.substr( idx + 1 ); - } - - // Just the file name - return val; - } - - if ( typeof val === "string" ) { - return val.replace( /\r/g, "" ); - } - return val; - }, - - check: function( element ) { - element = this.validationTargetFor( this.clean( element ) ); - - var rules = $( element ).rules(), - rulesCount = $.map( rules, function( n, i ) { - return i; - } ).length, - dependencyMismatch = false, - val = this.elementValue( element ), - result, method, rule, normalizer; - - // Prioritize the local normalizer defined for this element over the global one - // if the former exists, otherwise user the global one in case it exists. - if ( typeof rules.normalizer === "function" ) { - normalizer = rules.normalizer; - } else if ( typeof this.settings.normalizer === "function" ) { - normalizer = this.settings.normalizer; - } - - // If normalizer is defined, then call it to retreive the changed value instead - // of using the real one. - // Note that `this` in the normalizer is `element`. - if ( normalizer ) { - val = normalizer.call( element, val ); - - // Delete the normalizer from rules to avoid treating it as a pre-defined method. - delete rules.normalizer; - } - - for ( method in rules ) { - rule = { method: method, parameters: rules[ method ] }; - try { - result = $.validator.methods[ method ].call( this, val, element, rule.parameters ); - - // If a method indicates that the field is optional and therefore valid, - // don't mark it as valid when there are no other rules - if ( result === "dependency-mismatch" && rulesCount === 1 ) { - dependencyMismatch = true; - continue; - } - dependencyMismatch = false; - - if ( result === "pending" ) { - this.toHide = this.toHide.not( this.errorsFor( element ) ); - return; - } - - if ( !result ) { - this.formatAndAdd( element, rule ); - return false; - } - } catch ( e ) { - if ( this.settings.debug && window.console ) { - console.log( "Exception occurred when checking element " + element.id + ", check the '" + rule.method + "' method.", e ); - } - if ( e instanceof TypeError ) { - e.message += ". Exception occurred when checking element " + element.id + ", check the '" + rule.method + "' method."; - } - - throw e; - } - } - if ( dependencyMismatch ) { - return; - } - if ( this.objectLength( rules ) ) { - this.successList.push( element ); - } - return true; - }, - - // Return the custom message for the given element and validation method - // specified in the element's HTML5 data attribute - // return the generic message if present and no method specific message is present - customDataMessage: function( element, method ) { - return $( element ).data( "msg" + method.charAt( 0 ).toUpperCase() + - method.substring( 1 ).toLowerCase() ) || $( element ).data( "msg" ); - }, - - // Return the custom message for the given element name and validation method - customMessage: function( name, method ) { - var m = this.settings.messages[ name ]; - return m && ( m.constructor === String ? m : m[ method ] ); - }, - - // Return the first defined argument, allowing empty strings - findDefined: function() { - for ( var i = 0; i < arguments.length; i++ ) { - if ( arguments[ i ] !== undefined ) { - return arguments[ i ]; - } - } - return undefined; - }, - - // The second parameter 'rule' used to be a string, and extended to an object literal - // of the following form: - // rule = { - // method: "method name", - // parameters: "the given method parameters" - // } - // - // The old behavior still supported, kept to maintain backward compatibility with - // old code, and will be removed in the next major release. - defaultMessage: function( element, rule ) { - if ( typeof rule === "string" ) { - rule = { method: rule }; - } - - var message = this.findDefined( - this.customMessage( element.name, rule.method ), - this.customDataMessage( element, rule.method ), - - // 'title' is never undefined, so handle empty string as undefined - !this.settings.ignoreTitle && element.title || undefined, - $.validator.messages[ rule.method ], - "Warning: No message defined for " + element.name + "" - ), - theregex = /\$?\{(\d+)\}/g; - if ( typeof message === "function" ) { - message = message.call( this, rule.parameters, element ); - } else if ( theregex.test( message ) ) { - message = $.validator.format( message.replace( theregex, "{$1}" ), rule.parameters ); - } - - return message; - }, - - formatAndAdd: function( element, rule ) { - var message = this.defaultMessage( element, rule ); - - this.errorList.push( { - message: message, - element: element, - method: rule.method - } ); - - this.errorMap[ element.name ] = message; - this.submitted[ element.name ] = message; - }, - - addWrapper: function( toToggle ) { - if ( this.settings.wrapper ) { - toToggle = toToggle.add( toToggle.parent( this.settings.wrapper ) ); - } - return toToggle; - }, - - defaultShowErrors: function() { - var i, elements, error; - for ( i = 0; this.errorList[ i ]; i++ ) { - error = this.errorList[ i ]; - if ( this.settings.highlight ) { - this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass ); - } - this.showLabel( error.element, error.message ); - } - if ( this.errorList.length ) { - this.toShow = this.toShow.add( this.containers ); - } - if ( this.settings.success ) { - for ( i = 0; this.successList[ i ]; i++ ) { - this.showLabel( this.successList[ i ] ); - } - } - if ( this.settings.unhighlight ) { - for ( i = 0, elements = this.validElements(); elements[ i ]; i++ ) { - this.settings.unhighlight.call( this, elements[ i ], this.settings.errorClass, this.settings.validClass ); - } - } - this.toHide = this.toHide.not( this.toShow ); - this.hideErrors(); - this.addWrapper( this.toShow ).show(); - }, - - validElements: function() { - return this.currentElements.not( this.invalidElements() ); - }, - - invalidElements: function() { - return $( this.errorList ).map( function() { - return this.element; - } ); - }, - - showLabel: function( element, message ) { - var place, group, errorID, v, - error = this.errorsFor( element ), - elementID = this.idOrName( element ), - describedBy = $( element ).attr( "aria-describedby" ); - - if ( error.length ) { - - // Refresh error/success class - error.removeClass( this.settings.validClass ).addClass( this.settings.errorClass ); - - // Replace message on existing label - error.html( message ); - } else { - - // Create error element - error = $( "<" + this.settings.errorElement + ">" ) - .attr( "id", elementID + "-error" ) - .addClass( this.settings.errorClass ) - .html( message || "" ); - - // Maintain reference to the element to be placed into the DOM - place = error; - if ( this.settings.wrapper ) { - - // Make sure the element is visible, even in IE - // actually showing the wrapped element is handled elsewhere - place = error.hide().show().wrap( "<" + this.settings.wrapper + "/>" ).parent(); - } - if ( this.labelContainer.length ) { - this.labelContainer.append( place ); - } else if ( this.settings.errorPlacement ) { - this.settings.errorPlacement.call( this, place, $( element ) ); - } else { - place.insertAfter( element ); - } - - // Link error back to the element - if ( error.is( "label" ) ) { - - // If the error is a label, then associate using 'for' - error.attr( "for", elementID ); - - // If the element is not a child of an associated label, then it's necessary - // to explicitly apply aria-describedby - } else if ( error.parents( "label[for='" + this.escapeCssMeta( elementID ) + "']" ).length === 0 ) { - errorID = error.attr( "id" ); - - // Respect existing non-error aria-describedby - if ( !describedBy ) { - describedBy = errorID; - } else if ( !describedBy.match( new RegExp( "\\b" + this.escapeCssMeta( errorID ) + "\\b" ) ) ) { - - // Add to end of list if not already present - describedBy += " " + errorID; - } - $( element ).attr( "aria-describedby", describedBy ); - - // If this element is grouped, then assign to all elements in the same group - group = this.groups[ element.name ]; - if ( group ) { - v = this; - $.each( v.groups, function( name, testgroup ) { - if ( testgroup === group ) { - $( "[name='" + v.escapeCssMeta( name ) + "']", v.currentForm ) - .attr( "aria-describedby", error.attr( "id" ) ); - } - } ); - } - } - } - if ( !message && this.settings.success ) { - error.text( "" ); - if ( typeof this.settings.success === "string" ) { - error.addClass( this.settings.success ); - } else { - this.settings.success( error, element ); - } - } - this.toShow = this.toShow.add( error ); - }, - - errorsFor: function( element ) { - var name = this.escapeCssMeta( this.idOrName( element ) ), - describer = $( element ).attr( "aria-describedby" ), - selector = "label[for='" + name + "'], label[for='" + name + "'] *"; - - // 'aria-describedby' should directly reference the error element - if ( describer ) { - selector = selector + ", #" + this.escapeCssMeta( describer ) - .replace( /\s+/g, ", #" ); - } - - return this - .errors() - .filter( selector ); - }, - - // See https://api.jquery.com/category/selectors/, for CSS - // meta-characters that should be escaped in order to be used with JQuery - // as a literal part of a name/id or any selector. - escapeCssMeta: function( string ) { - return string.replace( /([\\!"#$%&'()*+,./:;<=>?@\[\]^`{|}~])/g, "\\$1" ); - }, - - idOrName: function( element ) { - return this.groups[ element.name ] || ( this.checkable( element ) ? element.name : element.id || element.name ); - }, - - validationTargetFor: function( element ) { - - // If radio/checkbox, validate first element in group instead - if ( this.checkable( element ) ) { - element = this.findByName( element.name ); - } - - // Always apply ignore filter - return $( element ).not( this.settings.ignore )[ 0 ]; - }, - - checkable: function( element ) { - return ( /radio|checkbox/i ).test( element.type ); - }, - - findByName: function( name ) { - return $( this.currentForm ).find( "[name='" + this.escapeCssMeta( name ) + "']" ); - }, - - getLength: function( value, element ) { - switch ( element.nodeName.toLowerCase() ) { - case "select": - return $( "option:selected", element ).length; - case "input": - if ( this.checkable( element ) ) { - return this.findByName( element.name ).filter( ":checked" ).length; - } - } - return value.length; - }, - - depend: function( param, element ) { - return this.dependTypes[ typeof param ] ? this.dependTypes[ typeof param ]( param, element ) : true; - }, - - dependTypes: { - "boolean": function( param ) { - return param; - }, - "string": function( param, element ) { - return !!$( param, element.form ).length; - }, - "function": function( param, element ) { - return param( element ); - } - }, - - optional: function( element ) { - var val = this.elementValue( element ); - return !$.validator.methods.required.call( this, val, element ) && "dependency-mismatch"; - }, - - startRequest: function( element ) { - if ( !this.pending[ element.name ] ) { - this.pendingRequest++; - $( element ).addClass( this.settings.pendingClass ); - this.pending[ element.name ] = true; - } - }, - - stopRequest: function( element, valid ) { - this.pendingRequest--; - - // Sometimes synchronization fails, make sure pendingRequest is never < 0 - if ( this.pendingRequest < 0 ) { - this.pendingRequest = 0; - } - delete this.pending[ element.name ]; - $( element ).removeClass( this.settings.pendingClass ); - if ( valid && this.pendingRequest === 0 && this.formSubmitted && this.form() ) { - $( this.currentForm ).submit(); - - // Remove the hidden input that was used as a replacement for the - // missing submit button. The hidden input is added by `handle()` - // to ensure that the value of the used submit button is passed on - // for scripted submits triggered by this method - if ( this.submitButton ) { - $( "input:hidden[name='" + this.submitButton.name + "']", this.currentForm ).remove(); - } - - this.formSubmitted = false; - } else if ( !valid && this.pendingRequest === 0 && this.formSubmitted ) { - $( this.currentForm ).triggerHandler( "invalid-form", [ this ] ); - this.formSubmitted = false; - } - }, - - previousValue: function( element, method ) { - method = typeof method === "string" && method || "remote"; - - return $.data( element, "previousValue" ) || $.data( element, "previousValue", { - old: null, - valid: true, - message: this.defaultMessage( element, { method: method } ) - } ); - }, - - // Cleans up all forms and elements, removes validator-specific events - destroy: function() { - this.resetForm(); - - $( this.currentForm ) - .off( ".validate" ) - .removeData( "validator" ) - .find( ".validate-equalTo-blur" ) - .off( ".validate-equalTo" ) - .removeClass( "validate-equalTo-blur" ) - .find( ".validate-lessThan-blur" ) - .off( ".validate-lessThan" ) - .removeClass( "validate-lessThan-blur" ) - .find( ".validate-lessThanEqual-blur" ) - .off( ".validate-lessThanEqual" ) - .removeClass( "validate-lessThanEqual-blur" ) - .find( ".validate-greaterThanEqual-blur" ) - .off( ".validate-greaterThanEqual" ) - .removeClass( "validate-greaterThanEqual-blur" ) - .find( ".validate-greaterThan-blur" ) - .off( ".validate-greaterThan" ) - .removeClass( "validate-greaterThan-blur" ); - } - - }, - - classRuleSettings: { - required: { required: true }, - email: { email: true }, - url: { url: true }, - date: { date: true }, - dateISO: { dateISO: true }, - number: { number: true }, - digits: { digits: true }, - creditcard: { creditcard: true } - }, - - addClassRules: function( className, rules ) { - if ( className.constructor === String ) { - this.classRuleSettings[ className ] = rules; - } else { - $.extend( this.classRuleSettings, className ); - } - }, - - classRules: function( element ) { - var rules = {}, - classes = $( element ).attr( "class" ); - - if ( classes ) { - $.each( classes.split( " " ), function() { - if ( this in $.validator.classRuleSettings ) { - $.extend( rules, $.validator.classRuleSettings[ this ] ); - } - } ); - } - return rules; - }, - - normalizeAttributeRule: function( rules, type, method, value ) { - - // Convert the value to a number for number inputs, and for text for backwards compability - // allows type="date" and others to be compared as strings - if ( /min|max|step/.test( method ) && ( type === null || /number|range|text/.test( type ) ) ) { - value = Number( value ); - - // Support Opera Mini, which returns NaN for undefined minlength - if ( isNaN( value ) ) { - value = undefined; - } - } - - if ( value || value === 0 ) { - rules[ method ] = value; - } else if ( type === method && type !== "range" ) { - - // Exception: the jquery validate 'range' method - // does not test for the html5 'range' type - rules[ method ] = true; - } - }, - - attributeRules: function( element ) { - var rules = {}, - $element = $( element ), - type = element.getAttribute( "type" ), - method, value; - - for ( method in $.validator.methods ) { - - // Support for in both html5 and older browsers - if ( method === "required" ) { - value = element.getAttribute( method ); - - // Some browsers return an empty string for the required attribute - // and non-HTML5 browsers might have required="" markup - if ( value === "" ) { - value = true; - } - - // Force non-HTML5 browsers to return bool - value = !!value; - } else { - value = $element.attr( method ); - } - - this.normalizeAttributeRule( rules, type, method, value ); - } - - // 'maxlength' may be returned as -1, 2147483647 ( IE ) and 524288 ( safari ) for text inputs - if ( rules.maxlength && /-1|2147483647|524288/.test( rules.maxlength ) ) { - delete rules.maxlength; - } - - return rules; - }, - - dataRules: function( element ) { - var rules = {}, - $element = $( element ), - type = element.getAttribute( "type" ), - method, value; - - for ( method in $.validator.methods ) { - value = $element.data( "rule" + method.charAt( 0 ).toUpperCase() + method.substring( 1 ).toLowerCase() ); - - // Cast empty attributes like `data-rule-required` to `true` - if ( value === "" ) { - value = true; - } - - this.normalizeAttributeRule( rules, type, method, value ); - } - return rules; - }, - - staticRules: function( element ) { - var rules = {}, - validator = $.data( element.form, "validator" ); - - if ( validator.settings.rules ) { - rules = $.validator.normalizeRule( validator.settings.rules[ element.name ] ) || {}; - } - return rules; - }, - - normalizeRules: function( rules, element ) { - - // Handle dependency check - $.each( rules, function( prop, val ) { - - // Ignore rule when param is explicitly false, eg. required:false - if ( val === false ) { - delete rules[ prop ]; - return; - } - if ( val.param || val.depends ) { - var keepRule = true; - switch ( typeof val.depends ) { - case "string": - keepRule = !!$( val.depends, element.form ).length; - break; - case "function": - keepRule = val.depends.call( element, element ); - break; - } - if ( keepRule ) { - rules[ prop ] = val.param !== undefined ? val.param : true; - } else { - $.data( element.form, "validator" ).resetElements( $( element ) ); - delete rules[ prop ]; - } - } - } ); - - // Evaluate parameters - $.each( rules, function( rule, parameter ) { - rules[ rule ] = $.isFunction( parameter ) && rule !== "normalizer" ? parameter( element ) : parameter; - } ); - - // Clean number parameters - $.each( [ "minlength", "maxlength" ], function() { - if ( rules[ this ] ) { - rules[ this ] = Number( rules[ this ] ); - } - } ); - $.each( [ "rangelength", "range" ], function() { - var parts; - if ( rules[ this ] ) { - if ( $.isArray( rules[ this ] ) ) { - rules[ this ] = [ Number( rules[ this ][ 0 ] ), Number( rules[ this ][ 1 ] ) ]; - } else if ( typeof rules[ this ] === "string" ) { - parts = rules[ this ].replace( /[\[\]]/g, "" ).split( /[\s,]+/ ); - rules[ this ] = [ Number( parts[ 0 ] ), Number( parts[ 1 ] ) ]; - } - } - } ); - - if ( $.validator.autoCreateRanges ) { - - // Auto-create ranges - if ( rules.min != null && rules.max != null ) { - rules.range = [ rules.min, rules.max ]; - delete rules.min; - delete rules.max; - } - if ( rules.minlength != null && rules.maxlength != null ) { - rules.rangelength = [ rules.minlength, rules.maxlength ]; - delete rules.minlength; - delete rules.maxlength; - } - } - - return rules; - }, - - // Converts a simple string to a {string: true} rule, e.g., "required" to {required:true} - normalizeRule: function( data ) { - if ( typeof data === "string" ) { - var transformed = {}; - $.each( data.split( /\s/ ), function() { - transformed[ this ] = true; - } ); - data = transformed; - } - return data; - }, - - // https://jqueryvalidation.org/jQuery.validator.addMethod/ - addMethod: function( name, method, message ) { - $.validator.methods[ name ] = method; - $.validator.messages[ name ] = message !== undefined ? message : $.validator.messages[ name ]; - if ( method.length < 3 ) { - $.validator.addClassRules( name, $.validator.normalizeRule( name ) ); - } - }, - - // https://jqueryvalidation.org/jQuery.validator.methods/ - methods: { - - // https://jqueryvalidation.org/required-method/ - required: function( value, element, param ) { - - // Check if dependency is met - if ( !this.depend( param, element ) ) { - return "dependency-mismatch"; - } - if ( element.nodeName.toLowerCase() === "select" ) { - - // Could be an array for select-multiple or a string, both are fine this way - var val = $( element ).val(); - return val && val.length > 0; - } - if ( this.checkable( element ) ) { - return this.getLength( value, element ) > 0; - } - return value !== undefined && value !== null && value.length > 0; - }, - - // https://jqueryvalidation.org/email-method/ - email: function( value, element ) { - - // From https://html.spec.whatwg.org/multipage/forms.html#valid-e-mail-address - // Retrieved 2014-01-14 - // If you have a problem with this implementation, report a bug against the above spec - // Or use custom methods to implement your own email validation - return this.optional( element ) || /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test( value ); - }, - - // https://jqueryvalidation.org/url-method/ - url: function( value, element ) { - - // Copyright (c) 2010-2013 Diego Perini, MIT licensed - // https://gist.github.com/dperini/729294 - // see also https://mathiasbynens.be/demo/url-regex - // modified to allow protocol-relative URLs - return this.optional( element ) || /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test( value ); - }, - - // https://jqueryvalidation.org/date-method/ - date: ( function() { - var called = false; - - return function( value, element ) { - if ( !called ) { - called = true; - if ( this.settings.debug && window.console ) { - console.warn( - "The `date` method is deprecated and will be removed in version '2.0.0'.\n" + - "Please don't use it, since it relies on the Date constructor, which\n" + - "behaves very differently across browsers and locales. Use `dateISO`\n" + - "instead or one of the locale specific methods in `localizations/`\n" + - "and `additional-methods.js`." - ); - } - } - - return this.optional( element ) || !/Invalid|NaN/.test( new Date( value ).toString() ); - }; - }() ), - - // https://jqueryvalidation.org/dateISO-method/ - dateISO: function( value, element ) { - return this.optional( element ) || /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test( value ); - }, - - // https://jqueryvalidation.org/number-method/ - number: function( value, element ) { - return this.optional( element ) || /^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test( value ); - }, - - // https://jqueryvalidation.org/digits-method/ - digits: function( value, element ) { - return this.optional( element ) || /^\d+$/.test( value ); - }, - - // https://jqueryvalidation.org/minlength-method/ - minlength: function( value, element, param ) { - var length = $.isArray( value ) ? value.length : this.getLength( value, element ); - return this.optional( element ) || length >= param; - }, - - // https://jqueryvalidation.org/maxlength-method/ - maxlength: function( value, element, param ) { - var length = $.isArray( value ) ? value.length : this.getLength( value, element ); - return this.optional( element ) || length <= param; - }, - - // https://jqueryvalidation.org/rangelength-method/ - rangelength: function( value, element, param ) { - var length = $.isArray( value ) ? value.length : this.getLength( value, element ); - return this.optional( element ) || ( length >= param[ 0 ] && length <= param[ 1 ] ); - }, - - // https://jqueryvalidation.org/min-method/ - min: function( value, element, param ) { - return this.optional( element ) || value >= param; - }, - - // https://jqueryvalidation.org/max-method/ - max: function( value, element, param ) { - return this.optional( element ) || value <= param; - }, - - // https://jqueryvalidation.org/range-method/ - range: function( value, element, param ) { - return this.optional( element ) || ( value >= param[ 0 ] && value <= param[ 1 ] ); - }, - - // https://jqueryvalidation.org/step-method/ - step: function( value, element, param ) { - var type = $( element ).attr( "type" ), - errorMessage = "Step attribute on input type " + type + " is not supported.", - supportedTypes = [ "text", "number", "range" ], - re = new RegExp( "\\b" + type + "\\b" ), - notSupported = type && !re.test( supportedTypes.join() ), - decimalPlaces = function( num ) { - var match = ( "" + num ).match( /(?:\.(\d+))?$/ ); - if ( !match ) { - return 0; - } - - // Number of digits right of decimal point. - return match[ 1 ] ? match[ 1 ].length : 0; - }, - toInt = function( num ) { - return Math.round( num * Math.pow( 10, decimals ) ); - }, - valid = true, - decimals; - - // Works only for text, number and range input types - // TODO find a way to support input types date, datetime, datetime-local, month, time and week - if ( notSupported ) { - throw new Error( errorMessage ); - } - - decimals = decimalPlaces( param ); - - // Value can't have too many decimals - if ( decimalPlaces( value ) > decimals || toInt( value ) % toInt( param ) !== 0 ) { - valid = false; - } - - return this.optional( element ) || valid; - }, - - // https://jqueryvalidation.org/equalTo-method/ - equalTo: function( value, element, param ) { - - // Bind to the blur event of the target in order to revalidate whenever the target field is updated - var target = $( param ); - if ( this.settings.onfocusout && target.not( ".validate-equalTo-blur" ).length ) { - target.addClass( "validate-equalTo-blur" ).on( "blur.validate-equalTo", function() { - $( element ).valid(); - } ); - } - return value === target.val(); - }, - - // https://jqueryvalidation.org/remote-method/ - remote: function( value, element, param, method ) { - if ( this.optional( element ) ) { - return "dependency-mismatch"; - } - - method = typeof method === "string" && method || "remote"; - - var previous = this.previousValue( element, method ), - validator, data, optionDataString; - - if ( !this.settings.messages[ element.name ] ) { - this.settings.messages[ element.name ] = {}; - } - previous.originalMessage = previous.originalMessage || this.settings.messages[ element.name ][ method ]; - this.settings.messages[ element.name ][ method ] = previous.message; - - param = typeof param === "string" && { url: param } || param; - optionDataString = $.param( $.extend( { data: value }, param.data ) ); - if ( previous.old === optionDataString ) { - return previous.valid; - } - - previous.old = optionDataString; - validator = this; - this.startRequest( element ); - data = {}; - data[ element.name ] = value; - $.ajax( $.extend( true, { - mode: "abort", - port: "validate" + element.name, - dataType: "json", - data: data, - context: validator.currentForm, - success: function( response ) { - var valid = response === true || response === "true", - errors, message, submitted; - - validator.settings.messages[ element.name ][ method ] = previous.originalMessage; - if ( valid ) { - submitted = validator.formSubmitted; - validator.resetInternals(); - validator.toHide = validator.errorsFor( element ); - validator.formSubmitted = submitted; - validator.successList.push( element ); - validator.invalid[ element.name ] = false; - validator.showErrors(); - } else { - errors = {}; - message = response || validator.defaultMessage( element, { method: method, parameters: value } ); - errors[ element.name ] = previous.message = message; - validator.invalid[ element.name ] = true; - validator.showErrors( errors ); - } - previous.valid = valid; - validator.stopRequest( element, valid ); - } - }, param ) ); - return "pending"; - } - } - -} ); - -// Ajax mode: abort -// usage: $.ajax({ mode: "abort"[, port: "uniqueport"]}); -// if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort() - -var pendingRequests = {}, - ajax; - -// Use a prefilter if available (1.5+) -if ( $.ajaxPrefilter ) { - $.ajaxPrefilter( function( settings, _, xhr ) { - var port = settings.port; - if ( settings.mode === "abort" ) { - if ( pendingRequests[ port ] ) { - pendingRequests[ port ].abort(); - } - pendingRequests[ port ] = xhr; - } - } ); -} else { - - // Proxy ajax - ajax = $.ajax; - $.ajax = function( settings ) { - var mode = ( "mode" in settings ? settings : $.ajaxSettings ).mode, - port = ( "port" in settings ? settings : $.ajaxSettings ).port; - if ( mode === "abort" ) { - if ( pendingRequests[ port ] ) { - pendingRequests[ port ].abort(); - } - pendingRequests[ port ] = ajax.apply( this, arguments ); - return pendingRequests[ port ]; - } - return ajax.apply( this, arguments ); - }; -} -return $; -})); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ar.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ar.min.js deleted file mode 100644 index 146d7edb78..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ar.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"هذا الحقل إلزامي",remote:"يرجى تصحيح هذا الحقل للمتابعة",email:"رجاء إدخال عنوان بريد إلكتروني صحيح",url:"رجاء إدخال عنوان موقع إلكتروني صحيح",date:"رجاء إدخال تاريخ صحيح",dateISO:"رجاء إدخال تاريخ صحيح (ISO)",number:"رجاء إدخال عدد بطريقة صحيحة",digits:"رجاء إدخال أرقام فقط",creditcard:"رجاء إدخال رقم بطاقة ائتمان صحيح",equalTo:"رجاء إدخال نفس القيمة",extension:"رجاء إدخال ملف بامتداد موافق عليه",maxlength:a.validator.format("الحد الأقصى لعدد الحروف هو {0}"),minlength:a.validator.format("الحد الأدنى لعدد الحروف هو {0}"),rangelength:a.validator.format("عدد الحروف يجب أن يكون بين {0} و {1}"),range:a.validator.format("رجاء إدخال عدد قيمته بين {0} و {1}"),max:a.validator.format("رجاء إدخال عدد أقل من أو يساوي {0}"),min:a.validator.format("رجاء إدخال عدد أكبر من أو يساوي {0}")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_az.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_az.min.js deleted file mode 100644 index e31d25455e..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_az.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Bu xana mütləq doldurulmalıdır.",remote:"Zəhmət olmasa, düzgün məna daxil edin.",email:"Zəhmət olmasa, düzgün elektron poçt daxil edin.",url:"Zəhmət olmasa, düzgün URL daxil edin.",date:"Zəhmət olmasa, düzgün tarix daxil edin.",dateISO:"Zəhmət olmasa, düzgün ISO formatlı tarix daxil edin.",number:"Zəhmət olmasa, düzgün rəqəm daxil edin.",digits:"Zəhmət olmasa, yalnız rəqəm daxil edin.",creditcard:"Zəhmət olmasa, düzgün kredit kart nömrəsini daxil edin.",equalTo:"Zəhmət olmasa, eyni mənanı bir daha daxil edin.",extension:"Zəhmət olmasa, düzgün genişlənməyə malik faylı seçin.",maxlength:a.validator.format("Zəhmət olmasa, {0} simvoldan çox olmayaraq daxil edin."),minlength:a.validator.format("Zəhmət olmasa, {0} simvoldan az olmayaraq daxil edin."),rangelength:a.validator.format("Zəhmət olmasa, {0} - {1} aralığında uzunluğa malik simvol daxil edin."),range:a.validator.format("Zəhmət olmasa, {0} - {1} aralığında rəqəm daxil edin."),max:a.validator.format("Zəhmət olmasa, {0} və ondan kiçik rəqəm daxil edin."),min:a.validator.format("Zəhmət olmasa, {0} və ondan böyük rəqəm daxil edin")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_bg.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_bg.js deleted file mode 100644 index c725eeadb5..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_bg.js +++ /dev/null @@ -1,35 +0,0 @@ -(function( factory ) { - if ( typeof define === "function" && define.amd ) { - define( ["jquery", "../jquery.validate"], factory ); - } else if (typeof module === "object" && module.exports) { - module.exports = factory( require( "jquery" ) ); - } else { - factory( jQuery ); - } -}(function( $ ) { - -/* - * Translated default messages for the jQuery validation plugin. - * Locale: BG (Bulgarian; български език) - */ -$.extend( $.validator.messages, { - required: "Полето е задължително.", - remote: "Моля, въведете правилната стойност.", - email: "Моля, въведете валиден email.", - url: "Моля, въведете валидно URL.", - date: "Моля, въведете валидна дата.", - dateISO: "Моля, въведете валидна дата (ISO).", - number: "Моля, въведете валиден номер.", - digits: "Моля, въведете само цифри.", - creditcard: "Моля, въведете валиден номер на кредитна карта.", - equalTo: "Моля, въведете същата стойност отново.", - extension: "Моля, въведете стойност с валидно разширение.", - maxlength: $.validator.format( "Моля, въведете не повече от {0} символа." ), - minlength: $.validator.format( "Моля, въведете поне {0} символа." ), - rangelength: $.validator.format( "Моля, въведете стойност с дължина между {0} и {1} символа." ), - range: $.validator.format( "Моля, въведете стойност между {0} и {1}." ), - max: $.validator.format( "Моля, въведете стойност по-малка или равна на {0}." ), - min: $.validator.format( "Моля, въведете стойност по-голяма или равна на {0}." ) -} ); -return $; -})); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_bg.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_bg.min.js deleted file mode 100644 index 1ea5fe095d..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_bg.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Полето е задължително.",remote:"Моля, въведете правилната стойност.",email:"Моля, въведете валиден email.",url:"Моля, въведете валидно URL.",date:"Моля, въведете валидна дата.",dateISO:"Моля, въведете валидна дата (ISO).",number:"Моля, въведете валиден номер.",digits:"Моля, въведете само цифри.",creditcard:"Моля, въведете валиден номер на кредитна карта.",equalTo:"Моля, въведете същата стойност отново.",extension:"Моля, въведете стойност с валидно разширение.",maxlength:a.validator.format("Моля, въведете не повече от {0} символа."),minlength:a.validator.format("Моля, въведете поне {0} символа."),rangelength:a.validator.format("Моля, въведете стойност с дължина между {0} и {1} символа."),range:a.validator.format("Моля, въведете стойност между {0} и {1}."),max:a.validator.format("Моля, въведете стойност по-малка или равна на {0}."),min:a.validator.format("Моля, въведете стойност по-голяма или равна на {0}.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_bn_BD.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_bn_BD.min.js deleted file mode 100644 index c6d336d704..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_bn_BD.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"এই তথ্যটি আবশ্যক।",remote:"এই তথ্যটি ঠিক করুন।",email:"অনুগ্রহ করে একটি সঠিক মেইল ঠিকানা লিখুন।",url:"অনুগ্রহ করে একটি সঠিক লিঙ্ক দিন।",date:"তারিখ সঠিক নয়।",dateISO:"অনুগ্রহ করে একটি সঠিক (ISO) তারিখ লিখুন।",number:"অনুগ্রহ করে একটি সঠিক নম্বর লিখুন।",digits:"এখানে শুধু সংখ্যা ব্যবহার করা যাবে।",creditcard:"অনুগ্রহ করে একটি ক্রেডিট কার্ডের সঠিক নম্বর লিখুন।",equalTo:"একই মান আবার লিখুন।",extension:"সঠিক ধরনের ফাইল আপলোড করুন।",maxlength:a.validator.format("{0}টির বেশি অক্ষর লেখা যাবে না।"),minlength:a.validator.format("{0}টির কম অক্ষর লেখা যাবে না।"),rangelength:a.validator.format("{0} থেকে {1} টি অক্ষর সম্বলিত মান লিখুন।"),range:a.validator.format("{0} থেকে {1} এর মধ্যে একটি মান ব্যবহার করুন।"),max:a.validator.format("অনুগ্রহ করে {0} বা তার চাইতে কম মান ব্যবহার করুন।"),min:a.validator.format("অনুগ্রহ করে {0} বা তার চাইতে বেশি মান ব্যবহার করুন।")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ca.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ca.min.js deleted file mode 100644 index affe49c71e..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ca.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Aquest camp és obligatori.",remote:"Si us plau, omple aquest camp.",email:"Si us plau, escriu una adreça de correu-e vàlida",url:"Si us plau, escriu una URL vàlida.",date:"Si us plau, escriu una data vàlida.",dateISO:"Si us plau, escriu una data (ISO) vàlida.",number:"Si us plau, escriu un número enter vàlid.",digits:"Si us plau, escriu només dígits.",creditcard:"Si us plau, escriu un número de tarjeta vàlid.",equalTo:"Si us plau, escriu el mateix valor de nou.",extension:"Si us plau, escriu un valor amb una extensió acceptada.",maxlength:a.validator.format("Si us plau, no escriguis més de {0} caracters."),minlength:a.validator.format("Si us plau, no escriguis menys de {0} caracters."),rangelength:a.validator.format("Si us plau, escriu un valor entre {0} i {1} caracters."),range:a.validator.format("Si us plau, escriu un valor entre {0} i {1}."),max:a.validator.format("Si us plau, escriu un valor menor o igual a {0}."),min:a.validator.format("Si us plau, escriu un valor major o igual a {0}.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_cs.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_cs.js deleted file mode 100644 index 2ab4b51668..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_cs.js +++ /dev/null @@ -1,36 +0,0 @@ -(function( factory ) { - if ( typeof define === "function" && define.amd ) { - define( ["jquery", "../jquery.validate"], factory ); - } else if (typeof module === "object" && module.exports) { - module.exports = factory( require( "jquery" ) ); - } else { - factory( jQuery ); - } -}(function( $ ) { - -/* - * Translated default messages for the jQuery validation plugin. - * Locale: CS (Czech; čeština, český jazyk) - */ -$.extend( $.validator.messages, { - required: "Tento údaj je povinný.", - remote: "Prosím, opravte tento údaj.", - email: "Prosím, zadejte platný e-mail.", - url: "Prosím, zadejte platné URL.", - date: "Prosím, zadejte platné datum.", - dateISO: "Prosím, zadejte platné datum (ISO).", - number: "Prosím, zadejte číslo.", - digits: "Prosím, zadávejte pouze číslice.", - creditcard: "Prosím, zadejte číslo kreditní karty.", - equalTo: "Prosím, zadejte znovu stejnou hodnotu.", - extension: "Prosím, zadejte soubor se správnou příponou.", - maxlength: $.validator.format( "Prosím, zadejte nejvíce {0} znaků." ), - minlength: $.validator.format( "Prosím, zadejte nejméně {0} znaků." ), - rangelength: $.validator.format( "Prosím, zadejte od {0} do {1} znaků." ), - range: $.validator.format( "Prosím, zadejte hodnotu od {0} do {1}." ), - max: $.validator.format( "Prosím, zadejte hodnotu menší nebo rovnu {0}." ), - min: $.validator.format( "Prosím, zadejte hodnotu větší nebo rovnu {0}." ), - step: $.validator.format( "Musí být násobkem čísla {0}." ) -} ); -return $; -})); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_cs.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_cs.min.js deleted file mode 100644 index 9415db241b..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_cs.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Tento údaj je povinný.",remote:"Prosím, opravte tento údaj.",email:"Prosím, zadejte platný e-mail.",url:"Prosím, zadejte platné URL.",date:"Prosím, zadejte platné datum.",dateISO:"Prosím, zadejte platné datum (ISO).",number:"Prosím, zadejte číslo.",digits:"Prosím, zadávejte pouze číslice.",creditcard:"Prosím, zadejte číslo kreditní karty.",equalTo:"Prosím, zadejte znovu stejnou hodnotu.",extension:"Prosím, zadejte soubor se správnou příponou.",maxlength:a.validator.format("Prosím, zadejte nejvíce {0} znaků."),minlength:a.validator.format("Prosím, zadejte nejméně {0} znaků."),rangelength:a.validator.format("Prosím, zadejte od {0} do {1} znaků."),range:a.validator.format("Prosím, zadejte hodnotu od {0} do {1}."),max:a.validator.format("Prosím, zadejte hodnotu menší nebo rovnu {0}."),min:a.validator.format("Prosím, zadejte hodnotu větší nebo rovnu {0}."),step:a.validator.format("Musí být násobkem čísla {0}.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_da.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_da.js deleted file mode 100644 index 302775c0fb..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_da.js +++ /dev/null @@ -1,46 +0,0 @@ -(function( factory ) { - if ( typeof define === "function" && define.amd ) { - define( ["jquery", "../jquery.validate"], factory ); - } else if (typeof module === "object" && module.exports) { - module.exports = factory( require( "jquery" ) ); - } else { - factory( jQuery ); - } -}(function( $ ) { - -/* - * Translated default messages for the jQuery validation plugin. - * Locale: DA (Danish; dansk) - */ -$.extend( $.validator.messages, { - required: "Dette felt er påkrævet.", - remote: "Ret venligst dette felt", - email: "Indtast en gyldig email-adresse.", - url: "Indtast en gyldig URL.", - date: "Indtast en gyldig dato.", - number: "Indtast et tal.", - digits: "Indtast kun cifre.", - creditcard: "Indtast et gyldigt kreditkortnummer.", - equalTo: "Indtast den samme værdi igen.", - time: "Angiv en gyldig tid mellem kl. 00:00 og 23:59.", - ipv4: "Angiv venligst en gyldig IPv4-adresse.", - ipv6: "Angiv venligst en gyldig IPv6-adresse.", - require_from_group: $.validator.format( "Angiv mindst {0} af disse felter." ), - extension: "Indtast venligst en værdi med en gyldig endelse", - pattern: "Ugyldigt format", - lettersonly: "Angiv venligst kun bogstaver.", - nowhitespace: "Må ikke indholde mellemrum", - maxlength: $.validator.format( "Indtast højst {0} tegn." ), - minlength: $.validator.format( "Indtast mindst {0} tegn." ), - rangelength: $.validator.format( "Indtast mindst {0} og højst {1} tegn." ), - range: $.validator.format( "Angiv en værdi mellem {0} og {1}." ), - max: $.validator.format( "Angiv en værdi der højst er {0}." ), - min: $.validator.format( "Angiv en værdi der mindst er {0}." ), - minWords: $.validator.format( "Indtast venligst mindst {0} ord" ), - maxWords: $.validator.format( "Indtast venligst højst {0} ord" ), - step: $.validator.format( "Angiv en værdi gange {0}." ), - notEqualTo: "Angiv en anden værdi, værdierne må ikke være det samme.", - integer: "Angiv et ikke-decimaltal, der er positivt eller negativt." -} ); -return $; -})); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_da.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_da.min.js deleted file mode 100644 index 6ddcd487f6..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_da.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Dette felt er påkrævet.",remote:"Ret venligst dette felt",email:"Indtast en gyldig email-adresse.",url:"Indtast en gyldig URL.",date:"Indtast en gyldig dato.",number:"Indtast et tal.",digits:"Indtast kun cifre.",creditcard:"Indtast et gyldigt kreditkortnummer.",equalTo:"Indtast den samme værdi igen.",time:"Angiv en gyldig tid mellem kl. 00:00 og 23:59.",ipv4:"Angiv venligst en gyldig IPv4-adresse.",ipv6:"Angiv venligst en gyldig IPv6-adresse.",require_from_group:a.validator.format("Angiv mindst {0} af disse felter."),extension:"Indtast venligst en værdi med en gyldig endelse",pattern:"Ugyldigt format",lettersonly:"Angiv venligst kun bogstaver.",nowhitespace:"Må ikke indholde mellemrum",maxlength:a.validator.format("Indtast højst {0} tegn."),minlength:a.validator.format("Indtast mindst {0} tegn."),rangelength:a.validator.format("Indtast mindst {0} og højst {1} tegn."),range:a.validator.format("Angiv en værdi mellem {0} og {1}."),max:a.validator.format("Angiv en værdi der højst er {0}."),min:a.validator.format("Angiv en værdi der mindst er {0}."),minWords:a.validator.format("Indtast venligst mindst {0} ord"),maxWords:a.validator.format("Indtast venligst højst {0} ord"),step:a.validator.format("Angiv en værdi gange {0}."),notEqualTo:"Angiv en anden værdi, værdierne må ikke være det samme.",integer:"Angiv et ikke-decimaltal, der er positivt eller negativt."}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_de.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_de.js deleted file mode 100644 index 611c16f71f..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_de.js +++ /dev/null @@ -1,82 +0,0 @@ -(function( factory ) { - if ( typeof define === "function" && define.amd ) { - define( ["jquery", "../jquery.validate"], factory ); - } else if (typeof module === "object" && module.exports) { - module.exports = factory( require( "jquery" ) ); - } else { - factory( jQuery ); - } -}(function( $ ) { - -/* - * Translated default messages for the jQuery validation plugin. - * Locale: DE (German, Deutsch) - */ -$.extend( $.validator.messages, { - required: "Dieses Feld ist ein Pflichtfeld.", - maxlength: $.validator.format( "Geben Sie bitte maximal {0} Zeichen ein." ), - minlength: $.validator.format( "Geben Sie bitte mindestens {0} Zeichen ein." ), - rangelength: $.validator.format( "Geben Sie bitte mindestens {0} und maximal {1} Zeichen ein." ), - email: "Geben Sie bitte eine gültige E-Mail-Adresse ein.", - url: "Geben Sie bitte eine gültige URL ein.", - date: "Geben Sie bitte ein gültiges Datum ein.", - number: "Geben Sie bitte eine Nummer ein.", - digits: "Geben Sie bitte nur Ziffern ein.", - equalTo: "Wiederholen Sie bitte denselben Wert.", - range: $.validator.format( "Geben Sie bitte einen Wert zwischen {0} und {1} ein." ), - max: $.validator.format( "Geben Sie bitte einen Wert kleiner oder gleich {0} ein." ), - min: $.validator.format( "Geben Sie bitte einen Wert größer oder gleich {0} ein." ), - creditcard: "Geben Sie bitte eine gültige Kreditkarten-Nummer ein.", - remote: "Korrigieren Sie bitte dieses Feld.", - dateISO: "Geben Sie bitte ein gültiges Datum ein (ISO-Format).", - step: $.validator.format( "Geben Sie bitte ein Vielfaches von {0} ein." ), - maxWords: $.validator.format( "Geben Sie bitte {0} Wörter oder weniger ein." ), - minWords: $.validator.format( "Geben Sie bitte mindestens {0} Wörter ein." ), - rangeWords: $.validator.format( "Geben Sie bitte zwischen {0} und {1} Wörtern ein." ), - accept: "Geben Sie bitte einen Wert mit einem gültigen MIME-Typ ein.", - alphanumeric: "Geben Sie bitte nur Buchstaben (keine Umlaute), Zahlen oder Unterstriche ein.", - bankaccountNL: "Geben Sie bitte eine gültige Kontonummer ein.", - bankorgiroaccountNL: "Geben Sie bitte eine gültige Bank- oder Girokontonummer ein.", - bic: "Geben Sie bitte einen gültigen BIC-Code ein.", - cifES: "Geben Sie bitte eine gültige CIF-Nummer ein.", - cpfBR: "Geben Sie bitte eine gültige CPF-Nummer ein.", - creditcardtypes: "Geben Sie bitte eine gültige Kreditkarten-Nummer ein.", - currency: "Geben Sie bitte eine gültige Währung ein.", - extension: "Geben Sie bitte einen Wert mit einer gültigen Erweiterung ein.", - giroaccountNL: "Geben Sie bitte eine gültige Girokontonummer ein.", - iban: "Geben Sie bitte eine gültige IBAN ein.", - integer: "Geben Sie bitte eine positive oder negative Nicht-Dezimalzahl ein.", - ipv4: "Geben Sie bitte eine gültige IPv4-Adresse ein.", - ipv6: "Geben Sie bitte eine gültige IPv6-Adresse ein.", - lettersonly: "Geben Sie bitte nur Buchstaben ein.", - letterswithbasicpunc: "Geben Sie bitte nur Buchstaben oder Interpunktion ein.", - mobileNL: "Geben Sie bitte eine gültige Handynummer ein.", - mobileUK: "Geben Sie bitte eine gültige Handynummer ein.", - netmask: "Geben Sie bitte eine gültige Netzmaske ein.", - nieES: "Geben Sie bitte eine gültige NIE-Nummer ein.", - nifES: "Geben Sie bitte eine gültige NIF-Nummer ein.", - nipPL: "Geben Sie bitte eine gültige NIP-Nummer ein.", - notEqualTo: "Geben Sie bitte einen anderen Wert ein. Die Werte dürfen nicht gleich sein.", - nowhitespace: "Kein Leerzeichen bitte.", - pattern: "Ungültiges Format.", - phoneNL: "Geben Sie bitte eine gültige Telefonnummer ein.", - phonesUK: "Geben Sie bitte eine gültige britische Telefonnummer ein.", - phoneUK: "Geben Sie bitte eine gültige Telefonnummer ein.", - phoneUS: "Geben Sie bitte eine gültige Telefonnummer ein.", - postalcodeBR: "Geben Sie bitte eine gültige brasilianische Postleitzahl ein.", - postalCodeCA: "Geben Sie bitte eine gültige kanadische Postleitzahl ein.", - postalcodeIT: "Geben Sie bitte eine gültige italienische Postleitzahl ein.", - postalcodeNL: "Geben Sie bitte eine gültige niederländische Postleitzahl ein.", - postcodeUK: "Geben Sie bitte eine gültige britische Postleitzahl ein.", - require_from_group: $.validator.format( "Füllen Sie bitte mindestens {0} dieser Felder aus." ), - skip_or_fill_minimum: $.validator.format( "Überspringen Sie bitte diese Felder oder füllen Sie mindestens {0} von ihnen aus." ), - stateUS: "Geben Sie bitte einen gültigen US-Bundesstaat ein.", - strippedminlength: $.validator.format( "Geben Sie bitte mindestens {0} Zeichen ein." ), - time: "Geben Sie bitte eine gültige Uhrzeit zwischen 00:00 und 23:59 ein.", - time12h: "Geben Sie bitte eine gültige Uhrzeit im 12-Stunden-Format ein.", - vinUS: "Die angegebene Fahrzeugidentifikationsnummer (VIN) ist ungültig.", - zipcodeUS: "Die angegebene US-Postleitzahl ist ungültig.", - ziprange: "Ihre Postleitzahl muss im Bereich 902xx-xxxx bis 905xx-xxxx liegen." -} ); -return $; -})); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_de.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_de.min.js deleted file mode 100644 index 4e03c22a49..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_de.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Dieses Feld ist ein Pflichtfeld.",maxlength:a.validator.format("Geben Sie bitte maximal {0} Zeichen ein."),minlength:a.validator.format("Geben Sie bitte mindestens {0} Zeichen ein."),rangelength:a.validator.format("Geben Sie bitte mindestens {0} und maximal {1} Zeichen ein."),email:"Geben Sie bitte eine gültige E-Mail-Adresse ein.",url:"Geben Sie bitte eine gültige URL ein.",date:"Geben Sie bitte ein gültiges Datum ein.",number:"Geben Sie bitte eine Nummer ein.",digits:"Geben Sie bitte nur Ziffern ein.",equalTo:"Wiederholen Sie bitte denselben Wert.",range:a.validator.format("Geben Sie bitte einen Wert zwischen {0} und {1} ein."),max:a.validator.format("Geben Sie bitte einen Wert kleiner oder gleich {0} ein."),min:a.validator.format("Geben Sie bitte einen Wert größer oder gleich {0} ein."),creditcard:"Geben Sie bitte eine gültige Kreditkarten-Nummer ein.",remote:"Korrigieren Sie bitte dieses Feld.",dateISO:"Geben Sie bitte ein gültiges Datum ein (ISO-Format).",step:a.validator.format("Geben Sie bitte ein Vielfaches von {0} ein."),maxWords:a.validator.format("Geben Sie bitte {0} Wörter oder weniger ein."),minWords:a.validator.format("Geben Sie bitte mindestens {0} Wörter ein."),rangeWords:a.validator.format("Geben Sie bitte zwischen {0} und {1} Wörtern ein."),accept:"Geben Sie bitte einen Wert mit einem gültigen MIME-Typ ein.",alphanumeric:"Geben Sie bitte nur Buchstaben (keine Umlaute), Zahlen oder Unterstriche ein.",bankaccountNL:"Geben Sie bitte eine gültige Kontonummer ein.",bankorgiroaccountNL:"Geben Sie bitte eine gültige Bank- oder Girokontonummer ein.",bic:"Geben Sie bitte einen gültigen BIC-Code ein.",cifES:"Geben Sie bitte eine gültige CIF-Nummer ein.",cpfBR:"Geben Sie bitte eine gültige CPF-Nummer ein.",creditcardtypes:"Geben Sie bitte eine gültige Kreditkarten-Nummer ein.",currency:"Geben Sie bitte eine gültige Währung ein.",extension:"Geben Sie bitte einen Wert mit einer gültigen Erweiterung ein.",giroaccountNL:"Geben Sie bitte eine gültige Girokontonummer ein.",iban:"Geben Sie bitte eine gültige IBAN ein.",integer:"Geben Sie bitte eine positive oder negative Nicht-Dezimalzahl ein.",ipv4:"Geben Sie bitte eine gültige IPv4-Adresse ein.",ipv6:"Geben Sie bitte eine gültige IPv6-Adresse ein.",lettersonly:"Geben Sie bitte nur Buchstaben ein.",letterswithbasicpunc:"Geben Sie bitte nur Buchstaben oder Interpunktion ein.",mobileNL:"Geben Sie bitte eine gültige Handynummer ein.",mobileUK:"Geben Sie bitte eine gültige Handynummer ein.",netmask:"Geben Sie bitte eine gültige Netzmaske ein.",nieES:"Geben Sie bitte eine gültige NIE-Nummer ein.",nifES:"Geben Sie bitte eine gültige NIF-Nummer ein.",nipPL:"Geben Sie bitte eine gültige NIP-Nummer ein.",notEqualTo:"Geben Sie bitte einen anderen Wert ein. Die Werte dürfen nicht gleich sein.",nowhitespace:"Kein Leerzeichen bitte.",pattern:"Ungültiges Format.",phoneNL:"Geben Sie bitte eine gültige Telefonnummer ein.",phonesUK:"Geben Sie bitte eine gültige britische Telefonnummer ein.",phoneUK:"Geben Sie bitte eine gültige Telefonnummer ein.",phoneUS:"Geben Sie bitte eine gültige Telefonnummer ein.",postalcodeBR:"Geben Sie bitte eine gültige brasilianische Postleitzahl ein.",postalCodeCA:"Geben Sie bitte eine gültige kanadische Postleitzahl ein.",postalcodeIT:"Geben Sie bitte eine gültige italienische Postleitzahl ein.",postalcodeNL:"Geben Sie bitte eine gültige niederländische Postleitzahl ein.",postcodeUK:"Geben Sie bitte eine gültige britische Postleitzahl ein.",require_from_group:a.validator.format("Füllen Sie bitte mindestens {0} dieser Felder aus."),skip_or_fill_minimum:a.validator.format("Überspringen Sie bitte diese Felder oder füllen Sie mindestens {0} von ihnen aus."),stateUS:"Geben Sie bitte einen gültigen US-Bundesstaat ein.",strippedminlength:a.validator.format("Geben Sie bitte mindestens {0} Zeichen ein."),time:"Geben Sie bitte eine gültige Uhrzeit zwischen 00:00 und 23:59 ein.",time12h:"Geben Sie bitte eine gültige Uhrzeit im 12-Stunden-Format ein.",vinUS:"Die angegebene Fahrzeugidentifikationsnummer (VIN) ist ungültig.",zipcodeUS:"Die angegebene US-Postleitzahl ist ungültig.",ziprange:"Ihre Postleitzahl muss im Bereich 902xx-xxxx bis 905xx-xxxx liegen."}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_el.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_el.min.js deleted file mode 100644 index d3c1b4d7f0..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_el.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Αυτό το πεδίο είναι υποχρεωτικό.",remote:"Παρακαλώ διορθώστε αυτό το πεδίο.",email:"Παρακαλώ εισάγετε μια έγκυρη διεύθυνση email.",url:"Παρακαλώ εισάγετε ένα έγκυρο URL.",date:"Παρακαλώ εισάγετε μια έγκυρη ημερομηνία.",dateISO:"Παρακαλώ εισάγετε μια έγκυρη ημερομηνία (ISO).",number:"Παρακαλώ εισάγετε έναν έγκυρο αριθμό.",digits:"Παρακαλώ εισάγετε μόνο αριθμητικά ψηφία.",creditcard:"Παρακαλώ εισάγετε έναν έγκυρο αριθμό πιστωτικής κάρτας.",equalTo:"Παρακαλώ εισάγετε την ίδια τιμή ξανά.",extension:"Παρακαλώ εισάγετε μια τιμή με έγκυρη επέκταση αρχείου.",maxlength:a.validator.format("Παρακαλώ εισάγετε μέχρι και {0} χαρακτήρες."),minlength:a.validator.format("Παρακαλώ εισάγετε τουλάχιστον {0} χαρακτήρες."),rangelength:a.validator.format("Παρακαλώ εισάγετε μια τιμή με μήκος μεταξύ {0} και {1} χαρακτήρων."),range:a.validator.format("Παρακαλώ εισάγετε μια τιμή μεταξύ {0} και {1}."),max:a.validator.format("Παρακαλώ εισάγετε μια τιμή μικρότερη ή ίση του {0}."),min:a.validator.format("Παρακαλώ εισάγετε μια τιμή μεγαλύτερη ή ίση του {0}.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_es.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_es.min.js deleted file mode 100644 index ab0bb43dc2..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_es.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Este campo es obligatorio.",remote:"Por favor, rellena este campo.",email:"Por favor, escribe una dirección de correo válida.",url:"Por favor, escribe una URL válida.",date:"Por favor, escribe una fecha válida.",dateISO:"Por favor, escribe una fecha (ISO) válida.",number:"Por favor, escribe un número válido.",digits:"Por favor, escribe sólo dígitos.",creditcard:"Por favor, escribe un número de tarjeta válido.",equalTo:"Por favor, escribe el mismo valor de nuevo.",extension:"Por favor, escribe un valor con una extensión aceptada.",maxlength:a.validator.format("Por favor, no escribas más de {0} caracteres."),minlength:a.validator.format("Por favor, no escribas menos de {0} caracteres."),rangelength:a.validator.format("Por favor, escribe un valor entre {0} y {1} caracteres."),range:a.validator.format("Por favor, escribe un valor entre {0} y {1}."),max:a.validator.format("Por favor, escribe un valor menor o igual a {0}."),min:a.validator.format("Por favor, escribe un valor mayor o igual a {0}."),nifES:"Por favor, escribe un NIF válido.",nieES:"Por favor, escribe un NIE válido.",cifES:"Por favor, escribe un CIF válido."}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_es_AR.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_es_AR.min.js deleted file mode 100644 index 12996ac5c7..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_es_AR.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Este campo es obligatorio.",remote:"Por favor, completá este campo.",email:"Por favor, escribí una dirección de correo válida.",url:"Por favor, escribí una URL válida.",date:"Por favor, escribí una fecha válida.",dateISO:"Por favor, escribí una fecha (ISO) válida.",number:"Por favor, escribí un número entero válido.",digits:"Por favor, escribí sólo dígitos.",creditcard:"Por favor, escribí un número de tarjeta válido.",equalTo:"Por favor, escribí el mismo valor de nuevo.",extension:"Por favor, escribí un valor con una extensión aceptada.",maxlength:a.validator.format("Por favor, no escribas más de {0} caracteres."),minlength:a.validator.format("Por favor, no escribas menos de {0} caracteres."),rangelength:a.validator.format("Por favor, escribí un valor entre {0} y {1} caracteres."),range:a.validator.format("Por favor, escribí un valor entre {0} y {1}."),max:a.validator.format("Por favor, escribí un valor menor o igual a {0}."),min:a.validator.format("Por favor, escribí un valor mayor o igual a {0}."),nifES:"Por favor, escribí un NIF válido.",nieES:"Por favor, escribí un NIE válido.",cifES:"Por favor, escribí un CIF válido."}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_es_PE.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_es_PE.min.js deleted file mode 100644 index 5cfe40db72..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_es_PE.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Este campo es obligatorio.",remote:"Por favor, llene este campo.",email:"Por favor, escriba un correo electrónico válido.",url:"Por favor, escriba una URL válida.",date:"Por favor, escriba una fecha válida.",dateISO:"Por favor, escriba una fecha (ISO) válida.",number:"Por favor, escriba un número válido.",digits:"Por favor, escriba sólo dígitos.",creditcard:"Por favor, escriba un número de tarjeta válido.",equalTo:"Por favor, escriba el mismo valor de nuevo.",extension:"Por favor, escriba un valor con una extensión permitida.",maxlength:a.validator.format("Por favor, no escriba más de {0} caracteres."),minlength:a.validator.format("Por favor, no escriba menos de {0} caracteres."),rangelength:a.validator.format("Por favor, escriba un valor entre {0} y {1} caracteres."),range:a.validator.format("Por favor, escriba un valor entre {0} y {1}."),max:a.validator.format("Por favor, escriba un valor menor o igual a {0}."),min:a.validator.format("Por favor, escriba un valor mayor o igual a {0}."),nifES:"Por favor, escriba un NIF válido.",nieES:"Por favor, escriba un NIE válido.",cifES:"Por favor, escriba un CIF válido."}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_et.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_et.min.js deleted file mode 100644 index f5e7fe9905..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_et.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"See väli peab olema täidetud.",maxlength:a.validator.format("Palun sisestage vähem kui {0} tähemärki."),minlength:a.validator.format("Palun sisestage vähemalt {0} tähemärki."),rangelength:a.validator.format("Palun sisestage väärtus vahemikus {0} kuni {1} tähemärki."),email:"Palun sisestage korrektne e-maili aadress.",url:"Palun sisestage korrektne URL.",date:"Palun sisestage korrektne kuupäev.",dateISO:"Palun sisestage korrektne kuupäev (YYYY-MM-DD).",number:"Palun sisestage korrektne number.",digits:"Palun sisestage ainult numbreid.",equalTo:"Palun sisestage sama väärtus uuesti.",range:a.validator.format("Palun sisestage väärtus vahemikus {0} kuni {1}."),max:a.validator.format("Palun sisestage väärtus, mis on väiksem või võrdne arvuga {0}."),min:a.validator.format("Palun sisestage väärtus, mis on suurem või võrdne arvuga {0}."),creditcard:"Palun sisestage korrektne krediitkaardi number."}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_eu.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_eu.min.js deleted file mode 100644 index a172876dcd..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_eu.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Eremu hau beharrezkoa da.",remote:"Mesedez, bete eremu hau.",email:"Mesedez, idatzi baliozko posta helbide bat.",url:"Mesedez, idatzi baliozko URL bat.",date:"Mesedez, idatzi baliozko data bat.",dateISO:"Mesedez, idatzi baliozko (ISO) data bat.",number:"Mesedez, idatzi baliozko zenbaki oso bat.",digits:"Mesedez, idatzi digituak soilik.",creditcard:"Mesedez, idatzi baliozko txartel zenbaki bat.",equalTo:"Mesedez, idatzi berdina berriro ere.",extension:"Mesedez, idatzi onartutako luzapena duen balio bat.",maxlength:a.validator.format("Mesedez, ez idatzi {0} karaktere baino gehiago."),minlength:a.validator.format("Mesedez, ez idatzi {0} karaktere baino gutxiago."),rangelength:a.validator.format("Mesedez, idatzi {0} eta {1} karaktere arteko balio bat."),range:a.validator.format("Mesedez, idatzi {0} eta {1} arteko balio bat."),max:a.validator.format("Mesedez, idatzi {0} edo txikiagoa den balio bat."),min:a.validator.format("Mesedez, idatzi {0} edo handiagoa den balio bat.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_fa.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_fa.js deleted file mode 100644 index 0f5283b34a..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_fa.js +++ /dev/null @@ -1,39 +0,0 @@ -(function( factory ) { - if ( typeof define === "function" && define.amd ) { - define( ["jquery", "../jquery.validate"], factory ); - } else if (typeof module === "object" && module.exports) { - module.exports = factory( require( "jquery" ) ); - } else { - factory( jQuery ); - } -}(function( $ ) { - -/* - * Translated default messages for the jQuery validation plugin. - * Locale: FA (Persian; فارسی) - */ -$.extend( $.validator.messages, { - required: "تکمیل این فیلد اجباری است.", - remote: "لطفا این فیلد را تصحیح کنید.", - email: "لطفا یک ایمیل صحیح وارد کنید.", - url: "لطفا آدرس صحیح وارد کنید.", - date: "لطفا تاریخ صحیح وارد کنید.", - dateFA: "لطفا یک تاریخ صحیح وارد کنید.", - dateISO: "لطفا تاریخ صحیح وارد کنید (ISO).", - number: "لطفا عدد صحیح وارد کنید.", - digits: "لطفا تنها رقم وارد کنید.", - creditcard: "لطفا کریدیت کارت صحیح وارد کنید.", - equalTo: "لطفا مقدار برابری وارد کنید.", - extension: "لطفا مقداری وارد کنید که", - alphanumeric: "لطفا مقدار را عدد (انگلیسی) وارد کنید.", - maxlength: $.validator.format( "لطفا بیشتر از {0} حرف وارد نکنید." ), - minlength: $.validator.format( "لطفا کمتر از {0} حرف وارد نکنید." ), - rangelength: $.validator.format( "لطفا مقداری بین {0} تا {1} حرف وارد کنید." ), - range: $.validator.format( "لطفا مقداری بین {0} تا {1} حرف وارد کنید." ), - max: $.validator.format( "لطفا مقداری کمتر از {0} وارد کنید." ), - min: $.validator.format( "لطفا مقداری بیشتر از {0} وارد کنید." ), - minWords: $.validator.format( "لطفا حداقل {0} کلمه وارد کنید." ), - maxWords: $.validator.format( "لطفا حداکثر {0} کلمه وارد کنید." ) -} ); -return $; -})); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_fa.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_fa.min.js deleted file mode 100644 index eb1549a27a..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_fa.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"تکمیل این فیلد اجباری است.",remote:"لطفا این فیلد را تصحیح کنید.",email:"لطفا یک ایمیل صحیح وارد کنید.",url:"لطفا آدرس صحیح وارد کنید.",date:"لطفا تاریخ صحیح وارد کنید.",dateFA:"لطفا یک تاریخ صحیح وارد کنید.",dateISO:"لطفا تاریخ صحیح وارد کنید (ISO).",number:"لطفا عدد صحیح وارد کنید.",digits:"لطفا تنها رقم وارد کنید.",creditcard:"لطفا کریدیت کارت صحیح وارد کنید.",equalTo:"لطفا مقدار برابری وارد کنید.",extension:"لطفا مقداری وارد کنید که",alphanumeric:"لطفا مقدار را عدد (انگلیسی) وارد کنید.",maxlength:a.validator.format("لطفا بیشتر از {0} حرف وارد نکنید."),minlength:a.validator.format("لطفا کمتر از {0} حرف وارد نکنید."),rangelength:a.validator.format("لطفا مقداری بین {0} تا {1} حرف وارد کنید."),range:a.validator.format("لطفا مقداری بین {0} تا {1} حرف وارد کنید."),max:a.validator.format("لطفا مقداری کمتر از {0} وارد کنید."),min:a.validator.format("لطفا مقداری بیشتر از {0} وارد کنید."),minWords:a.validator.format("لطفا حداقل {0} کلمه وارد کنید."),maxWords:a.validator.format("لطفا حداکثر {0} کلمه وارد کنید.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_fi.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_fi.min.js deleted file mode 100644 index fb2a331816..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_fi.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Tämä kenttä on pakollinen.",email:"Syötä oikea sähköpostiosoite.",url:"Syötä oikea URL-osoite.",date:"Syötä oikea päivämäärä.",dateISO:"Syötä oikea päivämäärä muodossa VVVV-KK-PP.",number:"Syötä luku.",creditcard:"Syötä voimassa oleva luottokorttinumero.",digits:"Syötä pelkästään numeroita.",equalTo:"Syötä sama arvo uudestaan.",maxlength:a.validator.format("Voit syöttää enintään {0} merkkiä."),minlength:a.validator.format("Vähintään {0} merkkiä."),rangelength:a.validator.format("Syötä vähintään {0} ja enintään {1} merkkiä."),range:a.validator.format("Syötä arvo väliltä {0}–{1}."),max:a.validator.format("Syötä arvo, joka on enintään {0}."),min:a.validator.format("Syötä arvo, joka on vähintään {0}.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_fr.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_fr.js deleted file mode 100644 index e9f18364fe..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_fr.js +++ /dev/null @@ -1,63 +0,0 @@ -(function( factory ) { - if ( typeof define === "function" && define.amd ) { - define( ["jquery", "../jquery.validate"], factory ); - } else if (typeof module === "object" && module.exports) { - module.exports = factory( require( "jquery" ) ); - } else { - factory( jQuery ); - } -}(function( $ ) { - -/* - * Translated default messages for the jQuery validation plugin. - * Locale: FR (French; français) - */ -$.extend( $.validator.messages, { - required: "Ce champ est obligatoire.", - remote: "Veuillez corriger ce champ.", - email: "Veuillez fournir une adresse électronique valide.", - url: "Veuillez fournir une adresse URL valide.", - date: "Veuillez fournir une date valide.", - dateISO: "Veuillez fournir une date valide (ISO).", - number: "Veuillez fournir un numéro valide.", - digits: "Veuillez fournir seulement des chiffres.", - creditcard: "Veuillez fournir un numéro de carte de crédit valide.", - equalTo: "Veuillez fournir encore la même valeur.", - notEqualTo: "Veuillez fournir une valeur différente, les valeurs ne doivent pas être identiques.", - extension: "Veuillez fournir une valeur avec une extension valide.", - maxlength: $.validator.format( "Veuillez fournir au plus {0} caractères." ), - minlength: $.validator.format( "Veuillez fournir au moins {0} caractères." ), - rangelength: $.validator.format( "Veuillez fournir une valeur qui contient entre {0} et {1} caractères." ), - range: $.validator.format( "Veuillez fournir une valeur entre {0} et {1}." ), - max: $.validator.format( "Veuillez fournir une valeur inférieure ou égale à {0}." ), - min: $.validator.format( "Veuillez fournir une valeur supérieure ou égale à {0}." ), - step: $.validator.format( "Veuillez fournir une valeur multiple de {0}." ), - maxWords: $.validator.format( "Veuillez fournir au plus {0} mots." ), - minWords: $.validator.format( "Veuillez fournir au moins {0} mots." ), - rangeWords: $.validator.format( "Veuillez fournir entre {0} et {1} mots." ), - letterswithbasicpunc: "Veuillez fournir seulement des lettres et des signes de ponctuation.", - alphanumeric: "Veuillez fournir seulement des lettres, nombres, espaces et soulignages.", - lettersonly: "Veuillez fournir seulement des lettres.", - nowhitespace: "Veuillez ne pas inscrire d'espaces blancs.", - ziprange: "Veuillez fournir un code postal entre 902xx-xxxx et 905-xx-xxxx.", - integer: "Veuillez fournir un nombre non décimal qui est positif ou négatif.", - vinUS: "Veuillez fournir un numéro d'identification du véhicule (VIN).", - dateITA: "Veuillez fournir une date valide.", - time: "Veuillez fournir une heure valide entre 00:00 et 23:59.", - phoneUS: "Veuillez fournir un numéro de téléphone valide.", - phoneUK: "Veuillez fournir un numéro de téléphone valide.", - mobileUK: "Veuillez fournir un numéro de téléphone mobile valide.", - strippedminlength: $.validator.format( "Veuillez fournir au moins {0} caractères." ), - email2: "Veuillez fournir une adresse électronique valide.", - url2: "Veuillez fournir une adresse URL valide.", - creditcardtypes: "Veuillez fournir un numéro de carte de crédit valide.", - ipv4: "Veuillez fournir une adresse IP v4 valide.", - ipv6: "Veuillez fournir une adresse IP v6 valide.", - require_from_group: $.validator.format( "Veuillez fournir au moins {0} de ces champs." ), - nifES: "Veuillez fournir un numéro NIF valide.", - nieES: "Veuillez fournir un numéro NIE valide.", - cifES: "Veuillez fournir un numéro CIF valide.", - postalCodeCA: "Veuillez fournir un code postal valide." -} ); -return $; -})); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_fr.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_fr.min.js deleted file mode 100644 index 99d3a53f20..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_fr.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Ce champ est obligatoire.",remote:"Veuillez corriger ce champ.",email:"Veuillez fournir une adresse électronique valide.",url:"Veuillez fournir une adresse URL valide.",date:"Veuillez fournir une date valide.",dateISO:"Veuillez fournir une date valide (ISO).",number:"Veuillez fournir un numéro valide.",digits:"Veuillez fournir seulement des chiffres.",creditcard:"Veuillez fournir un numéro de carte de crédit valide.",equalTo:"Veuillez fournir encore la même valeur.",notEqualTo:"Veuillez fournir une valeur différente, les valeurs ne doivent pas être identiques.",extension:"Veuillez fournir une valeur avec une extension valide.",maxlength:a.validator.format("Veuillez fournir au plus {0} caractères."),minlength:a.validator.format("Veuillez fournir au moins {0} caractères."),rangelength:a.validator.format("Veuillez fournir une valeur qui contient entre {0} et {1} caractères."),range:a.validator.format("Veuillez fournir une valeur entre {0} et {1}."),max:a.validator.format("Veuillez fournir une valeur inférieure ou égale à {0}."),min:a.validator.format("Veuillez fournir une valeur supérieure ou égale à {0}."),step:a.validator.format("Veuillez fournir une valeur multiple de {0}."),maxWords:a.validator.format("Veuillez fournir au plus {0} mots."),minWords:a.validator.format("Veuillez fournir au moins {0} mots."),rangeWords:a.validator.format("Veuillez fournir entre {0} et {1} mots."),letterswithbasicpunc:"Veuillez fournir seulement des lettres et des signes de ponctuation.",alphanumeric:"Veuillez fournir seulement des lettres, nombres, espaces et soulignages.",lettersonly:"Veuillez fournir seulement des lettres.",nowhitespace:"Veuillez ne pas inscrire d'espaces blancs.",ziprange:"Veuillez fournir un code postal entre 902xx-xxxx et 905-xx-xxxx.",integer:"Veuillez fournir un nombre non décimal qui est positif ou négatif.",vinUS:"Veuillez fournir un numéro d'identification du véhicule (VIN).",dateITA:"Veuillez fournir une date valide.",time:"Veuillez fournir une heure valide entre 00:00 et 23:59.",phoneUS:"Veuillez fournir un numéro de téléphone valide.",phoneUK:"Veuillez fournir un numéro de téléphone valide.",mobileUK:"Veuillez fournir un numéro de téléphone mobile valide.",strippedminlength:a.validator.format("Veuillez fournir au moins {0} caractères."),email2:"Veuillez fournir une adresse électronique valide.",url2:"Veuillez fournir une adresse URL valide.",creditcardtypes:"Veuillez fournir un numéro de carte de crédit valide.",ipv4:"Veuillez fournir une adresse IP v4 valide.",ipv6:"Veuillez fournir une adresse IP v6 valide.",require_from_group:a.validator.format("Veuillez fournir au moins {0} de ces champs."),nifES:"Veuillez fournir un numéro NIF valide.",nieES:"Veuillez fournir un numéro NIE valide.",cifES:"Veuillez fournir un numéro CIF valide.",postalCodeCA:"Veuillez fournir un code postal valide."}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ge.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ge.min.js deleted file mode 100644 index 456c6da8f1..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ge.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"ეს ველი სავალდებულოა",remote:"გთხოვთ შეასწოროთ.",email:"გთხოვთ შეიყვანოთ სწორი ფორმატით.",url:"გთხოვთ შეიყვანოთ სწორი ფორმატით.",date:"გთხოვთ შეიყვანოთ სწორი თარიღი.",dateISO:"გთხოვთ შეიყვანოთ სწორი ფორმატით (ISO).",number:"გთხოვთ შეიყვანოთ რიცხვი.",digits:"დაშვებულია მხოლოდ ციფრები.",creditcard:"გთხოვთ შეიყვანოთ სწორი ფორმატის ბარათის კოდი.",equalTo:"გთხოვთ შეიყვანოთ იგივე მნიშვნელობა.",maxlength:a.validator.format("გთხოვთ შეიყვანოთ არა უმეტეს {0} სიმბოლოსი."),minlength:a.validator.format("შეიყვანეთ მინიმუმ {0} სიმბოლო."),rangelength:a.validator.format("გთხოვთ შეიყვანოთ {0} -დან {1} -მდე რაოდენობის სიმბოლოები."),range:a.validator.format("შეიყვანეთ {0} -სა {1} -ს შორის."),max:a.validator.format("გთხოვთ შეიყვანოთ მნიშვნელობა ნაკლები ან ტოლი {0} -ს."),min:a.validator.format("გთხოვთ შეიყვანოთ მნიშვნელობა მეტი ან ტოლი {0} -ს.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_gl.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_gl.min.js deleted file mode 100644 index 77eac5ffdf..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_gl.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return function(a){a.extend(a.validator.messages,{required:"Este campo é obrigatorio.",remote:"Por favor, cubre este campo.",email:"Por favor, escribe unha dirección de correo válida.",url:"Por favor, escribe unha URL válida.",date:"Por favor, escribe unha data válida.",dateISO:"Por favor, escribe unha data (ISO) válida.",number:"Por favor, escribe un número válido.",digits:"Por favor, escribe só díxitos.",creditcard:"Por favor, escribe un número de tarxeta válido.",equalTo:"Por favor, escribe o mesmo valor de novo.",extension:"Por favor, escribe un valor cunha extensión aceptada.",maxlength:a.validator.format("Por favor, non escribas máis de {0} caracteres."),minlength:a.validator.format("Por favor, non escribas menos de {0} caracteres."),rangelength:a.validator.format("Por favor, escribe un valor entre {0} e {1} caracteres."),range:a.validator.format("Por favor, escribe un valor entre {0} e {1}."),max:a.validator.format("Por favor, escribe un valor menor ou igual a {0}."),min:a.validator.format("Por favor, escribe un valor maior ou igual a {0}."),nifES:"Por favor, escribe un NIF válido.",nieES:"Por favor, escribe un NIE válido.",cifES:"Por favor, escribe un CIF válido."})}(jQuery),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_he.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_he.min.js deleted file mode 100644 index 381e1079eb..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_he.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"השדה הזה הינו שדה חובה",remote:"נא לתקן שדה זה",email:'נא למלא כתובת דוא"ל חוקית',url:"נא למלא כתובת אינטרנט חוקית",date:"נא למלא תאריך חוקי",dateISO:"נא למלא תאריך חוקי (ISO)",number:"נא למלא מספר",digits:"נא למלא רק מספרים",creditcard:"נא למלא מספר כרטיס אשראי חוקי",equalTo:"נא למלא את אותו ערך שוב",extension:"נא למלא ערך עם סיומת חוקית",maxlength:a.validator.format(".נא לא למלא יותר מ- {0} תווים"),minlength:a.validator.format("נא למלא לפחות {0} תווים"),rangelength:a.validator.format("נא למלא ערך בין {0} ל- {1} תווים"),range:a.validator.format("נא למלא ערך בין {0} ל- {1}"),max:a.validator.format("נא למלא ערך קטן או שווה ל- {0}"),min:a.validator.format("נא למלא ערך גדול או שווה ל- {0}")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_hr.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_hr.min.js deleted file mode 100644 index ee2b757297..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_hr.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Ovo polje je obavezno.",remote:"Ovo polje treba popraviti.",email:"Unesite ispravnu e-mail adresu.",url:"Unesite ispravan URL.",date:"Unesite ispravan datum.",dateISO:"Unesite ispravan datum (ISO).",number:"Unesite ispravan broj.",digits:"Unesite samo brojeve.",creditcard:"Unesite ispravan broj kreditne kartice.",equalTo:"Unesite ponovo istu vrijednost.",extension:"Unesite vrijednost sa ispravnom ekstenzijom.",maxlength:a.validator.format("Maksimalni broj znakova je {0} ."),minlength:a.validator.format("Minimalni broj znakova je {0} ."),rangelength:a.validator.format("Unesite vrijednost između {0} i {1} znakova."),range:a.validator.format("Unesite vrijednost između {0} i {1}."),max:a.validator.format("Unesite vrijednost manju ili jednaku {0}."),min:a.validator.format("Unesite vrijednost veću ili jednaku {0}.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_hu.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_hu.min.js deleted file mode 100644 index ea2cbcd67f..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_hu.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Kötelező megadni.",maxlength:a.validator.format("Legfeljebb {0} karakter hosszú legyen."),minlength:a.validator.format("Legalább {0} karakter hosszú legyen."),rangelength:a.validator.format("Legalább {0} és legfeljebb {1} karakter hosszú legyen."),email:"Érvényes e-mail címnek kell lennie.",url:"Érvényes URL-nek kell lennie.",date:"Dátumnak kell lennie.",number:"Számnak kell lennie.",digits:"Csak számjegyek lehetnek.",equalTo:"Meg kell egyeznie a két értéknek.",range:a.validator.format("{0} és {1} közé kell esnie."),max:a.validator.format("Nem lehet nagyobb, mint {0}."),min:a.validator.format("Nem lehet kisebb, mint {0}."),creditcard:"Érvényes hitelkártyaszámnak kell lennie.",remote:"Kérem javítsa ki ezt a mezőt.",dateISO:"Kérem írjon be egy érvényes dátumot (ISO).",step:a.validator.format("A {0} egyik többszörösét adja meg.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_hy_AM.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_hy_AM.min.js deleted file mode 100644 index 01d6e02454..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_hy_AM.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Պարտադիր լրացման դաշտ",remote:"Ներմուծեք ճիշտ արժեքը",email:"Ներմուծեք վավեր էլեկտրոնային փոստի հասցե",url:"Ներմուծեք վավեր URL",date:"Ներմուծեք վավեր ամսաթիվ",dateISO:"Ներմուծեք ISO ֆորմատով վավեր ամսաթիվ։",number:"Ներմուծեք թիվ",digits:"Ներմուծեք միայն թվեր",creditcard:"Ներմուծեք ճիշտ բանկային քարտի համար",equalTo:"Ներմուծեք միևնուն արժեքը ևս մեկ անգամ",extension:"Ընտրեք ճիշտ ընդլանումով ֆայլ",maxlength:a.validator.format("Ներմուծեք ոչ ավել քան {0} նիշ"),minlength:a.validator.format("Ներմուծեք ոչ պակաս քան {0} նիշ"),rangelength:a.validator.format("Ներմուծեք {0}֊ից {1} երկարությամբ արժեք"),range:a.validator.format("Ներմուծեք թիվ {0}֊ից {1} միջակայքում"),max:a.validator.format("Ներմուծեք թիվ, որը փոքր կամ հավասար է {0}֊ին"),min:a.validator.format("Ներմուծեք թիվ, որը մեծ կամ հավասար է {0}֊ին")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_id.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_id.min.js deleted file mode 100644 index 5686069cb2..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_id.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Kolom ini diperlukan.",remote:"Harap benarkan kolom ini.",email:"Silakan masukkan format email yang benar.",url:"Silakan masukkan format URL yang benar.",date:"Silakan masukkan format tanggal yang benar.",dateISO:"Silakan masukkan format tanggal(ISO) yang benar.",number:"Silakan masukkan angka yang benar.",digits:"Harap masukan angka saja.",creditcard:"Harap masukkan format kartu kredit yang benar.",equalTo:"Harap masukkan nilai yg sama dengan sebelumnya.",maxlength:a.validator.format("Input dibatasi hanya {0} karakter."),minlength:a.validator.format("Input tidak kurang dari {0} karakter."),rangelength:a.validator.format("Panjang karakter yg diizinkan antara {0} dan {1} karakter."),range:a.validator.format("Harap masukkan nilai antara {0} dan {1}."),max:a.validator.format("Harap masukkan nilai lebih kecil atau sama dengan {0}."),min:a.validator.format("Harap masukkan nilai lebih besar atau sama dengan {0}.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_is.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_is.min.js deleted file mode 100644 index 3a6c01751c..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_is.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Þessi reitur er nauðsynlegur.",remote:"Lagaðu þennan reit.",maxlength:a.validator.format("Sláðu inn mest {0} stafi."),minlength:a.validator.format("Sláðu inn minnst {0} stafi."),rangelength:a.validator.format("Sláðu inn minnst {0} og mest {1} stafi."),email:"Sláðu inn gilt netfang.",url:"Sláðu inn gilda vefslóð.",date:"Sláðu inn gilda dagsetningu.",number:"Sláðu inn tölu.",digits:"Sláðu inn tölustafi eingöngu.",equalTo:"Sláðu sama gildi inn aftur.",range:a.validator.format("Sláðu inn gildi milli {0} og {1}."),max:a.validator.format("Sláðu inn gildi sem er minna en eða jafnt og {0}."),min:a.validator.format("Sláðu inn gildi sem er stærra en eða jafnt og {0}."),creditcard:"Sláðu inn gilt greiðslukortanúmer."}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_it.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_it.min.js deleted file mode 100644 index f37ee14730..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_it.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Campo obbligatorio",remote:"Controlla questo campo",email:"Inserisci un indirizzo email valido",url:"Inserisci un indirizzo web valido",date:"Inserisci una data valida",dateISO:"Inserisci una data valida (ISO)",number:"Inserisci un numero valido",digits:"Inserisci solo numeri",creditcard:"Inserisci un numero di carta di credito valido",equalTo:"Il valore non corrisponde",extension:"Inserisci un valore con un'estensione valida",maxlength:a.validator.format("Non inserire più di {0} caratteri"),minlength:a.validator.format("Inserisci almeno {0} caratteri"),rangelength:a.validator.format("Inserisci un valore compreso tra {0} e {1} caratteri"),range:a.validator.format("Inserisci un valore compreso tra {0} e {1}"),max:a.validator.format("Inserisci un valore minore o uguale a {0}"),min:a.validator.format("Inserisci un valore maggiore o uguale a {0}"),nifES:"Inserisci un NIF valido",nieES:"Inserisci un NIE valido",cifES:"Inserisci un CIF valido",currency:"Inserisci una valuta valida"}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ja.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ja.min.js deleted file mode 100644 index a8f684ad79..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ja.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"このフィールドは必須です。",remote:"このフィールドを修正してください。",email:"有効なEメールアドレスを入力してください。",url:"有効なURLを入力してください。",date:"有効な日付を入力してください。",dateISO:"有効な日付(ISO)を入力してください。",number:"有効な数字を入力してください。",digits:"数字のみを入力してください。",creditcard:"有効なクレジットカード番号を入力してください。",equalTo:"同じ値をもう一度入力してください。",extension:"有効な拡張子を含む値を入力してください。",maxlength:a.validator.format("{0} 文字以内で入力してください。"),minlength:a.validator.format("{0} 文字以上で入力してください。"),rangelength:a.validator.format("{0} 文字から {1} 文字までの値を入力してください。"),range:a.validator.format("{0} から {1} までの値を入力してください。"),step:a.validator.format("{0} の倍数を入力してください。"),max:a.validator.format("{0} 以下の値を入力してください。"),min:a.validator.format("{0} 以上の値を入力してください。")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ka.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ka.min.js deleted file mode 100644 index 6273e8b7a3..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ka.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"ამ ველის შევსება აუცილებელია.",remote:"გთხოვთ მიუთითოთ სწორი მნიშვნელობა.",email:"გთხოვთ მიუთითოთ ელ-ფოსტის კორექტული მისამართი.",url:"გთხოვთ მიუთითოთ კორექტული URL.",date:"გთხოვთ მიუთითოთ კორექტული თარიღი.",dateISO:"გთხოვთ მიუთითოთ კორექტული თარიღი ISO ფორმატში.",number:"გთხოვთ მიუთითოთ ციფრი.",digits:"გთხოვთ მიუთითოთ მხოლოდ ციფრები.",creditcard:"გთხოვთ მიუთითოთ საკრედიტო ბარათის კორექტული ნომერი.",equalTo:"გთხოვთ მიუთითოთ ასეთივე მნიშვნელობა კიდევ ერთხელ.",extension:"გთხოვთ აირჩიოთ ფაილი კორექტული გაფართოებით.",maxlength:a.validator.format("დასაშვებია არაუმეტეს {0} სიმბოლო."),minlength:a.validator.format("აუცილებელია შეიყვანოთ მინიმუმ {0} სიმბოლო."),rangelength:a.validator.format("ტექსტში სიმბოლოების რაოდენობა უნდა იყოს {0}-დან {1}-მდე."),range:a.validator.format("გთხოვთ შეიყვანოთ ციფრი {0}-დან {1}-მდე."),max:a.validator.format("გთხოვთ შეიყვანოთ ციფრი რომელიც ნაკლებია ან უდრის {0}-ს."),min:a.validator.format("გთხოვთ შეიყვანოთ ციფრი რომელიც მეტია ან უდრის {0}-ს.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_kk.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_kk.min.js deleted file mode 100644 index 7f450ec8f6..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_kk.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Бұл өрісті міндетті түрде толтырыңыз.",remote:"Дұрыс мағына енгізуіңізді сұраймыз.",email:"Нақты электронды поштаңызды енгізуіңізді сұраймыз.",url:"Нақты URL-ды енгізуіңізді сұраймыз.",date:"Нақты URL-ды енгізуіңізді сұраймыз.",dateISO:"Нақты ISO форматымен сәйкес датасын енгізуіңізді сұраймыз.",number:"Күнді енгізуіңізді сұраймыз.",digits:"Тек қана сандарды енгізуіңізді сұраймыз.",creditcard:"Несие картасының нөмірін дұрыс енгізуіңізді сұраймыз.",equalTo:"Осы мәнді қайта енгізуіңізді сұраймыз.",extension:"Файлдың кеңейтуін дұрыс таңдаңыз.",maxlength:a.validator.format("Ұзындығы {0} символдан көр болмасын."),minlength:a.validator.format("Ұзындығы {0} символдан аз болмасын."),rangelength:a.validator.format("Ұзындығы {0}-{1} дейін мән енгізуіңізді сұраймыз."),range:a.validator.format("Пожалуйста, введите число от {0} до {1}. - {0} - {1} санын енгізуіңізді сұраймыз."),max:a.validator.format("{0} аз немесе тең санын енгізуіңіді сұраймыз."),min:a.validator.format("{0} көп немесе тең санын енгізуіңізді сұраймыз.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ko.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ko.min.js deleted file mode 100644 index 4634eca42c..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ko.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"필수 항목입니다.",remote:"항목을 수정하세요.",email:"유효하지 않은 E-Mail주소입니다.",url:"유효하지 않은 URL입니다.",date:"올바른 날짜를 입력하세요.",dateISO:"올바른 날짜(ISO)를 입력하세요.",number:"유효한 숫자가 아닙니다.",digits:"숫자만 입력 가능합니다.",creditcard:"신용카드 번호가 바르지 않습니다.",equalTo:"같은 값을 다시 입력하세요.",extension:"올바른 확장자가 아닙니다.",maxlength:a.validator.format("{0}자를 넘을 수 없습니다. "),minlength:a.validator.format("{0}자 이상 입력하세요."),rangelength:a.validator.format("문자 길이가 {0} 에서 {1} 사이의 값을 입력하세요."),range:a.validator.format("{0} 에서 {1} 사이의 값을 입력하세요."),max:a.validator.format("{0} 이하의 값을 입력하세요."),min:a.validator.format("{0} 이상의 값을 입력하세요.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_lt.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_lt.min.js deleted file mode 100644 index 67ad519db3..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_lt.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Šis laukas yra privalomas.",remote:"Prašau pataisyti šį lauką.",email:"Prašau įvesti teisingą elektroninio pašto adresą.",url:"Prašau įvesti teisingą URL.",date:"Prašau įvesti teisingą datą.",dateISO:"Prašau įvesti teisingą datą (ISO).",number:"Prašau įvesti teisingą skaičių.",digits:"Prašau naudoti tik skaitmenis.",creditcard:"Prašau įvesti teisingą kreditinės kortelės numerį.",equalTo:"Prašau įvestį tą pačią reikšmę dar kartą.",extension:"Prašau įvesti reikšmę su teisingu plėtiniu.",maxlength:a.validator.format("Prašau įvesti ne daugiau kaip {0} simbolių."),minlength:a.validator.format("Prašau įvesti bent {0} simbolius."),rangelength:a.validator.format("Prašau įvesti reikšmes, kurių ilgis nuo {0} iki {1} simbolių."),range:a.validator.format("Prašau įvesti reikšmę intervale nuo {0} iki {1}."),max:a.validator.format("Prašau įvesti reikšmę mažesnę arba lygią {0}."),min:a.validator.format("Prašau įvesti reikšmę didesnę arba lygią {0}.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_lv.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_lv.min.js deleted file mode 100644 index 67e4efa448..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_lv.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Šis lauks ir obligāts.",remote:"Lūdzu, pārbaudiet šo lauku.",email:"Lūdzu, ievadiet derīgu e-pasta adresi.",url:"Lūdzu, ievadiet derīgu URL adresi.",date:"Lūdzu, ievadiet derīgu datumu.",dateISO:"Lūdzu, ievadiet derīgu datumu (ISO).",number:"Lūdzu, ievadiet derīgu numuru.",digits:"Lūdzu, ievadiet tikai ciparus.",creditcard:"Lūdzu, ievadiet derīgu kredītkartes numuru.",equalTo:"Lūdzu, ievadiet to pašu vēlreiz.",extension:"Lūdzu, ievadiet vērtību ar derīgu paplašinājumu.",maxlength:a.validator.format("Lūdzu, ievadiet ne vairāk kā {0} rakstzīmes."),minlength:a.validator.format("Lūdzu, ievadiet vismaz {0} rakstzīmes."),rangelength:a.validator.format("Lūdzu ievadiet {0} līdz {1} rakstzīmes."),range:a.validator.format("Lūdzu, ievadiet skaitli no {0} līdz {1}."),max:a.validator.format("Lūdzu, ievadiet skaitli, kurš ir mazāks vai vienāds ar {0}."),min:a.validator.format("Lūdzu, ievadiet skaitli, kurš ir lielāks vai vienāds ar {0}.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_mk.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_mk.min.js deleted file mode 100644 index 93a33adf20..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_mk.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Полето е задолжително.",remote:"Поправете го ова поле",email:"Внесете правилна e-mail адреса",url:"Внесете правилен URL.",date:"Внесете правилен датум",dateISO:"Внесете правилен датум (ISO).",number:"Внесете правилен број.",digits:"Внесете само бројки.",creditcard:"Внесете правилен број на кредитната картичка.",equalTo:"Внесете ја истата вредност повторно.",extension:"Внесете вредност со соодветна екстензија.",maxlength:a.validator.format("Внесете максимално {0} знаци."),minlength:a.validator.format("Внесете барем {0} знаци."),rangelength:a.validator.format("Внесете вредност со должина помеѓу {0} и {1} знаци."),range:a.validator.format("Внесете вредност помеѓу {0} и {1}."),max:a.validator.format("Внесете вредност помала или еднаква на {0}."),min:a.validator.format("Внесете вредност поголема или еднаква на {0}")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_my.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_my.min.js deleted file mode 100644 index 721a1be35d..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_my.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Medan ini diperlukan.",remote:"Sila betulkan medan ini.",email:"Sila masukkan alamat emel yang betul.",url:"Sila masukkan URL yang betul.",date:"Sila masukkan tarikh yang betul.",dateISO:"Sila masukkan tarikh(ISO) yang betul.",number:"Sila masukkan nombor yang betul.",digits:"Sila masukkan nilai digit sahaja.",creditcard:"Sila masukkan nombor kredit kad yang betul.",equalTo:"Sila masukkan nilai yang sama semula.",extension:"Sila masukkan nilai yang telah diterima.",maxlength:a.validator.format("Sila masukkan tidak lebih dari {0} aksara."),minlength:a.validator.format("Sila masukkan sekurang-kurangnya {0} aksara."),rangelength:a.validator.format("Sila masukkan antara {0} dan {1} panjang aksara."),range:a.validator.format("Sila masukkan nilai antara {0} dan {1} aksara."),max:a.validator.format("Sila masukkan nilai yang kurang atau sama dengan {0}."),min:a.validator.format("Sila masukkan nilai yang lebih atau sama dengan {0}.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_nl.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_nl.min.js deleted file mode 100644 index f69c3d5186..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_nl.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Dit is een verplicht veld.",remote:"Controleer dit veld.",email:"Vul hier een geldig e-mailadres in.",url:"Vul hier een geldige URL in.",date:"Vul hier een geldige datum in.",dateISO:"Vul hier een geldige datum in (ISO-formaat).",number:"Vul hier een geldig getal in.",digits:"Vul hier alleen getallen in.",creditcard:"Vul hier een geldig creditcardnummer in.",equalTo:"Vul hier dezelfde waarde in.",extension:"Vul hier een waarde in met een geldige extensie.",maxlength:a.validator.format("Vul hier maximaal {0} tekens in."),minlength:a.validator.format("Vul hier minimaal {0} tekens in."),rangelength:a.validator.format("Vul hier een waarde in van minimaal {0} en maximaal {1} tekens."),range:a.validator.format("Vul hier een waarde in van minimaal {0} en maximaal {1}."),max:a.validator.format("Vul hier een waarde in kleiner dan of gelijk aan {0}."),min:a.validator.format("Vul hier een waarde in groter dan of gelijk aan {0}."),step:a.validator.format("Vul hier een veelvoud van {0} in."),iban:"Vul hier een geldig IBAN in.",dateNL:"Vul hier een geldige datum in.",phoneNL:"Vul hier een geldig Nederlands telefoonnummer in.",mobileNL:"Vul hier een geldig Nederlands mobiel telefoonnummer in.",postalcodeNL:"Vul hier een geldige postcode in.",bankaccountNL:"Vul hier een geldig bankrekeningnummer in.",giroaccountNL:"Vul hier een geldig gironummer in.",bankorgiroaccountNL:"Vul hier een geldig bank- of gironummer in."}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_no.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_no.js deleted file mode 100644 index d6d7be0893..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_no.js +++ /dev/null @@ -1,35 +0,0 @@ -(function( factory ) { - if ( typeof define === "function" && define.amd ) { - define( ["jquery", "../jquery.validate"], factory ); - } else if (typeof module === "object" && module.exports) { - module.exports = factory( require( "jquery" ) ); - } else { - factory( jQuery ); - } -}(function( $ ) { - -/* - * Translated default messages for the jQuery validation plugin. - * Locale: NO (Norwegian; Norsk) - */ -$.extend( $.validator.messages, { - required: "Angi en verdi.", - remote: "Ugyldig verdi.", - email: "Angi en gyldig epostadresse.", - url: "Angi en gyldig URL.", - date: "Angi en gyldig dato.", - dateISO: "Angi en gyldig dato (&ARING;&ARING;&ARING;&ARING;-MM-DD).", - number: "Angi et gyldig tall.", - digits: "Skriv kun tall.", - equalTo: "Skriv samme verdi igjen.", - maxlength: $.validator.format( "Maksimalt {0} tegn." ), - minlength: $.validator.format( "Minimum {0} tegn." ), - rangelength: $.validator.format( "Angi minimum {0} og maksimum {1} tegn." ), - range: $.validator.format( "Angi en verdi mellom {0} og {1}." ), - max: $.validator.format( "Angi en verdi som er mindre eller lik {0}." ), - min: $.validator.format( "Angi en verdi som er større eller lik {0}." ), - step: $.validator.format( "Angi en verdi ganger {0}." ), - creditcard: "Angi et gyldig kredittkortnummer." -} ); -return $; -})); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_no.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_no.min.js deleted file mode 100644 index 6feaa2df65..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_no.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Angi en verdi.",remote:"Ugyldig verdi.",email:"Angi en gyldig epostadresse.",url:"Angi en gyldig URL.",date:"Angi en gyldig dato.",dateISO:"Angi en gyldig dato (&ARING;&ARING;&ARING;&ARING;-MM-DD).",number:"Angi et gyldig tall.",digits:"Skriv kun tall.",equalTo:"Skriv samme verdi igjen.",maxlength:a.validator.format("Maksimalt {0} tegn."),minlength:a.validator.format("Minimum {0} tegn."),rangelength:a.validator.format("Angi minimum {0} og maksimum {1} tegn."),range:a.validator.format("Angi en verdi mellom {0} og {1}."),max:a.validator.format("Angi en verdi som er mindre eller lik {0}."),min:a.validator.format("Angi en verdi som er større eller lik {0}."),step:a.validator.format("Angi en verdi ganger {0}."),creditcard:"Angi et gyldig kredittkortnummer."}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_pl.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_pl.js deleted file mode 100644 index fc07b85200..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_pl.js +++ /dev/null @@ -1,38 +0,0 @@ -(function( factory ) { - if ( typeof define === "function" && define.amd ) { - define( ["jquery", "../jquery.validate"], factory ); - } else if (typeof module === "object" && module.exports) { - module.exports = factory( require( "jquery" ) ); - } else { - factory( jQuery ); - } -}(function( $ ) { - -/* - * Translated default messages for the jQuery validation plugin. - * Locale: PL (Polish; język polski, polszczyzna) - */ -$.extend( $.validator.messages, { - required: "To pole jest wymagane.", - remote: "Proszę o wypełnienie tego pola.", - email: "Proszę o podanie prawidłowego adresu email.", - url: "Proszę o podanie prawidłowego URL.", - date: "Proszę o podanie prawidłowej daty.", - dateISO: "Proszę o podanie prawidłowej daty (ISO).", - number: "Proszę o podanie prawidłowej liczby.", - digits: "Proszę o podanie samych cyfr.", - creditcard: "Proszę o podanie prawidłowej karty kredytowej.", - equalTo: "Proszę o podanie tej samej wartości ponownie.", - extension: "Proszę o podanie wartości z prawidłowym rozszerzeniem.", - nipPL: "Proszę o podanie prawidłowego numeru NIP.", - phonePL: "Proszę o podanie prawidłowego numeru telefonu", - maxlength: $.validator.format( "Proszę o podanie nie więcej niż {0} znaków." ), - minlength: $.validator.format( "Proszę o podanie przynajmniej {0} znaków." ), - rangelength: $.validator.format( "Proszę o podanie wartości o długości od {0} do {1} znaków." ), - range: $.validator.format( "Proszę o podanie wartości z przedziału od {0} do {1}." ), - max: $.validator.format( "Proszę o podanie wartości mniejszej bądź równej {0}." ), - min: $.validator.format( "Proszę o podanie wartości większej bądź równej {0}." ), - pattern: $.validator.format( "Pole zawiera niedozwolone znaki." ) -} ); -return $; -})); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_pl.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_pl.min.js deleted file mode 100644 index fdca59ea7b..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_pl.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"To pole jest wymagane.",remote:"Proszę o wypełnienie tego pola.",email:"Proszę o podanie prawidłowego adresu email.",url:"Proszę o podanie prawidłowego URL.",date:"Proszę o podanie prawidłowej daty.",dateISO:"Proszę o podanie prawidłowej daty (ISO).",number:"Proszę o podanie prawidłowej liczby.",digits:"Proszę o podanie samych cyfr.",creditcard:"Proszę o podanie prawidłowej karty kredytowej.",equalTo:"Proszę o podanie tej samej wartości ponownie.",extension:"Proszę o podanie wartości z prawidłowym rozszerzeniem.",nipPL:"Proszę o podanie prawidłowego numeru NIP.",phonePL:"Proszę o podanie prawidłowego numeru telefonu",maxlength:a.validator.format("Proszę o podanie nie więcej niż {0} znaków."),minlength:a.validator.format("Proszę o podanie przynajmniej {0} znaków."),rangelength:a.validator.format("Proszę o podanie wartości o długości od {0} do {1} znaków."),range:a.validator.format("Proszę o podanie wartości z przedziału od {0} do {1}."),max:a.validator.format("Proszę o podanie wartości mniejszej bądź równej {0}."),min:a.validator.format("Proszę o podanie wartości większej bądź równej {0}."),pattern:a.validator.format("Pole zawiera niedozwolone znaki.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_pt_BR.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_pt_BR.js deleted file mode 100644 index 92d9604c33..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_pt_BR.js +++ /dev/null @@ -1,91 +0,0 @@ -(function( factory ) { - if ( typeof define === "function" && define.amd ) { - define( ["jquery", "../jquery.validate"], factory ); - } else if (typeof module === "object" && module.exports) { - module.exports = factory( require( "jquery" ) ); - } else { - factory( jQuery ); - } -}(function( $ ) { - -/* - * Translated default messages for the jQuery validation plugin. - * Locale: PT (Portuguese; português) - * Region: BR (Brazil) - */ -$.extend( $.validator.messages, { - - // Core - required: "Este campo é requerido.", - remote: "Por favor, corrija este campo.", - email: "Por favor, forneça um endereço de email válido.", - url: "Por favor, forneça uma URL válida.", - date: "Por favor, forneça uma data válida.", - dateISO: "Por favor, forneça uma data válida (ISO).", - number: "Por favor, forneça um número válido.", - digits: "Por favor, forneça somente dígitos.", - creditcard: "Por favor, forneça um cartão de crédito válido.", - equalTo: "Por favor, forneça o mesmo valor novamente.", - maxlength: $.validator.format( "Por favor, forneça não mais que {0} caracteres." ), - minlength: $.validator.format( "Por favor, forneça ao menos {0} caracteres." ), - rangelength: $.validator.format( "Por favor, forneça um valor entre {0} e {1} caracteres de comprimento." ), - range: $.validator.format( "Por favor, forneça um valor entre {0} e {1}." ), - max: $.validator.format( "Por favor, forneça um valor menor ou igual a {0}." ), - min: $.validator.format( "Por favor, forneça um valor maior ou igual a {0}." ), - step: $.validator.format( "Por favor, forneça um valor múltiplo de {0}." ), - - // Metodos Adicionais - maxWords: $.validator.format( "Por favor, forneça com {0} palavras ou menos." ), - minWords: $.validator.format( "Por favor, forneça pelo menos {0} palavras." ), - rangeWords: $.validator.format( "Por favor, forneça entre {0} e {1} palavras." ), - accept: "Por favor, forneça um tipo válido.", - alphanumeric: "Por favor, forneça somente com letras, números e sublinhados.", - bankaccountNL: "Por favor, forneça com um número de conta bancária válida.", - bankorgiroaccountNL: "Por favor, forneça um banco válido ou número de conta.", - bic: "Por favor, forneça um código BIC válido.", - cifES: "Por favor, forneça um código CIF válido.", - creditcardtypes: "Por favor, forneça um número de cartão de crédito válido.", - currency: "Por favor, forneça uma moeda válida.", - dateFA: "Por favor, forneça uma data correta.", - dateITA: "Por favor, forneça uma data correta.", - dateNL: "Por favor, forneça uma data correta.", - extension: "Por favor, forneça um valor com uma extensão válida.", - giroaccountNL: "Por favor, forneça um número de conta corrente válido.", - iban: "Por favor, forneça um código IBAN válido.", - integer: "Por favor, forneça um número não decimal.", - ipv4: "Por favor, forneça um IPv4 válido.", - ipv6: "Por favor, forneça um IPv6 válido.", - lettersonly: "Por favor, forneça apenas com letras.", - letterswithbasicpunc: "Por favor, forneça apenas letras ou pontuações.", - mobileNL: "Por favor, forneceça um número válido de telefone.", - mobileUK: "Por favor, forneceça um número válido de telefone.", - nieES: "Por favor, forneça um NIE válido.", - nifES: "Por favor, forneça um NIF válido.", - nowhitespace: "Por favor, não utilize espaços em branco.", - pattern: "O formato fornecido é inválido.", - phoneNL: "Por favor, forneça um número de telefone válido.", - phoneUK: "Por favor, forneça um número de telefone válido.", - phoneUS: "Por favor, forneça um número de telefone válido.", - phonesUK: "Por favor, forneça um número de telefone válido.", - postalCodeCA: "Por favor, forneça um número de código postal válido.", - postalcodeIT: "Por favor, forneça um número de código postal válido.", - postalcodeNL: "Por favor, forneça um número de código postal válido.", - postcodeUK: "Por favor, forneça um número de código postal válido.", - postalcodeBR: "Por favor, forneça um CEP válido.", - require_from_group: $.validator.format( "Por favor, forneça pelo menos {0} destes campos." ), - skip_or_fill_minimum: $.validator.format( "Por favor, optar entre ignorar esses campos ou preencher pelo menos {0} deles." ), - stateUS: "Por favor, forneça um estado válido.", - strippedminlength: $.validator.format( "Por favor, forneça pelo menos {0} caracteres." ), - time: "Por favor, forneça um horário válido, no intervado de 00:00 a 23:59.", - time12h: "Por favor, forneça um horário válido, no intervado de 01:00 a 12:59 am/pm.", - url2: "Por favor, forneça uma URL válida.", - vinUS: "O número de identificação de veículo informado (VIN) é inválido.", - zipcodeUS: "Por favor, forneça um código postal americano válido.", - ziprange: "O código postal deve estar entre 902xx-xxxx e 905xx-xxxx", - cpfBR: "Por favor, forneça um CPF válido.", - nisBR: "Por favor, forneça um NIS/PIS válido", - cnhBR: "Por favor, forneça um CNH válido.", - cnpjBR: "Por favor, forneça um CNPJ válido." -} ); -return $; -})); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_pt_BR.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_pt_BR.min.js deleted file mode 100644 index bac1ae897d..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_pt_BR.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Este campo é requerido.",remote:"Por favor, corrija este campo.",email:"Por favor, forneça um endereço de email válido.",url:"Por favor, forneça uma URL válida.",date:"Por favor, forneça uma data válida.",dateISO:"Por favor, forneça uma data válida (ISO).",number:"Por favor, forneça um número válido.",digits:"Por favor, forneça somente dígitos.",creditcard:"Por favor, forneça um cartão de crédito válido.",equalTo:"Por favor, forneça o mesmo valor novamente.",maxlength:a.validator.format("Por favor, forneça não mais que {0} caracteres."),minlength:a.validator.format("Por favor, forneça ao menos {0} caracteres."),rangelength:a.validator.format("Por favor, forneça um valor entre {0} e {1} caracteres de comprimento."),range:a.validator.format("Por favor, forneça um valor entre {0} e {1}."),max:a.validator.format("Por favor, forneça um valor menor ou igual a {0}."),min:a.validator.format("Por favor, forneça um valor maior ou igual a {0}."),step:a.validator.format("Por favor, forneça um valor múltiplo de {0}."),maxWords:a.validator.format("Por favor, forneça com {0} palavras ou menos."),minWords:a.validator.format("Por favor, forneça pelo menos {0} palavras."),rangeWords:a.validator.format("Por favor, forneça entre {0} e {1} palavras."),accept:"Por favor, forneça um tipo válido.",alphanumeric:"Por favor, forneça somente com letras, números e sublinhados.",bankaccountNL:"Por favor, forneça com um número de conta bancária válida.",bankorgiroaccountNL:"Por favor, forneça um banco válido ou número de conta.",bic:"Por favor, forneça um código BIC válido.",cifES:"Por favor, forneça um código CIF válido.",creditcardtypes:"Por favor, forneça um número de cartão de crédito válido.",currency:"Por favor, forneça uma moeda válida.",dateFA:"Por favor, forneça uma data correta.",dateITA:"Por favor, forneça uma data correta.",dateNL:"Por favor, forneça uma data correta.",extension:"Por favor, forneça um valor com uma extensão válida.",giroaccountNL:"Por favor, forneça um número de conta corrente válido.",iban:"Por favor, forneça um código IBAN válido.",integer:"Por favor, forneça um número não decimal.",ipv4:"Por favor, forneça um IPv4 válido.",ipv6:"Por favor, forneça um IPv6 válido.",lettersonly:"Por favor, forneça apenas com letras.",letterswithbasicpunc:"Por favor, forneça apenas letras ou pontuações.",mobileNL:"Por favor, forneceça um número válido de telefone.",mobileUK:"Por favor, forneceça um número válido de telefone.",nieES:"Por favor, forneça um NIE válido.",nifES:"Por favor, forneça um NIF válido.",nowhitespace:"Por favor, não utilize espaços em branco.",pattern:"O formato fornecido é inválido.",phoneNL:"Por favor, forneça um número de telefone válido.",phoneUK:"Por favor, forneça um número de telefone válido.",phoneUS:"Por favor, forneça um número de telefone válido.",phonesUK:"Por favor, forneça um número de telefone válido.",postalCodeCA:"Por favor, forneça um número de código postal válido.",postalcodeIT:"Por favor, forneça um número de código postal válido.",postalcodeNL:"Por favor, forneça um número de código postal válido.",postcodeUK:"Por favor, forneça um número de código postal válido.",postalcodeBR:"Por favor, forneça um CEP válido.",require_from_group:a.validator.format("Por favor, forneça pelo menos {0} destes campos."),skip_or_fill_minimum:a.validator.format("Por favor, optar entre ignorar esses campos ou preencher pelo menos {0} deles."),stateUS:"Por favor, forneça um estado válido.",strippedminlength:a.validator.format("Por favor, forneça pelo menos {0} caracteres."),time:"Por favor, forneça um horário válido, no intervado de 00:00 a 23:59.",time12h:"Por favor, forneça um horário válido, no intervado de 01:00 a 12:59 am/pm.",url2:"Por favor, forneça uma URL válida.",vinUS:"O número de identificação de veículo informado (VIN) é inválido.",zipcodeUS:"Por favor, forneça um código postal americano válido.",ziprange:"O código postal deve estar entre 902xx-xxxx e 905xx-xxxx",cpfBR:"Por favor, forneça um CPF válido.",nisBR:"Por favor, forneça um NIS/PIS válido",cnhBR:"Por favor, forneça um CNH válido.",cnpjBR:"Por favor, forneça um CNPJ válido."}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_pt_PT.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_pt_PT.min.js deleted file mode 100644 index 3b9ef3629e..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_pt_PT.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Campo de preenchimento obrigatório.",remote:"Por favor, corrija este campo.",email:"Por favor, introduza um endereço eletrónico válido.",url:"Por favor, introduza um URL válido.",date:"Por favor, introduza uma data válida.",dateISO:"Por favor, introduza uma data válida (ISO).",number:"Por favor, introduza um número válido.",digits:"Por favor, introduza apenas dígitos.",creditcard:"Por favor, introduza um número de cartão de crédito válido.",equalTo:"Por favor, introduza de novo o mesmo valor.",extension:"Por favor, introduza um ficheiro com uma extensão válida.",maxlength:a.validator.format("Por favor, não introduza mais do que {0} caracteres."),minlength:a.validator.format("Por favor, introduza pelo menos {0} caracteres."),rangelength:a.validator.format("Por favor, introduza entre {0} e {1} caracteres."),range:a.validator.format("Por favor, introduza um valor entre {0} e {1}."),max:a.validator.format("Por favor, introduza um valor menor ou igual a {0}."),min:a.validator.format("Por favor, introduza um valor maior ou igual a {0}."),nifES:"Por favor, introduza um NIF válido.",nieES:"Por favor, introduza um NIE válido.",cifES:"Por favor, introduza um CIF válido."}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ro.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ro.min.js deleted file mode 100644 index e1ba18507e..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ro.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Acest câmp este obligatoriu.",remote:"Te rugăm să completezi acest câmp.",email:"Te rugăm să introduci o adresă de email validă",url:"Te rugăm sa introduci o adresă URL validă.",date:"Te rugăm să introduci o dată corectă.",dateISO:"Te rugăm să introduci o dată (ISO) corectă.",number:"Te rugăm să introduci un număr întreg valid.",digits:"Te rugăm să introduci doar cifre.",creditcard:"Te rugăm să introduci un numar de carte de credit valid.",equalTo:"Te rugăm să reintroduci valoarea.",extension:"Te rugăm să introduci o valoare cu o extensie validă.",maxlength:a.validator.format("Te rugăm să nu introduci mai mult de {0} caractere."),minlength:a.validator.format("Te rugăm să introduci cel puțin {0} caractere."),rangelength:a.validator.format("Te rugăm să introduci o valoare între {0} și {1} caractere."),range:a.validator.format("Te rugăm să introduci o valoare între {0} și {1}."),max:a.validator.format("Te rugăm să introduci o valoare egal sau mai mică decât {0}."),min:a.validator.format("Te rugăm să introduci o valoare egal sau mai mare decât {0}.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ru.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ru.min.js deleted file mode 100644 index 4934d77181..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ru.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Это поле необходимо заполнить.",remote:"Пожалуйста, введите правильное значение.",email:"Пожалуйста, введите корректный адрес электронной почты.",url:"Пожалуйста, введите корректный URL.",date:"Пожалуйста, введите корректную дату.",dateISO:"Пожалуйста, введите корректную дату в формате ISO.",number:"Пожалуйста, введите число.",digits:"Пожалуйста, вводите только цифры.",creditcard:"Пожалуйста, введите правильный номер кредитной карты.",equalTo:"Пожалуйста, введите такое же значение ещё раз.",extension:"Пожалуйста, выберите файл с правильным расширением.",maxlength:a.validator.format("Пожалуйста, введите не больше {0} символов."),minlength:a.validator.format("Пожалуйста, введите не меньше {0} символов."),rangelength:a.validator.format("Пожалуйста, введите значение длиной от {0} до {1} символов."),range:a.validator.format("Пожалуйста, введите число от {0} до {1}."),max:a.validator.format("Пожалуйста, введите число, меньшее или равное {0}."),min:a.validator.format("Пожалуйста, введите число, большее или равное {0}.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_sd.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_sd.min.js deleted file mode 100644 index d41d835da8..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_sd.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"هنن جاين جي ضرورت آهي",remote:"هنن جاين جي ضرورت آهي",email:"لکيل اي ميل غلط آهي",url:"لکيل ايڊريس غلط آهي",date:"لکيل تاريخ غلط آهي",dateISO:"جي معيار جي مطابق نه آهي (ISO) لکيل تاريخ",number:"لکيل انگ صحيح ناهي",digits:"رڳو انگ داخل ڪري سگهجي ٿو",creditcard:"لکيل ڪارڊ نمبر صحيح نه آهي",equalTo:"داخل ٿيل ڀيٽ صحيح نه آهي",extension:"لکيل غلط آهي",maxlength:a.validator.format("وڌ کان وڌ {0} جي داخلا ڪري سگهجي ٿي"),minlength:a.validator.format("گهٽ ۾ گهٽ {0} جي داخلا ڪرڻ ضروري آهي"),rangelength:a.validator.format("داخلا جو {0} ۽ {1}جي وچ ۾ هجڻ ضروري آهي"),range:a.validator.format("داخلا جو {0} ۽ {1}جي وچ ۾ هجڻ ضروري آهي"),max:a.validator.format("وڌ کان وڌ {0} جي داخلا ڪري سگهجي ٿي"),min:a.validator.format("گهٽ ۾ گهٽ {0} جي داخلا ڪرڻ ضروري آهي")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_si.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_si.min.js deleted file mode 100644 index a64185ff95..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_si.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"To polje je obvezno.",remote:"Vpis v tem polju ni v pravi obliki.",email:"Prosimo, vnesite pravi email naslov.",url:"Prosimo, vnesite pravi URL.",date:"Prosimo, vnesite pravi datum.",dateISO:"Prosimo, vnesite pravi datum (ISO).",number:"Prosimo, vnesite pravo številko.",digits:"Prosimo, vnesite samo številke.",creditcard:"Prosimo, vnesite pravo številko kreditne kartice.",equalTo:"Prosimo, ponovno vnesite enako vsebino.",extension:"Prosimo, vnesite vsebino z pravo končnico.",maxlength:a.validator.format("Prosimo, da ne vnašate več kot {0} znakov."),minlength:a.validator.format("Prosimo, vnesite vsaj {0} znakov."),rangelength:a.validator.format("Prosimo, vnesite od {0} do {1} znakov."),range:a.validator.format("Prosimo, vnesite vrednost med {0} in {1}."),max:a.validator.format("Prosimo, vnesite vrednost manjšo ali enako {0}."),min:a.validator.format("Prosimo, vnesite vrednost večjo ali enako {0}.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_sk.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_sk.js deleted file mode 100644 index 0f9becf59f..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_sk.js +++ /dev/null @@ -1,33 +0,0 @@ -(function( factory ) { - if ( typeof define === "function" && define.amd ) { - define( ["jquery", "../jquery.validate"], factory ); - } else if (typeof module === "object" && module.exports) { - module.exports = factory( require( "jquery" ) ); - } else { - factory( jQuery ); - } -}(function( $ ) { - -/* - * Translated default messages for the jQuery validation plugin. - * Locale: SK (Slovak; slovenčina, slovenský jazyk) - */ -$.extend( $.validator.messages, { - required: "Povinné zadať.", - maxlength: $.validator.format( "Maximálne {0} znakov." ), - minlength: $.validator.format( "Minimálne {0} znakov." ), - rangelength: $.validator.format( "Minimálne {0} a maximálne {1} znakov." ), - email: "E-mailová adresa musí byť platná.", - url: "URL musí byť platná.", - date: "Musí byť dátum.", - number: "Musí byť číslo.", - digits: "Môže obsahovať iba číslice.", - equalTo: "Dve hodnoty sa musia rovnať.", - range: $.validator.format( "Musí byť medzi {0} a {1}." ), - max: $.validator.format( "Nemôže byť viac ako {0}." ), - min: $.validator.format( "Nemôže byť menej ako {0}." ), - creditcard: "Číslo platobnej karty musí byť platné.", - step: $.validator.format( "Musí byť násobkom čísla {0}." ) -} ); -return $; -})); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_sk.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_sk.min.js deleted file mode 100644 index d22ec47f57..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_sk.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Povinné zadať.",maxlength:a.validator.format("Maximálne {0} znakov."),minlength:a.validator.format("Minimálne {0} znakov."),rangelength:a.validator.format("Minimálne {0} a maximálne {1} znakov."),email:"E-mailová adresa musí byť platná.",url:"URL musí byť platná.",date:"Musí byť dátum.",number:"Musí byť číslo.",digits:"Môže obsahovať iba číslice.",equalTo:"Dve hodnoty sa musia rovnať.",range:a.validator.format("Musí byť medzi {0} a {1}."),max:a.validator.format("Nemôže byť viac ako {0}."),min:a.validator.format("Nemôže byť menej ako {0}."),creditcard:"Číslo platobnej karty musí byť platné.",step:a.validator.format("Musí byť násobkom čísla {0}.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_sl.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_sl.min.js deleted file mode 100644 index b1dccbb6fc..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_sl.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"To polje je obvezno.",remote:"Prosimo popravite to polje.",email:"Prosimo vnesite veljaven email naslov.",url:"Prosimo vnesite veljaven URL naslov.",date:"Prosimo vnesite veljaven datum.",dateISO:"Prosimo vnesite veljaven ISO datum.",number:"Prosimo vnesite veljavno število.",digits:"Prosimo vnesite samo števila.",creditcard:"Prosimo vnesite veljavno številko kreditne kartice.",equalTo:"Prosimo ponovno vnesite vrednost.",extension:"Prosimo vnesite vrednost z veljavno končnico.",maxlength:a.validator.format("Prosimo vnesite največ {0} znakov."),minlength:a.validator.format("Prosimo vnesite najmanj {0} znakov."),rangelength:a.validator.format("Prosimo vnesite najmanj {0} in največ {1} znakov."),range:a.validator.format("Prosimo vnesite vrednost med {0} in {1}."),max:a.validator.format("Prosimo vnesite vrednost manjše ali enako {0}."),min:a.validator.format("Prosimo vnesite vrednost večje ali enako {0}.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_sr.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_sr.min.js deleted file mode 100644 index 8151b963f1..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_sr.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Поље је обавезно.",remote:"Средите ово поље.",email:"Унесите исправну и-мејл адресу.",url:"Унесите исправан URL.",date:"Унесите исправан датум.",dateISO:"Унесите исправан датум (ISO).",number:"Унесите исправан број.",digits:"Унесите само цифе.",creditcard:"Унесите исправан број кредитне картице.",equalTo:"Унесите исту вредност поново.",extension:"Унесите вредност са одговарајућом екстензијом.",maxlength:a.validator.format("Унесите мање од {0} карактера."),minlength:a.validator.format("Унесите барем {0} карактера."),rangelength:a.validator.format("Унесите вредност дугачку између {0} и {1} карактера."),range:a.validator.format("Унесите вредност између {0} и {1}."),max:a.validator.format("Унесите вредност мању или једнаку {0}."),min:a.validator.format("Унесите вредност већу или једнаку {0}.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_sr_lat.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_sr_lat.min.js deleted file mode 100644 index 15326ab6a1..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_sr_lat.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Polje je obavezno.",remote:"Sredite ovo polje.",email:"Unesite ispravnu e-mail adresu",url:"Unesite ispravan URL.",date:"Unesite ispravan datum.",dateISO:"Unesite ispravan datum (ISO).",number:"Unesite ispravan broj.",digits:"Unesite samo cifre.",creditcard:"Unesite ispravan broj kreditne kartice.",equalTo:"Unesite istu vrednost ponovo.",extension:"Unesite vrednost sa odgovarajućom ekstenzijom.",maxlength:a.validator.format("Unesite manje od {0} karaktera."),minlength:a.validator.format("Unesite barem {0} karaktera."),rangelength:a.validator.format("Unesite vrednost dugačku između {0} i {1} karaktera."),range:a.validator.format("Unesite vrednost između {0} i {1}."),max:a.validator.format("Unesite vrednost manju ili jednaku {0}."),min:a.validator.format("Unesite vrednost veću ili jednaku {0}.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_sv.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_sv.js deleted file mode 100644 index c19e8ca021..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_sv.js +++ /dev/null @@ -1,35 +0,0 @@ -(function( factory ) { - if ( typeof define === "function" && define.amd ) { - define( ["jquery", "../jquery.validate"], factory ); - } else if (typeof module === "object" && module.exports) { - module.exports = factory( require( "jquery" ) ); - } else { - factory( jQuery ); - } -}(function( $ ) { - -/* - * Translated default messages for the jQuery validation plugin. - * Locale: SV (Swedish; Svenska) - */ -$.extend( $.validator.messages, { - required: "Detta fält är obligatoriskt.", - remote: "Var snäll och åtgärda detta fält.", - maxlength: $.validator.format( "Du får ange högst {0} tecken." ), - minlength: $.validator.format( "Du måste ange minst {0} tecken." ), - rangelength: $.validator.format( "Ange minst {0} och max {1} tecken." ), - email: "Ange en korrekt e-postadress.", - url: "Ange en korrekt URL.", - date: "Ange ett korrekt datum.", - dateISO: "Ange ett korrekt datum (ÅÅÅÅ-MM-DD).", - number: "Ange ett korrekt nummer.", - digits: "Ange endast siffror.", - equalTo: "Ange samma värde igen.", - range: $.validator.format( "Ange ett värde mellan {0} och {1}." ), - max: $.validator.format( "Ange ett värde som är mindre eller lika med {0}." ), - min: $.validator.format( "Ange ett värde som är större eller lika med {0}." ), - creditcard: "Ange ett korrekt kreditkortsnummer.", - pattern: "Ogiltigt format." -} ); -return $; -})); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_sv.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_sv.min.js deleted file mode 100644 index 68a7676ee7..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_sv.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Detta fält är obligatoriskt.",remote:"Var snäll och åtgärda detta fält.",maxlength:a.validator.format("Du får ange högst {0} tecken."),minlength:a.validator.format("Du måste ange minst {0} tecken."),rangelength:a.validator.format("Ange minst {0} och max {1} tecken."),email:"Ange en korrekt e-postadress.",url:"Ange en korrekt URL.",date:"Ange ett korrekt datum.",dateISO:"Ange ett korrekt datum (ÅÅÅÅ-MM-DD).",number:"Ange ett korrekt nummer.",digits:"Ange endast siffror.",equalTo:"Ange samma värde igen.",range:a.validator.format("Ange ett värde mellan {0} och {1}."),max:a.validator.format("Ange ett värde som är mindre eller lika med {0}."),min:a.validator.format("Ange ett värde som är större eller lika med {0}."),creditcard:"Ange ett korrekt kreditkortsnummer.",pattern:"Ogiltigt format."}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_th.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_th.min.js deleted file mode 100644 index 33771b31cd..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_th.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"โปรดระบุ",remote:"โปรดแก้ไขให้ถูกต้อง",email:"โปรดระบุที่อยู่อีเมล์ที่ถูกต้อง",url:"โปรดระบุ URL ที่ถูกต้อง",date:"โปรดระบุวันที่ ที่ถูกต้อง",dateISO:"โปรดระบุวันที่ ที่ถูกต้อง (ระบบ ISO).",number:"โปรดระบุทศนิยมที่ถูกต้อง",digits:"โปรดระบุจำนวนเต็มที่ถูกต้อง",creditcard:"โปรดระบุรหัสบัตรเครดิตที่ถูกต้อง",equalTo:"โปรดระบุค่าเดิมอีกครั้ง",extension:"โปรดระบุค่าที่มีส่วนขยายที่ถูกต้อง",maxlength:a.validator.format("โปรดอย่าระบุค่าที่ยาวกว่า {0} อักขระ"),minlength:a.validator.format("โปรดอย่าระบุค่าที่สั้นกว่า {0} อักขระ"),rangelength:a.validator.format("โปรดอย่าระบุค่าความยาวระหว่าง {0} ถึง {1} อักขระ"),range:a.validator.format("โปรดระบุค่าระหว่าง {0} และ {1}"),max:a.validator.format("โปรดระบุค่าน้อยกว่าหรือเท่ากับ {0}"),min:a.validator.format("โปรดระบุค่ามากกว่าหรือเท่ากับ {0}")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_tj.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_tj.min.js deleted file mode 100644 index 3ae8d22ddb..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_tj.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Ворид кардани ин филд маҷбури аст.",remote:"Илтимос, маълумоти саҳеҳ ворид кунед.",email:"Илтимос, почтаи электронии саҳеҳ ворид кунед.",url:"Илтимос, URL адреси саҳеҳ ворид кунед.",date:"Илтимос, таърихи саҳеҳ ворид кунед.",dateISO:"Илтимос, таърихи саҳеҳи (ISO)ӣ ворид кунед.",number:"Илтимос, рақамҳои саҳеҳ ворид кунед.",digits:"Илтимос, танҳо рақам ворид кунед.",creditcard:"Илтимос, кредит карди саҳеҳ ворид кунед.",equalTo:"Илтимос, миқдори баробар ворид кунед.",extension:"Илтимос, қофияи файлро дуруст интихоб кунед",maxlength:a.validator.format("Илтимос, бештар аз {0} рамз ворид накунед."),minlength:a.validator.format("Илтимос, камтар аз {0} рамз ворид накунед."),rangelength:a.validator.format("Илтимос, камтар аз {0} ва зиёда аз {1} рамз ворид кунед."),range:a.validator.format("Илтимос, аз {0} то {1} рақам зиёд ворид кунед."),max:a.validator.format("Илтимос, бештар аз {0} рақам ворид накунед."),min:a.validator.format("Илтимос, камтар аз {0} рақам ворид накунед.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_tr.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_tr.js deleted file mode 100644 index bb8c32ee89..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_tr.js +++ /dev/null @@ -1,36 +0,0 @@ -(function( factory ) { - if ( typeof define === "function" && define.amd ) { - define( ["jquery", "../jquery.validate"], factory ); - } else if (typeof module === "object" && module.exports) { - module.exports = factory( require( "jquery" ) ); - } else { - factory( jQuery ); - } -}(function( $ ) { - -/* - * Translated default messages for the jQuery validation plugin. - * Locale: TR (Turkish; Türkçe) - */ -$.extend( $.validator.messages, { - required: "Bu alanın doldurulması zorunludur.", - remote: "Lütfen bu alanı düzeltin.", - email: "Lütfen geçerli bir e-posta adresi giriniz.", - url: "Lütfen geçerli bir web adresi (URL) giriniz.", - date: "Lütfen geçerli bir tarih giriniz.", - dateISO: "Lütfen geçerli bir tarih giriniz(ISO formatında)", - number: "Lütfen geçerli bir sayı giriniz.", - digits: "Lütfen sadece sayısal karakterler giriniz.", - creditcard: "Lütfen geçerli bir kredi kartı giriniz.", - equalTo: "Lütfen aynı değeri tekrar giriniz.", - extension: "Lütfen geçerli uzantıya sahip bir değer giriniz.", - maxlength: $.validator.format( "Lütfen en fazla {0} karakter uzunluğunda bir değer giriniz." ), - minlength: $.validator.format( "Lütfen en az {0} karakter uzunluğunda bir değer giriniz." ), - rangelength: $.validator.format( "Lütfen en az {0} ve en fazla {1} uzunluğunda bir değer giriniz." ), - range: $.validator.format( "Lütfen {0} ile {1} arasında bir değer giriniz." ), - max: $.validator.format( "Lütfen {0} değerine eşit ya da daha küçük bir değer giriniz." ), - min: $.validator.format( "Lütfen {0} değerine eşit ya da daha büyük bir değer giriniz." ), - require_from_group: $.validator.format( "Lütfen bu alanların en az {0} tanesini doldurunuz." ) -} ); -return $; -})); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_tr.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_tr.min.js deleted file mode 100644 index ee95102664..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_tr.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Bu alanın doldurulması zorunludur.",remote:"Lütfen bu alanı düzeltin.",email:"Lütfen geçerli bir e-posta adresi giriniz.",url:"Lütfen geçerli bir web adresi (URL) giriniz.",date:"Lütfen geçerli bir tarih giriniz.",dateISO:"Lütfen geçerli bir tarih giriniz(ISO formatında)",number:"Lütfen geçerli bir sayı giriniz.",digits:"Lütfen sadece sayısal karakterler giriniz.",creditcard:"Lütfen geçerli bir kredi kartı giriniz.",equalTo:"Lütfen aynı değeri tekrar giriniz.",extension:"Lütfen geçerli uzantıya sahip bir değer giriniz.",maxlength:a.validator.format("Lütfen en fazla {0} karakter uzunluğunda bir değer giriniz."),minlength:a.validator.format("Lütfen en az {0} karakter uzunluğunda bir değer giriniz."),rangelength:a.validator.format("Lütfen en az {0} ve en fazla {1} uzunluğunda bir değer giriniz."),range:a.validator.format("Lütfen {0} ile {1} arasında bir değer giriniz."),max:a.validator.format("Lütfen {0} değerine eşit ya da daha küçük bir değer giriniz."),min:a.validator.format("Lütfen {0} değerine eşit ya da daha büyük bir değer giriniz."),require_from_group:a.validator.format("Lütfen bu alanların en az {0} tanesini doldurunuz.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_uk.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_uk.min.js deleted file mode 100644 index 2951a660cc..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_uk.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Це поле необхідно заповнити.",remote:"Будь ласка, введіть правильне значення.",email:"Будь ласка, введіть коректну адресу електронної пошти.",url:"Будь ласка, введіть коректний URL.",date:"Будь ласка, введіть коректну дату.",dateISO:"Будь ласка, введіть коректну дату у форматі ISO.",number:"Будь ласка, введіть число.",digits:"Вводите потрібно лише цифри.",creditcard:"Будь ласка, введіть правильний номер кредитної карти.",equalTo:"Будь ласка, введіть таке ж значення ще раз.",extension:"Будь ласка, виберіть файл з правильним розширенням.",maxlength:a.validator.format("Будь ласка, введіть не більше {0} символів."),minlength:a.validator.format("Будь ласка, введіть не менше {0} символів."),rangelength:a.validator.format("Будь ласка, введіть значення довжиною від {0} до {1} символів."),range:a.validator.format("Будь ласка, введіть число від {0} до {1}."),max:a.validator.format("Будь ласка, введіть число, менше або рівно {0}."),min:a.validator.format("Будь ласка, введіть число, більше або рівно {0}.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ur.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ur.min.js deleted file mode 100644 index 50b5585571..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_ur.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"ان معلومات کا اندراج ضروری ہے",remote:"ان معلومات کا اندراج ضروری ہے",email:"درج کی ہوئی ای میل درست نہیں ہے",url:"درج کیا گیا پتہ درست نہیں ہے",date:"درج کی گئی تاریخ درست نہیں ہے",dateISO:"معیار کے مطابق نہیں ہے (ISO) درج کی گئی تاریخ",number:"درج کیےگئے ہندسے درست نہیں ہیں",digits:"صرف ہندسے اندراج کئے جاسکتے ہیں",creditcard:"درج کیا گیا کارڈ نمبر درست نہیں ہے",equalTo:"اندراج کا موازنہ درست نہیں ہے",extension:"اندراج درست نہیں ہے",maxlength:a.validator.format("زیادہ سے زیادہ {0} کا اندراج کر سکتے ہیں"),minlength:a.validator.format("کم سے کم {0} کا اندراج کرنا ضروری ہے"),rangelength:a.validator.format("اندراج کا {0} اور {1}کے درمیان ہونا ضروری ہے"),range:a.validator.format("اندراج کا {0} اور {1} کے درمیان ہونا ضروری ہے"),max:a.validator.format("زیادہ سے زیادہ {0} کا اندراج کر سکتے ہیں"),min:a.validator.format("کم سے کم {0} کا اندراج کرنا ضروری ہے")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_vi.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_vi.js deleted file mode 100644 index ae415453dc..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_vi.js +++ /dev/null @@ -1,35 +0,0 @@ -(function( factory ) { - if ( typeof define === "function" && define.amd ) { - define( ["jquery", "../jquery.validate"], factory ); - } else if (typeof module === "object" && module.exports) { - module.exports = factory( require( "jquery" ) ); - } else { - factory( jQuery ); - } -}(function( $ ) { - -/* - * Translated default messages for the jQuery validation plugin. - * Locale: VI (Vietnamese; Tiếng Việt) - */ -$.extend( $.validator.messages, { - required: "Hãy nhập.", - remote: "Hãy sửa cho đúng.", - email: "Hãy nhập email.", - url: "Hãy nhập URL.", - date: "Hãy nhập ngày.", - dateISO: "Hãy nhập ngày (ISO).", - number: "Hãy nhập số.", - digits: "Hãy nhập chữ số.", - creditcard: "Hãy nhập số thẻ tín dụng.", - equalTo: "Hãy nhập thêm lần nữa.", - extension: "Phần mở rộng không đúng.", - maxlength: $.validator.format( "Hãy nhập từ {0} kí tự trở xuống." ), - minlength: $.validator.format( "Hãy nhập từ {0} kí tự trở lên." ), - rangelength: $.validator.format( "Hãy nhập từ {0} đến {1} kí tự." ), - range: $.validator.format( "Hãy nhập từ {0} đến {1}." ), - max: $.validator.format( "Hãy nhập từ {0} trở xuống." ), - min: $.validator.format( "Hãy nhập từ {0} trở lên." ) -} ); -return $; -})); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_vi.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_vi.min.js deleted file mode 100644 index 5c7def95fe..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_vi.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"Hãy nhập.",remote:"Hãy sửa cho đúng.",email:"Hãy nhập email.",url:"Hãy nhập URL.",date:"Hãy nhập ngày.",dateISO:"Hãy nhập ngày (ISO).",number:"Hãy nhập số.",digits:"Hãy nhập chữ số.",creditcard:"Hãy nhập số thẻ tín dụng.",equalTo:"Hãy nhập thêm lần nữa.",extension:"Phần mở rộng không đúng.",maxlength:a.validator.format("Hãy nhập từ {0} kí tự trở xuống."),minlength:a.validator.format("Hãy nhập từ {0} kí tự trở lên."),rangelength:a.validator.format("Hãy nhập từ {0} đến {1} kí tự."),range:a.validator.format("Hãy nhập từ {0} đến {1}."),max:a.validator.format("Hãy nhập từ {0} trở xuống."),min:a.validator.format("Hãy nhập từ {0} trở lên.")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_zh.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_zh.js deleted file mode 100644 index afc74ab066..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_zh.js +++ /dev/null @@ -1,36 +0,0 @@ -(function( factory ) { - if ( typeof define === "function" && define.amd ) { - define( ["jquery", "../jquery.validate"], factory ); - } else if (typeof module === "object" && module.exports) { - module.exports = factory( require( "jquery" ) ); - } else { - factory( jQuery ); - } -}(function( $ ) { - -/* - * Translated default messages for the jQuery validation plugin. - * Locale: ZH (Chinese, 中文 (Zhōngwén), 汉语, 漢語) - */ -$.extend( $.validator.messages, { - required: "这是必填字段", - remote: "请修正此字段", - email: "请输入有效的电子邮件地址", - url: "请输入有效的网址", - date: "请输入有效的日期", - dateISO: "请输入有效的日期 (YYYY-MM-DD)", - number: "请输入有效的数字", - digits: "只能输入数字", - creditcard: "请输入有效的信用卡号码", - equalTo: "你的输入不相同", - extension: "请输入有效的后缀", - maxlength: $.validator.format( "最多可以输入 {0} 个字符" ), - minlength: $.validator.format( "最少要输入 {0} 个字符" ), - rangelength: $.validator.format( "请输入长度在 {0} 到 {1} 之间的字符串" ), - range: $.validator.format( "请输入范围在 {0} 到 {1} 之间的数值" ), - step: $.validator.format( "请输入 {0} 的整数倍值" ), - max: $.validator.format( "请输入不大于 {0} 的数值" ), - min: $.validator.format( "请输入不小于 {0} 的数值" ) -} ); -return $; -})); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_zh.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_zh.min.js deleted file mode 100644 index f006f5108b..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_zh.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"这是必填字段",remote:"请修正此字段",email:"请输入有效的电子邮件地址",url:"请输入有效的网址",date:"请输入有效的日期",dateISO:"请输入有效的日期 (YYYY-MM-DD)",number:"请输入有效的数字",digits:"只能输入数字",creditcard:"请输入有效的信用卡号码",equalTo:"你的输入不相同",extension:"请输入有效的后缀",maxlength:a.validator.format("最多可以输入 {0} 个字符"),minlength:a.validator.format("最少要输入 {0} 个字符"),rangelength:a.validator.format("请输入长度在 {0} 到 {1} 之间的字符串"),range:a.validator.format("请输入范围在 {0} 到 {1} 之间的数值"),step:a.validator.format("请输入 {0} 的整数倍值"),max:a.validator.format("请输入不大于 {0} 的数值"),min:a.validator.format("请输入不小于 {0} 的数值")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_zh_TW.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_zh_TW.min.js deleted file mode 100644 index e2474bc16f..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/messages_zh_TW.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.messages,{required:"必須填寫",remote:"請修正此欄位",email:"請輸入有效的電子郵件",url:"請輸入有效的網址",date:"請輸入有效的日期",dateISO:"請輸入有效的日期 (YYYY-MM-DD)",number:"請輸入正確的數值",digits:"只可輸入數字",creditcard:"請輸入有效的信用卡號碼",equalTo:"請重複輸入一次",extension:"請輸入有效的後綴",maxlength:a.validator.format("最多 {0} 個字"),minlength:a.validator.format("最少 {0} 個字"),rangelength:a.validator.format("請輸入長度為 {0} 至 {1} 之間的字串"),range:a.validator.format("請輸入 {0} 至 {1} 之間的數值"),max:a.validator.format("請輸入不大於 {0} 的數值"),min:a.validator.format("請輸入不小於 {0} 的數值")}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/methods_de.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/methods_de.min.js deleted file mode 100644 index 187560c1b3..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/methods_de.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.methods,{date:function(a,b){return this.optional(b)||/^\d\d?\.\d\d?\.\d\d\d?\d?$/.test(a)},number:function(a,b){return this.optional(b)||/^-?(?:\d+|\d{1,3}(?:\.\d{3})+)(?:,\d+)?$/.test(a)}}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/methods_es_CL.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/methods_es_CL.min.js deleted file mode 100644 index f25c78412e..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/methods_es_CL.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.methods,{date:function(a,b){return this.optional(b)||/^\d\d?\-\d\d?\-\d\d\d?\d?$/.test(a)},number:function(a,b){return this.optional(b)||/^-?(?:\d+|\d{1,3}(?:\.\d{3})+)(?:,\d+)?$/.test(a)}}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/methods_fi.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/methods_fi.min.js deleted file mode 100644 index 59c86e0ccc..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/methods_fi.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.methods,{date:function(a,b){return this.optional(b)||/^\d{1,2}\.\d{1,2}\.\d{4}$/.test(a)},number:function(a,b){return this.optional(b)||/^-?(?:\d+)(?:,\d+)?$/.test(a)}}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/methods_it.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/methods_it.js deleted file mode 100644 index b0ad79e501..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/methods_it.js +++ /dev/null @@ -1,24 +0,0 @@ -(function( factory ) { - if ( typeof define === "function" && define.amd ) { - define( ["jquery", "../jquery.validate"], factory ); - } else if (typeof module === "object" && module.exports) { - module.exports = factory( require( "jquery" ) ); - } else { - factory( jQuery ); - } -}(function( $ ) { - -/* - * Localized default methods for the jQuery validation plugin. - * Locale: IT - */ -$.extend( $.validator.methods, { - date: function( value, element ) { - return this.optional( element ) || /^\d\d?\-\d\d?\-\d\d\d?\d?$/.test( value ); - }, - number: function( value, element ) { - return this.optional( element ) || /^-?(?:\d+|\d{1,3}(?:\.\d{3})+)(?:,\d+)?$/.test( value ); - } -} ); -return $; -})); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/methods_it.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/methods_it.min.js deleted file mode 100644 index f25c78412e..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/methods_it.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.methods,{date:function(a,b){return this.optional(b)||/^\d\d?\-\d\d?\-\d\d\d?\d?$/.test(a)},number:function(a,b){return this.optional(b)||/^-?(?:\d+|\d{1,3}(?:\.\d{3})+)(?:,\d+)?$/.test(a)}}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/methods_nl.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/methods_nl.min.js deleted file mode 100644 index bf4f132826..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/methods_nl.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.methods,{date:function(a,b){return this.optional(b)||/^\d\d?[\.\/\-]\d\d?[\.\/\-]\d\d\d?\d?$/.test(a)},number:function(a,b){return this.optional(b)||/^-?(?:\d+|\d{1,3}(?:\.\d{3})+)(?:,\d+)?$/.test(a)}}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/methods_pt.min.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/methods_pt.min.js deleted file mode 100644 index dfed4d2621..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery-validation/localization/methods_pt.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.19.0 - 11/28/2018 - * https://jqueryvalidation.org/ - * Copyright (c) 2018 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","../jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return a.extend(a.validator.methods,{date:function(a,b){return this.optional(b)||/^\d\d?\/\d\d?\/\d\d\d?\d?$/.test(a)}}),a}); \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery/jquery.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery/jquery.js deleted file mode 100644 index 9b5206bcc6..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/jquery/jquery.js +++ /dev/null @@ -1,10364 +0,0 @@ -/*! - * jQuery JavaScript Library v3.3.1 - * https://jquery.com/ - * - * Includes Sizzle.js - * https://sizzlejs.com/ - * - * Copyright JS Foundation and other contributors - * Released under the MIT license - * https://jquery.org/license - * - * Date: 2018-01-20T17:24Z - */ -( function( global, factory ) { - - "use strict"; - - if ( typeof module === "object" && typeof module.exports === "object" ) { - - // For CommonJS and CommonJS-like environments where a proper `window` - // is present, execute the factory and get jQuery. - // For environments that do not have a `window` with a `document` - // (such as Node.js), expose a factory as module.exports. - // This accentuates the need for the creation of a real `window`. - // e.g. var jQuery = require("jquery")(window); - // See ticket #14549 for more info. - module.exports = global.document ? - factory( global, true ) : - function( w ) { - if ( !w.document ) { - throw new Error( "jQuery requires a window with a document" ); - } - return factory( w ); - }; - } else { - factory( global ); - } - -// Pass this if window is not defined yet -} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { - -// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 -// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode -// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common -// enough that all such attempts are guarded in a try block. -"use strict"; - -var arr = []; - -var document = window.document; - -var getProto = Object.getPrototypeOf; - -var slice = arr.slice; - -var concat = arr.concat; - -var push = arr.push; - -var indexOf = arr.indexOf; - -var class2type = {}; - -var toString = class2type.toString; - -var hasOwn = class2type.hasOwnProperty; - -var fnToString = hasOwn.toString; - -var ObjectFunctionString = fnToString.call( Object ); - -var support = {}; - -var isFunction = function isFunction( obj ) { - - // Support: Chrome <=57, Firefox <=52 - // In some browsers, typeof returns "function" for HTML elements - // (i.e., `typeof document.createElement( "object" ) === "function"`). - // We don't want to classify *any* DOM node as a function. - return typeof obj === "function" && typeof obj.nodeType !== "number"; - }; - - -var isWindow = function isWindow( obj ) { - return obj != null && obj === obj.window; - }; - - - - - var preservedScriptAttributes = { - type: true, - src: true, - noModule: true - }; - - function DOMEval( code, doc, node ) { - doc = doc || document; - - var i, - script = doc.createElement( "script" ); - - script.text = code; - if ( node ) { - for ( i in preservedScriptAttributes ) { - if ( node[ i ] ) { - script[ i ] = node[ i ]; - } - } - } - doc.head.appendChild( script ).parentNode.removeChild( script ); - } - - -function toType( obj ) { - if ( obj == null ) { - return obj + ""; - } - - // Support: Android <=2.3 only (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call( obj ) ] || "object" : - typeof obj; -} -/* global Symbol */ -// Defining this global in .eslintrc.json would create a danger of using the global -// unguarded in another place, it seems safer to define global only for this module - - - -var - version = "3.3.1", - - // Define a local copy of jQuery - jQuery = function( selector, context ) { - - // The jQuery object is actually just the init constructor 'enhanced' - // Need init if jQuery is called (just allow error to be thrown if not included) - return new jQuery.fn.init( selector, context ); - }, - - // Support: Android <=4.0 only - // Make sure we trim BOM and NBSP - rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; - -jQuery.fn = jQuery.prototype = { - - // The current version of jQuery being used - jquery: version, - - constructor: jQuery, - - // The default length of a jQuery object is 0 - length: 0, - - toArray: function() { - return slice.call( this ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - - // Return all the elements in a clean array - if ( num == null ) { - return slice.call( this ); - } - - // Return just the one element from the set - return num < 0 ? this[ num + this.length ] : this[ num ]; - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - - // Build a new jQuery matched element set - var ret = jQuery.merge( this.constructor(), elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - each: function( callback ) { - return jQuery.each( this, callback ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map( this, function( elem, i ) { - return callback.call( elem, i, elem ); - } ) ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ) ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - eq: function( i ) { - var len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); - }, - - end: function() { - return this.prevObject || this.constructor(); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: arr.sort, - splice: arr.splice -}; - -jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, - target = arguments[ 0 ] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - - // Skip the boolean and the target - target = arguments[ i ] || {}; - i++; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !isFunction( target ) ) { - target = {}; - } - - // Extend jQuery itself if only one argument is passed - if ( i === length ) { - target = this; - i--; - } - - for ( ; i < length; i++ ) { - - // Only deal with non-null/undefined values - if ( ( options = arguments[ i ] ) != null ) { - - // Extend the base object - for ( name in options ) { - src = target[ name ]; - copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject( copy ) || - ( copyIsArray = Array.isArray( copy ) ) ) ) { - - if ( copyIsArray ) { - copyIsArray = false; - clone = src && Array.isArray( src ) ? src : []; - - } else { - clone = src && jQuery.isPlainObject( src ) ? src : {}; - } - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend( { - - // Unique for each copy of jQuery on the page - expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), - - // Assume jQuery is ready without the ready module - isReady: true, - - error: function( msg ) { - throw new Error( msg ); - }, - - noop: function() {}, - - isPlainObject: function( obj ) { - var proto, Ctor; - - // Detect obvious negatives - // Use toString instead of jQuery.type to catch host objects - if ( !obj || toString.call( obj ) !== "[object Object]" ) { - return false; - } - - proto = getProto( obj ); - - // Objects with no prototype (e.g., `Object.create( null )`) are plain - if ( !proto ) { - return true; - } - - // Objects with prototype are plain iff they were constructed by a global Object function - Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; - return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; - }, - - isEmptyObject: function( obj ) { - - /* eslint-disable no-unused-vars */ - // See https://github.com/eslint/eslint/issues/6125 - var name; - - for ( name in obj ) { - return false; - } - return true; - }, - - // Evaluates a script in a global context - globalEval: function( code ) { - DOMEval( code ); - }, - - each: function( obj, callback ) { - var length, i = 0; - - if ( isArrayLike( obj ) ) { - length = obj.length; - for ( ; i < length; i++ ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } else { - for ( i in obj ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } - - return obj; - }, - - // Support: Android <=4.0 only - trim: function( text ) { - return text == null ? - "" : - ( text + "" ).replace( rtrim, "" ); - }, - - // results is for internal usage only - makeArray: function( arr, results ) { - var ret = results || []; - - if ( arr != null ) { - if ( isArrayLike( Object( arr ) ) ) { - jQuery.merge( ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - push.call( ret, arr ); - } - } - - return ret; - }, - - inArray: function( elem, arr, i ) { - return arr == null ? -1 : indexOf.call( arr, elem, i ); - }, - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - merge: function( first, second ) { - var len = +second.length, - j = 0, - i = first.length; - - for ( ; j < len; j++ ) { - first[ i++ ] = second[ j ]; - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, invert ) { - var callbackInverse, - matches = [], - i = 0, - length = elems.length, - callbackExpect = !invert; - - // Go through the array, only saving the items - // that pass the validator function - for ( ; i < length; i++ ) { - callbackInverse = !callback( elems[ i ], i ); - if ( callbackInverse !== callbackExpect ) { - matches.push( elems[ i ] ); - } - } - - return matches; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var length, value, - i = 0, - ret = []; - - // Go through the array, translating each of the items to their new values - if ( isArrayLike( elems ) ) { - length = elems.length; - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - - // Go through every key on the object, - } else { - for ( i in elems ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - } - - // Flatten any nested arrays - return concat.apply( [], ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // jQuery.support is not used in Core but other projects attach their - // properties to it so it needs to exist. - support: support -} ); - -if ( typeof Symbol === "function" ) { - jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; -} - -// Populate the class2type map -jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), -function( i, name ) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -} ); - -function isArrayLike( obj ) { - - // Support: real iOS 8.2 only (not reproducible in simulator) - // `in` check used to prevent JIT error (gh-2145) - // hasOwn isn't used here due to false negatives - // regarding Nodelist length in IE - var length = !!obj && "length" in obj && obj.length, - type = toType( obj ); - - if ( isFunction( obj ) || isWindow( obj ) ) { - return false; - } - - return type === "array" || length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj; -} -var Sizzle = -/*! - * Sizzle CSS Selector Engine v2.3.3 - * https://sizzlejs.com/ - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2016-08-08 - */ -(function( window ) { - -var i, - support, - Expr, - getText, - isXML, - tokenize, - compile, - select, - outermostContext, - sortInput, - hasDuplicate, - - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - - // Instance-specific data - expando = "sizzle" + 1 * new Date(), - preferredDoc = window.document, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - } - return 0; - }, - - // Instance methods - hasOwn = ({}).hasOwnProperty, - arr = [], - pop = arr.pop, - push_native = arr.push, - push = arr.push, - slice = arr.slice, - // Use a stripped-down indexOf as it's faster than native - // https://jsperf.com/thor-indexof-vs-for/5 - indexOf = function( list, elem ) { - var i = 0, - len = list.length; - for ( ; i < len; i++ ) { - if ( list[i] === elem ) { - return i; - } - } - return -1; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", - - // Regular expressions - - // http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - - // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", - - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + - // Operator (capture 2) - "*([*^$|!~]?=)" + whitespace + - // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + - "*\\]", - - pseudos = ":(" + identifier + ")(?:\\((" + - // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: - // 1. quoted (capture 3; capture 4 or capture 5) - "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + - // 2. simple (capture 6) - "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + - // 3. anything else (capture 2) - ".*" + - ")\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), - - rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), - - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - "ID": new RegExp( "^#(" + identifier + ")" ), - "CLASS": new RegExp( "^\\.(" + identifier + ")" ), - "TAG": new RegExp( "^(" + identifier + "|[*])" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + - "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + - "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + - whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rnative = /^[^{]+\{\s*\[native \w/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rsibling = /[+~]/, - - // CSS escapes - // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), - funescape = function( _, escaped, escapedWhitespace ) { - var high = "0x" + escaped - 0x10000; - // NaN means non-codepoint - // Support: Firefox<24 - // Workaround erroneous numeric interpretation of +"0x" - return high !== high || escapedWhitespace ? - escaped : - high < 0 ? - // BMP codepoint - String.fromCharCode( high + 0x10000 ) : - // Supplemental Plane codepoint (surrogate pair) - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }, - - // CSS string/identifier serialization - // https://drafts.csswg.org/cssom/#common-serializing-idioms - rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, - fcssescape = function( ch, asCodePoint ) { - if ( asCodePoint ) { - - // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER - if ( ch === "\0" ) { - return "\uFFFD"; - } - - // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; - } - - // Other potentially-special ASCII characters get backslash-escaped - return "\\" + ch; - }, - - // Used for iframes - // See setDocument() - // Removing the function wrapper causes a "Permission Denied" - // error in IE - unloadHandler = function() { - setDocument(); - }, - - disabledAncestor = addCombinator( - function( elem ) { - return elem.disabled === true && ("form" in elem || "label" in elem); - }, - { dir: "parentNode", next: "legend" } - ); - -// Optimize for push.apply( _, NodeList ) -try { - push.apply( - (arr = slice.call( preferredDoc.childNodes )), - preferredDoc.childNodes - ); - // Support: Android<4.0 - // Detect silently failing push.apply - arr[ preferredDoc.childNodes.length ].nodeType; -} catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { - push_native.apply( target, slice.call(els) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - var j = target.length, - i = 0; - // Can't trust NodeList.length - while ( (target[j++] = els[i++]) ) {} - target.length = j - 1; - } - }; -} - -function Sizzle( selector, context, results, seed ) { - var m, i, elem, nid, match, groups, newSelector, - newContext = context && context.ownerDocument, - - // nodeType defaults to 9, since context defaults to document - nodeType = context ? context.nodeType : 9; - - results = results || []; - - // Return early from calls with invalid selector or context - if ( typeof selector !== "string" || !selector || - nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { - - return results; - } - - // Try to shortcut find operations (as opposed to filters) in HTML documents - if ( !seed ) { - - if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { - setDocument( context ); - } - context = context || document; - - if ( documentIsHTML ) { - - // If the selector is sufficiently simple, try using a "get*By*" DOM method - // (excepting DocumentFragment context, where the methods don't exist) - if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { - - // ID selector - if ( (m = match[1]) ) { - - // Document context - if ( nodeType === 9 ) { - if ( (elem = context.getElementById( m )) ) { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - - // Element context - } else { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( newContext && (elem = newContext.getElementById( m )) && - contains( context, elem ) && - elem.id === m ) { - - results.push( elem ); - return results; - } - } - - // Type selector - } else if ( match[2] ) { - push.apply( results, context.getElementsByTagName( selector ) ); - return results; - - // Class selector - } else if ( (m = match[3]) && support.getElementsByClassName && - context.getElementsByClassName ) { - - push.apply( results, context.getElementsByClassName( m ) ); - return results; - } - } - - // Take advantage of querySelectorAll - if ( support.qsa && - !compilerCache[ selector + " " ] && - (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { - - if ( nodeType !== 1 ) { - newContext = context; - newSelector = selector; - - // qSA looks outside Element context, which is not what we want - // Thanks to Andrew Dupont for this workaround technique - // Support: IE <=8 - // Exclude object elements - } else if ( context.nodeName.toLowerCase() !== "object" ) { - - // Capture the context ID, setting it first if necessary - if ( (nid = context.getAttribute( "id" )) ) { - nid = nid.replace( rcssescape, fcssescape ); - } else { - context.setAttribute( "id", (nid = expando) ); - } - - // Prefix every selector in the list - groups = tokenize( selector ); - i = groups.length; - while ( i-- ) { - groups[i] = "#" + nid + " " + toSelector( groups[i] ); - } - newSelector = groups.join( "," ); - - // Expand context for sibling selectors - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || - context; - } - - if ( newSelector ) { - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch ( qsaError ) { - } finally { - if ( nid === expando ) { - context.removeAttribute( "id" ); - } - } - } - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); -} - -/** - * Create key-value caches of limited size - * @returns {function(string, object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var keys = []; - - function cache( key, value ) { - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key + " " ) > Expr.cacheLength ) { - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return (cache[ key + " " ] = value); - } - return cache; -} - -/** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created element and returns a boolean result - */ -function assert( fn ) { - var el = document.createElement("fieldset"); - - try { - return !!fn( el ); - } catch (e) { - return false; - } finally { - // Remove from its parent by default - if ( el.parentNode ) { - el.parentNode.removeChild( el ); - } - // release memory in IE - el = null; - } -} - -/** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ -function addHandle( attrs, handler ) { - var arr = attrs.split("|"), - i = arr.length; - - while ( i-- ) { - Expr.attrHandle[ arr[i] ] = handler; - } -} - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - a.sourceIndex - b.sourceIndex; - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( (cur = cur.nextSibling) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - -/** - * Returns a function to use in pseudos for input types - * @param {String} type - */ -function createInputPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for buttons - * @param {String} type - */ -function createButtonPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for :enabled/:disabled - * @param {Boolean} disabled true for :disabled; false for :enabled - */ -function createDisabledPseudo( disabled ) { - - // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable - return function( elem ) { - - // Only certain elements can match :enabled or :disabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled - if ( "form" in elem ) { - - // Check for inherited disabledness on relevant non-disabled elements: - // * listed form-associated elements in a disabled fieldset - // https://html.spec.whatwg.org/multipage/forms.html#category-listed - // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled - // * option elements in a disabled optgroup - // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled - // All such elements have a "form" property. - if ( elem.parentNode && elem.disabled === false ) { - - // Option elements defer to a parent optgroup if present - if ( "label" in elem ) { - if ( "label" in elem.parentNode ) { - return elem.parentNode.disabled === disabled; - } else { - return elem.disabled === disabled; - } - } - - // Support: IE 6 - 11 - // Use the isDisabled shortcut property to check for disabled fieldset ancestors - return elem.isDisabled === disabled || - - // Where there is no isDisabled, check manually - /* jshint -W018 */ - elem.isDisabled !== !disabled && - disabledAncestor( elem ) === disabled; - } - - return elem.disabled === disabled; - - // Try to winnow out elements that can't be disabled before trusting the disabled property. - // Some victims get caught in our net (label, legend, menu, track), but it shouldn't - // even exist on them, let alone have a boolean value. - } else if ( "label" in elem ) { - return elem.disabled === disabled; - } - - // Remaining elements are neither :enabled nor :disabled - return false; - }; -} - -/** - * Returns a function to use in pseudos for positionals - * @param {Function} fn - */ -function createPositionalPseudo( fn ) { - return markFunction(function( argument ) { - argument = +argument; - return markFunction(function( seed, matches ) { - var j, - matchIndexes = fn( [], seed.length, argument ), - i = matchIndexes.length; - - // Match elements found at the specified indexes - while ( i-- ) { - if ( seed[ (j = matchIndexes[i]) ] ) { - seed[j] = !(matches[j] = seed[j]); - } - } - }); - }); -} - -/** - * Checks a node for validity as a Sizzle context - * @param {Element|Object=} context - * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value - */ -function testContext( context ) { - return context && typeof context.getElementsByTagName !== "undefined" && context; -} - -// Expose support vars for convenience -support = Sizzle.support = {}; - -/** - * Detects XML nodes - * @param {Element|Object} elem An element or a document - * @returns {Boolean} True iff elem is a non-HTML XML node - */ -isXML = Sizzle.isXML = function( elem ) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = elem && (elem.ownerDocument || elem).documentElement; - return documentElement ? documentElement.nodeName !== "HTML" : false; -}; - -/** - * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ -setDocument = Sizzle.setDocument = function( node ) { - var hasCompare, subWindow, - doc = node ? node.ownerDocument || node : preferredDoc; - - // Return early if doc is invalid or already selected - if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { - return document; - } - - // Update global variables - document = doc; - docElem = document.documentElement; - documentIsHTML = !isXML( document ); - - // Support: IE 9-11, Edge - // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) - if ( preferredDoc !== document && - (subWindow = document.defaultView) && subWindow.top !== subWindow ) { - - // Support: IE 11, Edge - if ( subWindow.addEventListener ) { - subWindow.addEventListener( "unload", unloadHandler, false ); - - // Support: IE 9 - 10 only - } else if ( subWindow.attachEvent ) { - subWindow.attachEvent( "onunload", unloadHandler ); - } - } - - /* Attributes - ---------------------------------------------------------------------- */ - - // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties - // (excepting IE8 booleans) - support.attributes = assert(function( el ) { - el.className = "i"; - return !el.getAttribute("className"); - }); - - /* getElement(s)By* - ---------------------------------------------------------------------- */ - - // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert(function( el ) { - el.appendChild( document.createComment("") ); - return !el.getElementsByTagName("*").length; - }); - - // Support: IE<9 - support.getElementsByClassName = rnative.test( document.getElementsByClassName ); - - // Support: IE<10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programmatically-set names, - // so use a roundabout getElementsByName test - support.getById = assert(function( el ) { - docElem.appendChild( el ).id = expando; - return !document.getElementsByName || !document.getElementsByName( expando ).length; - }); - - // ID filter and find - if ( support.getById ) { - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - return elem.getAttribute("id") === attrId; - }; - }; - Expr.find["ID"] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var elem = context.getElementById( id ); - return elem ? [ elem ] : []; - } - }; - } else { - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - var node = typeof elem.getAttributeNode !== "undefined" && - elem.getAttributeNode("id"); - return node && node.value === attrId; - }; - }; - - // Support: IE 6 - 7 only - // getElementById is not reliable as a find shortcut - Expr.find["ID"] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var node, i, elems, - elem = context.getElementById( id ); - - if ( elem ) { - - // Verify the id attribute - node = elem.getAttributeNode("id"); - if ( node && node.value === id ) { - return [ elem ]; - } - - // Fall back on getElementsByName - elems = context.getElementsByName( id ); - i = 0; - while ( (elem = elems[i++]) ) { - node = elem.getAttributeNode("id"); - if ( node && node.value === id ) { - return [ elem ]; - } - } - } - - return []; - } - }; - } - - // Tag - Expr.find["TAG"] = support.getElementsByTagName ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( tag ); - - // DocumentFragment nodes don't have gEBTN - } else if ( support.qsa ) { - return context.querySelectorAll( tag ); - } - } : - - function( tag, context ) { - var elem, - tmp = [], - i = 0, - // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too - results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - while ( (elem = results[i++]) ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } - - return tmp; - } - return results; - }; - - // Class - Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { - if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { - return context.getElementsByClassName( className ); - } - }; - - /* QSA/matchesSelector - ---------------------------------------------------------------------- */ - - // QSA and matchesSelector support - - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21) - // We allow this because of a bug in IE8/9 that throws an error - // whenever `document.activeElement` is accessed on an iframe - // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See https://bugs.jquery.com/ticket/13378 - rbuggyQSA = []; - - if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert(function( el ) { - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // https://bugs.jquery.com/ticket/12359 - docElem.appendChild( el ).innerHTML = "" + - ""; - - // Support: IE8, Opera 11-12.16 - // Nothing should be selected when empty strings follow ^= or $= or *= - // The test attribute must be unknown in Opera but "safe" for WinRT - // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( el.querySelectorAll("[msallowcapture^='']").length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); - } - - // Support: IE8 - // Boolean attributes and "value" are not treated correctly - if ( !el.querySelectorAll("[selected]").length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); - } - - // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ - if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push("~="); - } - - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !el.querySelectorAll(":checked").length ) { - rbuggyQSA.push(":checked"); - } - - // Support: Safari 8+, iOS 8+ - // https://bugs.webkit.org/show_bug.cgi?id=136851 - // In-page `selector#id sibling-combinator selector` fails - if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push(".#.+[+~]"); - } - }); - - assert(function( el ) { - el.innerHTML = "" + - ""; - - // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - var input = document.createElement("input"); - input.setAttribute( "type", "hidden" ); - el.appendChild( input ).setAttribute( "name", "D" ); - - // Support: IE8 - // Enforce case-sensitivity of name attribute - if ( el.querySelectorAll("[name=d]").length ) { - rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( el.querySelectorAll(":enabled").length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: IE9-11+ - // IE's :disabled selector does not pick up the children of disabled fieldsets - docElem.appendChild( el ).disabled = true; - if ( el.querySelectorAll(":disabled").length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Opera 10-11 does not throw on post-comma invalid pseudos - el.querySelectorAll("*,:x"); - rbuggyQSA.push(",.*:"); - }); - } - - if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || - docElem.webkitMatchesSelector || - docElem.mozMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector) )) ) { - - assert(function( el ) { - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( el, "*" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( el, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - }); - } - - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); - - /* Contains - ---------------------------------------------------------------------- */ - hasCompare = rnative.test( docElem.compareDocumentPosition ); - - // Element contains another - // Purposefully self-exclusive - // As in, an element does not contain itself - contains = hasCompare || rnative.test( docElem.contains ) ? - function( a, b ) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - )); - } : - function( a, b ) { - if ( b ) { - while ( (b = b.parentNode) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - - /* Sorting - ---------------------------------------------------------------------- */ - - // Document order sorting - sortOrder = hasCompare ? - function( a, b ) { - - // Flag for duplicate removal - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - // Sort on method existence if only one input has compareDocumentPosition - var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; - if ( compare ) { - return compare; - } - - // Calculate position if both inputs belong to the same document - compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? - a.compareDocumentPosition( b ) : - - // Otherwise we know they are disconnected - 1; - - // Disconnected nodes - if ( compare & 1 || - (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { - - // Choose the first element that is related to our preferred document - if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { - return -1; - } - if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { - return 1; - } - - // Maintain original order - return sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - } - - return compare & 4 ? -1 : 1; - } : - function( a, b ) { - // Exit early if the nodes are identical - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [ a ], - bp = [ b ]; - - // Parentless nodes are either documents or disconnected - if ( !aup || !bup ) { - return a === document ? -1 : - b === document ? 1 : - aup ? -1 : - bup ? 1 : - sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - - // If the nodes are siblings, we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - } - - // Otherwise we need full lists of their ancestors for comparison - cur = a; - while ( (cur = cur.parentNode) ) { - ap.unshift( cur ); - } - cur = b; - while ( (cur = cur.parentNode) ) { - bp.unshift( cur ); - } - - // Walk down the tree looking for a discrepancy - while ( ap[i] === bp[i] ) { - i++; - } - - return i ? - // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[i], bp[i] ) : - - // Otherwise nodes in our document sort first - ap[i] === preferredDoc ? -1 : - bp[i] === preferredDoc ? 1 : - 0; - }; - - return document; -}; - -Sizzle.matches = function( expr, elements ) { - return Sizzle( expr, null, null, elements ); -}; - -Sizzle.matchesSelector = function( elem, expr ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - // Make sure that attribute selectors are quoted - expr = expr.replace( rattributeQuotes, "='$1']" ); - - if ( support.matchesSelector && documentIsHTML && - !compilerCache[ expr + " " ] && - ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && - ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { - - try { - var ret = matches.call( elem, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || support.disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch (e) {} - } - - return Sizzle( expr, document, null, [ elem ] ).length > 0; -}; - -Sizzle.contains = function( context, elem ) { - // Set document vars if needed - if ( ( context.ownerDocument || context ) !== document ) { - setDocument( context ); - } - return contains( context, elem ); -}; - -Sizzle.attr = function( elem, name ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - var fn = Expr.attrHandle[ name.toLowerCase() ], - // Don't get fooled by Object.prototype properties (jQuery #13807) - val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? - fn( elem, name, !documentIsHTML ) : - undefined; - - return val !== undefined ? - val : - support.attributes || !documentIsHTML ? - elem.getAttribute( name ) : - (val = elem.getAttributeNode(name)) && val.specified ? - val.value : - null; -}; - -Sizzle.escape = function( sel ) { - return (sel + "").replace( rcssescape, fcssescape ); -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -/** - * Document sorting and removing duplicates - * @param {ArrayLike} results - */ -Sizzle.uniqueSort = function( results ) { - var elem, - duplicates = [], - j = 0, - i = 0; - - // Unless we *know* we can detect duplicates, assume their presence - hasDuplicate = !support.detectDuplicates; - sortInput = !support.sortStable && results.slice( 0 ); - results.sort( sortOrder ); - - if ( hasDuplicate ) { - while ( (elem = results[i++]) ) { - if ( elem === results[ i ] ) { - j = duplicates.push( i ); - } - } - while ( j-- ) { - results.splice( duplicates[ j ], 1 ); - } - } - - // Clear input after sorting to release objects - // See https://github.com/jquery/sizzle/pull/225 - sortInput = null; - - return results; -}; - -/** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -getText = Sizzle.getText = function( elem ) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( !nodeType ) { - // If no nodeType, this is expected to be an array - while ( (node = elem[i++]) ) { - // Do not traverse comment nodes - ret += getText( node ); - } - } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - // Use textContent for elements - // innerText usage removed for consistency of new lines (jQuery #11153) - if ( typeof elem.textContent === "string" ) { - return elem.textContent; - } else { - // Traverse its children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - // Do not include comment or processing instruction nodes - - return ret; -}; - -Expr = Sizzle.selectors = { - - // Can be adjusted by the user - cacheLength: 50, - - createPseudo: markFunction, - - match: matchExpr, - - attrHandle: {}, - - find: {}, - - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, - - preFilter: { - "ATTR": function( match ) { - match[1] = match[1].replace( runescape, funescape ); - - // Move the given value to match[3] whether quoted or unquoted - match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); - - if ( match[2] === "~=" ) { - match[3] = " " + match[3] + " "; - } - - return match.slice( 0, 4 ); - }, - - "CHILD": function( match ) { - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[1] = match[1].toLowerCase(); - - if ( match[1].slice( 0, 3 ) === "nth" ) { - // nth-* requires argument - if ( !match[3] ) { - Sizzle.error( match[0] ); - } - - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); - match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); - - // other types prohibit arguments - } else if ( match[3] ) { - Sizzle.error( match[0] ); - } - - return match; - }, - - "PSEUDO": function( match ) { - var excess, - unquoted = !match[6] && match[2]; - - if ( matchExpr["CHILD"].test( match[0] ) ) { - return null; - } - - // Accept quoted arguments as-is - if ( match[3] ) { - match[2] = match[4] || match[5] || ""; - - // Strip excess characters from unquoted arguments - } else if ( unquoted && rpseudo.test( unquoted ) && - // Get excess from tokenize (recursively) - (excess = tokenize( unquoted, true )) && - // advance to the next closing parenthesis - (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { - - // excess is a negative index - match[0] = match[0].slice( 0, excess ); - match[2] = unquoted.slice( 0, excess ); - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - - "TAG": function( nodeNameSelector ) { - var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); - return nodeNameSelector === "*" ? - function() { return true; } : - function( elem ) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - - "CLASS": function( className ) { - var pattern = classCache[ className + " " ]; - - return pattern || - (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && - classCache( className, function( elem ) { - return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); - }); - }, - - "ATTR": function( name, operator, check ) { - return function( elem ) { - var result = Sizzle.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf( check ) === 0 : - operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.slice( -check.length ) === check : - operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : - false; - }; - }, - - "CHILD": function( type, what, argument, first, last ) { - var simple = type.slice( 0, 3 ) !== "nth", - forward = type.slice( -4 ) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function( elem ) { - return !!elem.parentNode; - } : - - function( elem, context, xml ) { - var cache, uniqueCache, outerCache, node, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType, - diff = false; - - if ( parent ) { - - // :(first|last|only)-(child|of-type) - if ( simple ) { - while ( dir ) { - node = elem; - while ( (node = node[ dir ]) ) { - if ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) { - - return false; - } - } - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } - - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if ( forward && useCache ) { - - // Seek `elem` from a previously-cached index - - // ...in a gzip-friendly way - node = parent; - outerCache = node[ expando ] || (node[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex && cache[ 2 ]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; - - while ( (node = ++nodeIndex && node && node[ dir ] || - - // Fallback to seeking `elem` from the start - (diff = nodeIndex = 0) || start.pop()) ) { - - // When found, cache indexes on `parent` and break - if ( node.nodeType === 1 && ++diff && node === elem ) { - uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } - - } else { - // Use previously-cached element index if available - if ( useCache ) { - // ...in a gzip-friendly way - node = elem; - outerCache = node[ expando ] || (node[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex; - } - - // xml :nth-child(...) - // or :nth-last-child(...) or :nth(-last)?-of-type(...) - if ( diff === false ) { - // Use the same loop as above to seek `elem` from the start - while ( (node = ++nodeIndex && node && node[ dir ] || - (diff = nodeIndex = 0) || start.pop()) ) { - - if ( ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) && - ++diff ) { - - // Cache the index of each encountered element - if ( useCache ) { - outerCache = node[ expando ] || (node[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); - - uniqueCache[ type ] = [ dirruns, diff ]; - } - - if ( node === elem ) { - break; - } - } - } - } - } - - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, - - "PSEUDO": function( pseudo, argument ) { - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction(function( seed, matches ) { - var idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf( seed, matched[i] ); - seed[ idx ] = !( matches[ idx ] = matched[i] ); - } - }) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - // Potentially complex pseudos - "not": markFunction(function( selector ) { - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile( selector.replace( rtrim, "$1" ) ); - - return matcher[ expando ] ? - markFunction(function( seed, matches, context, xml ) { - var elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( (elem = unmatched[i]) ) { - seed[i] = !(matches[i] = elem); - } - } - }) : - function( elem, context, xml ) { - input[0] = elem; - matcher( input, null, xml, results ); - // Don't keep the element (issue #299) - input[0] = null; - return !results.pop(); - }; - }), - - "has": markFunction(function( selector ) { - return function( elem ) { - return Sizzle( selector, elem ).length > 0; - }; - }), - - "contains": markFunction(function( text ) { - text = text.replace( runescape, funescape ); - return function( elem ) { - return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; - }; - }), - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction( function( lang ) { - // lang value must be a valid identifier - if ( !ridentifier.test(lang || "") ) { - Sizzle.error( "unsupported lang: " + lang ); - } - lang = lang.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - var elemLang; - do { - if ( (elemLang = documentIsHTML ? - elem.lang : - elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; - } - } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); - return false; - }; - }), - - // Miscellaneous - "target": function( elem ) { - var hash = window.location && window.location.hash; - return hash && hash.slice( 1 ) === elem.id; - }, - - "root": function( elem ) { - return elem === docElem; - }, - - "focus": function( elem ) { - return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); - }, - - // Boolean properties - "enabled": createDisabledPseudo( false ), - "disabled": createDisabledPseudo( true ), - - "checked": function( elem ) { - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); - }, - - "selected": function( elem ) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - // Contents - "empty": function( elem ) { - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), - // but not by others (comment: 8; processing instruction: 7; etc.) - // nodeType < 6 works because attributes (2) do not appear as children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeType < 6 ) { - return false; - } - } - return true; - }, - - "parent": function( elem ) { - return !Expr.pseudos["empty"]( elem ); - }, - - // Element/input types - "header": function( elem ) { - return rheader.test( elem.nodeName ); - }, - - "input": function( elem ) { - return rinputs.test( elem.nodeName ); - }, - - "button": function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === "button" || name === "button"; - }, - - "text": function( elem ) { - var attr; - return elem.nodeName.toLowerCase() === "input" && - elem.type === "text" && - - // Support: IE<8 - // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" - ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); - }, - - // Position-in-collection - "first": createPositionalPseudo(function() { - return [ 0 ]; - }), - - "last": createPositionalPseudo(function( matchIndexes, length ) { - return [ length - 1 ]; - }), - - "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { - return [ argument < 0 ? argument + length : argument ]; - }), - - "even": createPositionalPseudo(function( matchIndexes, length ) { - var i = 0; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "odd": createPositionalPseudo(function( matchIndexes, length ) { - var i = 1; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; --i >= 0; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; ++i < length; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }) - } -}; - -Expr.pseudos["nth"] = Expr.pseudos["eq"]; - -// Add button/input type pseudos -for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { - Expr.pseudos[ i ] = createInputPseudo( i ); -} -for ( i in { submit: true, reset: true } ) { - Expr.pseudos[ i ] = createButtonPseudo( i ); -} - -// Easy API for creating new setFilters -function setFilters() {} -setFilters.prototype = Expr.filters = Expr.pseudos; -Expr.setFilters = new setFilters(); - -tokenize = Sizzle.tokenize = function( selector, parseOnly ) { - var matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; - - if ( cached ) { - return parseOnly ? 0 : cached.slice( 0 ); - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || (match = rcomma.exec( soFar )) ) { - if ( match ) { - // Don't consume trailing commas as valid - soFar = soFar.slice( match[0].length ) || soFar; - } - groups.push( (tokens = []) ); - } - - matched = false; - - // Combinators - if ( (match = rcombinators.exec( soFar )) ) { - matched = match.shift(); - tokens.push({ - value: matched, - // Cast descendant combinators to space - type: match[0].replace( rtrim, " " ) - }); - soFar = soFar.slice( matched.length ); - } - - // Filters - for ( type in Expr.filter ) { - if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || - (match = preFilters[ type ]( match ))) ) { - matched = match.shift(); - tokens.push({ - value: matched, - type: type, - matches: match - }); - soFar = soFar.slice( matched.length ); - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - return parseOnly ? - soFar.length : - soFar ? - Sizzle.error( selector ) : - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -}; - -function toSelector( tokens ) { - var i = 0, - len = tokens.length, - selector = ""; - for ( ; i < len; i++ ) { - selector += tokens[i].value; - } - return selector; -} - -function addCombinator( matcher, combinator, base ) { - var dir = combinator.dir, - skip = combinator.next, - key = skip || dir, - checkNonElements = base && key === "parentNode", - doneName = done++; - - return combinator.first ? - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - return matcher( elem, context, xml ); - } - } - return false; - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - var oldCache, uniqueCache, outerCache, - newCache = [ dirruns, doneName ]; - - // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching - if ( xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - if ( matcher( elem, context, xml ) ) { - return true; - } - } - } - } else { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || (elem[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); - - if ( skip && skip === elem.nodeName.toLowerCase() ) { - elem = elem[ dir ] || elem; - } else if ( (oldCache = uniqueCache[ key ]) && - oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { - - // Assign to newCache so results back-propagate to previous elements - return (newCache[ 2 ] = oldCache[ 2 ]); - } else { - // Reuse newcache so results back-propagate to previous elements - uniqueCache[ key ] = newCache; - - // A match means we're done; a fail means we have to keep checking - if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { - return true; - } - } - } - } - } - return false; - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - var i = matchers.length; - while ( i-- ) { - if ( !matchers[i]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[0]; -} - -function multipleContexts( selector, contexts, results ) { - var i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - Sizzle( selector, contexts[i], results ); - } - return results; -} - -function condense( unmatched, map, filter, context, xml ) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( (elem = unmatched[i]) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction(function( seed, results, context, xml ) { - var temp, i, elem, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems, - - matcherOut = matcher ? - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results : - matcherIn; - - // Find primary matches - if ( matcher ) { - matcher( matcherIn, matcherOut, context, xml ); - } - - // Apply postFilter - if ( postFilter ) { - temp = condense( matcherOut, postMap ); - postFilter( temp, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while ( i-- ) { - if ( (elem = temp[i]) ) { - matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); - } - } - } - - if ( seed ) { - if ( postFinder || preFilter ) { - if ( postFinder ) { - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) ) { - // Restore matcherIn since elem is not yet a final match - temp.push( (matcherIn[i] = elem) ); - } - } - postFinder( null, (matcherOut = []), temp, xml ); - } - - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) && - (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { - - seed[temp] = !(results[temp] = elem); - } - } - } - - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - }); -} - -function matcherFromTokens( tokens ) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[0].type ], - implicitRelative = leadingRelative || Expr.relative[" "], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - (checkContext = context).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - // Avoid hanging onto element (issue #299) - checkContext = null; - return ret; - } ]; - - for ( ; i < len; i++ ) { - if ( (matcher = Expr.relative[ tokens[i].type ]) ) { - matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; - } else { - matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[j].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && toSelector( - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) - ).replace( rtrim, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), - j < len && toSelector( tokens ) - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - var bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, outermost ) { - var elem, j, matcher, - matchedCount = 0, - i = "0", - unmatched = seed && [], - setMatched = [], - contextBackup = outermostContext, - // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), - len = elems.length; - - if ( outermost ) { - outermostContext = context === document || context || outermost; - } - - // Add elements passing elementMatchers directly to results - // Support: IE<9, Safari - // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id - for ( ; i !== len && (elem = elems[i]) != null; i++ ) { - if ( byElement && elem ) { - j = 0; - if ( !context && elem.ownerDocument !== document ) { - setDocument( elem ); - xml = !documentIsHTML; - } - while ( (matcher = elementMatchers[j++]) ) { - if ( matcher( elem, context || document, xml) ) { - results.push( elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - // They will have gone through all possible matchers - if ( (elem = !matcher && elem) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // `i` is now the count of elements visited above, and adding it to `matchedCount` - // makes the latter nonnegative. - matchedCount += i; - - // Apply set filters to unmatched elements - // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` - // equals `i`), unless we didn't visit _any_ elements in the above loop because we have - // no element matchers and no seed. - // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that - // case, which will result in a "00" `matchedCount` that differs from `i` but is also - // numerically zero. - if ( bySet && i !== matchedCount ) { - j = 0; - while ( (matcher = setMatchers[j++]) ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !(unmatched[i] || setMatched[i]) ) { - setMatched[i] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - Sizzle.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if ( !cached ) { - // Generate a function of recursive functions that can be used to check each element - if ( !match ) { - match = tokenize( selector ); - } - i = match.length; - while ( i-- ) { - cached = matcherFromTokens( match[i] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); - - // Save selector and tokenization - cached.selector = selector; - } - return cached; -}; - -/** - * A low-level selection function that works with Sizzle's compiled - * selector functions - * @param {String|Function} selector A selector or a pre-compiled - * selector function built with Sizzle.compile - * @param {Element} context - * @param {Array} [results] - * @param {Array} [seed] A set of elements to match against - */ -select = Sizzle.select = function( selector, context, results, seed ) { - var i, tokens, token, type, find, - compiled = typeof selector === "function" && selector, - match = !seed && tokenize( (selector = compiled.selector || selector) ); - - results = results || []; - - // Try to minimize operations if there is only one selector in the list and no seed - // (the latter of which guarantees us context) - if ( match.length === 1 ) { - - // Reduce context if the leading compound selector is an ID - tokens = match[0] = match[0].slice( 0 ); - if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && - context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) { - - context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; - if ( !context ) { - return results; - - // Precompiled matchers will still verify ancestry, so step up a level - } else if ( compiled ) { - context = context.parentNode; - } - - selector = selector.slice( tokens.shift().value.length ); - } - - // Fetch a seed set for right-to-left matching - i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; - while ( i-- ) { - token = tokens[i]; - - // Abort if we hit a combinator - if ( Expr.relative[ (type = token.type) ] ) { - break; - } - if ( (find = Expr.find[ type ]) ) { - // Search, expanding context for leading sibling combinators - if ( (seed = find( - token.matches[0].replace( runescape, funescape ), - rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context - )) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && toSelector( tokens ); - if ( !selector ) { - push.apply( results, seed ); - return results; - } - - break; - } - } - } - } - - // Compile and execute a filtering function if one is not provided - // Provide `match` to avoid retokenization if we modified the selector above - ( compiled || compile( selector, match ) )( - seed, - context, - !documentIsHTML, - results, - !context || rsibling.test( selector ) && testContext( context.parentNode ) || context - ); - return results; -}; - -// One-time assignments - -// Sort stability -support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; - -// Support: Chrome 14-35+ -// Always assume duplicates if they aren't passed to the comparison function -support.detectDuplicates = !!hasDuplicate; - -// Initialize against the default document -setDocument(); - -// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) -// Detached nodes confoundingly follow *each other* -support.sortDetached = assert(function( el ) { - // Should return 1, but returns 4 (following) - return el.compareDocumentPosition( document.createElement("fieldset") ) & 1; -}); - -// Support: IE<8 -// Prevent attribute/property "interpolation" -// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !assert(function( el ) { - el.innerHTML = ""; - return el.firstChild.getAttribute("href") === "#" ; -}) ) { - addHandle( "type|href|height|width", function( elem, name, isXML ) { - if ( !isXML ) { - return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); - } - }); -} - -// Support: IE<9 -// Use defaultValue in place of getAttribute("value") -if ( !support.attributes || !assert(function( el ) { - el.innerHTML = ""; - el.firstChild.setAttribute( "value", "" ); - return el.firstChild.getAttribute( "value" ) === ""; -}) ) { - addHandle( "value", function( elem, name, isXML ) { - if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { - return elem.defaultValue; - } - }); -} - -// Support: IE<9 -// Use getAttributeNode to fetch booleans when getAttribute lies -if ( !assert(function( el ) { - return el.getAttribute("disabled") == null; -}) ) { - addHandle( booleans, function( elem, name, isXML ) { - var val; - if ( !isXML ) { - return elem[ name ] === true ? name.toLowerCase() : - (val = elem.getAttributeNode( name )) && val.specified ? - val.value : - null; - } - }); -} - -return Sizzle; - -})( window ); - - - -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; - -// Deprecated -jQuery.expr[ ":" ] = jQuery.expr.pseudos; -jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; -jQuery.escapeSelector = Sizzle.escape; - - - - -var dir = function( elem, dir, until ) { - var matched = [], - truncate = until !== undefined; - - while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { - if ( elem.nodeType === 1 ) { - if ( truncate && jQuery( elem ).is( until ) ) { - break; - } - matched.push( elem ); - } - } - return matched; -}; - - -var siblings = function( n, elem ) { - var matched = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - matched.push( n ); - } - } - - return matched; -}; - - -var rneedsContext = jQuery.expr.match.needsContext; - - - -function nodeName( elem, name ) { - - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - -}; -var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); - - - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, not ) { - if ( isFunction( qualifier ) ) { - return jQuery.grep( elements, function( elem, i ) { - return !!qualifier.call( elem, i, elem ) !== not; - } ); - } - - // Single element - if ( qualifier.nodeType ) { - return jQuery.grep( elements, function( elem ) { - return ( elem === qualifier ) !== not; - } ); - } - - // Arraylike of elements (jQuery, arguments, Array) - if ( typeof qualifier !== "string" ) { - return jQuery.grep( elements, function( elem ) { - return ( indexOf.call( qualifier, elem ) > -1 ) !== not; - } ); - } - - // Filtered directly for both simple and complex selectors - return jQuery.filter( qualifier, elements, not ); -} - -jQuery.filter = function( expr, elems, not ) { - var elem = elems[ 0 ]; - - if ( not ) { - expr = ":not(" + expr + ")"; - } - - if ( elems.length === 1 && elem.nodeType === 1 ) { - return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; - } - - return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { - return elem.nodeType === 1; - } ) ); -}; - -jQuery.fn.extend( { - find: function( selector ) { - var i, ret, - len = this.length, - self = this; - - if ( typeof selector !== "string" ) { - return this.pushStack( jQuery( selector ).filter( function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - } ) ); - } - - ret = this.pushStack( [] ); - - for ( i = 0; i < len; i++ ) { - jQuery.find( selector, self[ i ], ret ); - } - - return len > 1 ? jQuery.uniqueSort( ret ) : ret; - }, - filter: function( selector ) { - return this.pushStack( winnow( this, selector || [], false ) ); - }, - not: function( selector ) { - return this.pushStack( winnow( this, selector || [], true ) ); - }, - is: function( selector ) { - return !!winnow( - this, - - // If this is a positional/relative selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - typeof selector === "string" && rneedsContext.test( selector ) ? - jQuery( selector ) : - selector || [], - false - ).length; - } -} ); - - -// Initialize a jQuery object - - -// A central reference to the root jQuery(document) -var rootjQuery, - - // A simple way to check for HTML strings - // Prioritize #id over to avoid XSS via location.hash (#9521) - // Strict HTML recognition (#11290: must start with <) - // Shortcut simple #id case for speed - rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, - - init = jQuery.fn.init = function( selector, context, root ) { - var match, elem; - - // HANDLE: $(""), $(null), $(undefined), $(false) - if ( !selector ) { - return this; - } - - // Method init() accepts an alternate rootjQuery - // so migrate can support jQuery.sub (gh-2101) - root = root || rootjQuery; - - // Handle HTML strings - if ( typeof selector === "string" ) { - if ( selector[ 0 ] === "<" && - selector[ selector.length - 1 ] === ">" && - selector.length >= 3 ) { - - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = rquickExpr.exec( selector ); - } - - // Match html or make sure no context is specified for #id - if ( match && ( match[ 1 ] || !context ) ) { - - // HANDLE: $(html) -> $(array) - if ( match[ 1 ] ) { - context = context instanceof jQuery ? context[ 0 ] : context; - - // Option to run scripts is true for back-compat - // Intentionally let the error be thrown if parseHTML is not present - jQuery.merge( this, jQuery.parseHTML( - match[ 1 ], - context && context.nodeType ? context.ownerDocument || context : document, - true - ) ); - - // HANDLE: $(html, props) - if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { - for ( match in context ) { - - // Properties of context are called as methods if possible - if ( isFunction( this[ match ] ) ) { - this[ match ]( context[ match ] ); - - // ...and otherwise set as attributes - } else { - this.attr( match, context[ match ] ); - } - } - } - - return this; - - // HANDLE: $(#id) - } else { - elem = document.getElementById( match[ 2 ] ); - - if ( elem ) { - - // Inject the element directly into the jQuery object - this[ 0 ] = elem; - this.length = 1; - } - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || root ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(DOMElement) - } else if ( selector.nodeType ) { - this[ 0 ] = selector; - this.length = 1; - return this; - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( isFunction( selector ) ) { - return root.ready !== undefined ? - root.ready( selector ) : - - // Execute immediately if ready is not present - selector( jQuery ); - } - - return jQuery.makeArray( selector, this ); - }; - -// Give the init function the jQuery prototype for later instantiation -init.prototype = jQuery.fn; - -// Initialize central reference -rootjQuery = jQuery( document ); - - -var rparentsprev = /^(?:parents|prev(?:Until|All))/, - - // Methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.fn.extend( { - has: function( target ) { - var targets = jQuery( target, this ), - l = targets.length; - - return this.filter( function() { - var i = 0; - for ( ; i < l; i++ ) { - if ( jQuery.contains( this, targets[ i ] ) ) { - return true; - } - } - } ); - }, - - closest: function( selectors, context ) { - var cur, - i = 0, - l = this.length, - matched = [], - targets = typeof selectors !== "string" && jQuery( selectors ); - - // Positional selectors never match, since there's no _selection_ context - if ( !rneedsContext.test( selectors ) ) { - for ( ; i < l; i++ ) { - for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { - - // Always skip document fragments - if ( cur.nodeType < 11 && ( targets ? - targets.index( cur ) > -1 : - - // Don't pass non-elements to Sizzle - cur.nodeType === 1 && - jQuery.find.matchesSelector( cur, selectors ) ) ) { - - matched.push( cur ); - break; - } - } - } - } - - return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); - }, - - // Determine the position of an element within the set - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; - } - - // Index in selector - if ( typeof elem === "string" ) { - return indexOf.call( jQuery( elem ), this[ 0 ] ); - } - - // Locate the position of the desired element - return indexOf.call( this, - - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[ 0 ] : elem - ); - }, - - add: function( selector, context ) { - return this.pushStack( - jQuery.uniqueSort( - jQuery.merge( this.get(), jQuery( selector, context ) ) - ) - ); - }, - - addBack: function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter( selector ) - ); - } -} ); - -function sibling( cur, dir ) { - while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} - return cur; -} - -jQuery.each( { - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, i, until ) { - return dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return sibling( elem, "nextSibling" ); - }, - prev: function( elem ) { - return sibling( elem, "previousSibling" ); - }, - nextAll: function( elem ) { - return dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, i, until ) { - return dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, i, until ) { - return dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return siblings( ( elem.parentNode || {} ).firstChild, elem ); - }, - children: function( elem ) { - return siblings( elem.firstChild ); - }, - contents: function( elem ) { - if ( nodeName( elem, "iframe" ) ) { - return elem.contentDocument; - } - - // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only - // Treat the template element as a regular one in browsers that - // don't support it. - if ( nodeName( elem, "template" ) ) { - elem = elem.content || elem; - } - - return jQuery.merge( [], elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var matched = jQuery.map( this, fn, until ); - - if ( name.slice( -5 ) !== "Until" ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - matched = jQuery.filter( selector, matched ); - } - - if ( this.length > 1 ) { - - // Remove duplicates - if ( !guaranteedUnique[ name ] ) { - jQuery.uniqueSort( matched ); - } - - // Reverse order for parents* and prev-derivatives - if ( rparentsprev.test( name ) ) { - matched.reverse(); - } - } - - return this.pushStack( matched ); - }; -} ); -var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); - - - -// Convert String-formatted options into Object-formatted ones -function createOptions( options ) { - var object = {}; - jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { - object[ flag ] = true; - } ); - return object; -} - -/* - * Create a callback list using the following parameters: - * - * options: an optional list of space-separated options that will change how - * the callback list behaves or a more traditional option object - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible options: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( options ) { - - // Convert options from String-formatted to Object-formatted if needed - // (we check in cache first) - options = typeof options === "string" ? - createOptions( options ) : - jQuery.extend( {}, options ); - - var // Flag to know if list is currently firing - firing, - - // Last fire value for non-forgettable lists - memory, - - // Flag to know if list was already fired - fired, - - // Flag to prevent firing - locked, - - // Actual callback list - list = [], - - // Queue of execution data for repeatable lists - queue = [], - - // Index of currently firing callback (modified by add/remove as needed) - firingIndex = -1, - - // Fire callbacks - fire = function() { - - // Enforce single-firing - locked = locked || options.once; - - // Execute callbacks for all pending executions, - // respecting firingIndex overrides and runtime changes - fired = firing = true; - for ( ; queue.length; firingIndex = -1 ) { - memory = queue.shift(); - while ( ++firingIndex < list.length ) { - - // Run callback and check for early termination - if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && - options.stopOnFalse ) { - - // Jump to end and forget the data so .add doesn't re-fire - firingIndex = list.length; - memory = false; - } - } - } - - // Forget the data if we're done with it - if ( !options.memory ) { - memory = false; - } - - firing = false; - - // Clean up if we're done firing for good - if ( locked ) { - - // Keep an empty list if we have data for future add calls - if ( memory ) { - list = []; - - // Otherwise, this object is spent - } else { - list = ""; - } - } - }, - - // Actual Callbacks object - self = { - - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - - // If we have memory from a past run, we should fire after adding - if ( memory && !firing ) { - firingIndex = list.length - 1; - queue.push( memory ); - } - - ( function add( args ) { - jQuery.each( args, function( _, arg ) { - if ( isFunction( arg ) ) { - if ( !options.unique || !self.has( arg ) ) { - list.push( arg ); - } - } else if ( arg && arg.length && toType( arg ) !== "string" ) { - - // Inspect recursively - add( arg ); - } - } ); - } )( arguments ); - - if ( memory && !firing ) { - fire(); - } - } - return this; - }, - - // Remove a callback from the list - remove: function() { - jQuery.each( arguments, function( _, arg ) { - var index; - while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { - list.splice( index, 1 ); - - // Handle firing indexes - if ( index <= firingIndex ) { - firingIndex--; - } - } - } ); - return this; - }, - - // Check if a given callback is in the list. - // If no argument is given, return whether or not list has callbacks attached. - has: function( fn ) { - return fn ? - jQuery.inArray( fn, list ) > -1 : - list.length > 0; - }, - - // Remove all callbacks from the list - empty: function() { - if ( list ) { - list = []; - } - return this; - }, - - // Disable .fire and .add - // Abort any current/pending executions - // Clear all callbacks and values - disable: function() { - locked = queue = []; - list = memory = ""; - return this; - }, - disabled: function() { - return !list; - }, - - // Disable .fire - // Also disable .add unless we have memory (since it would have no effect) - // Abort any pending executions - lock: function() { - locked = queue = []; - if ( !memory && !firing ) { - list = memory = ""; - } - return this; - }, - locked: function() { - return !!locked; - }, - - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - if ( !locked ) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; - queue.push( args ); - if ( !firing ) { - fire(); - } - } - return this; - }, - - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - - // To know if the callbacks have already been called at least once - fired: function() { - return !!fired; - } - }; - - return self; -}; - - -function Identity( v ) { - return v; -} -function Thrower( ex ) { - throw ex; -} - -function adoptValue( value, resolve, reject, noValue ) { - var method; - - try { - - // Check for promise aspect first to privilege synchronous behavior - if ( value && isFunction( ( method = value.promise ) ) ) { - method.call( value ).done( resolve ).fail( reject ); - - // Other thenables - } else if ( value && isFunction( ( method = value.then ) ) ) { - method.call( value, resolve, reject ); - - // Other non-thenables - } else { - - // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: - // * false: [ value ].slice( 0 ) => resolve( value ) - // * true: [ value ].slice( 1 ) => resolve() - resolve.apply( undefined, [ value ].slice( noValue ) ); - } - - // For Promises/A+, convert exceptions into rejections - // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in - // Deferred#then to conditionally suppress rejection. - } catch ( value ) { - - // Support: Android 4.0 only - // Strict mode functions invoked without .call/.apply get global-object context - reject.apply( undefined, [ value ] ); - } -} - -jQuery.extend( { - - Deferred: function( func ) { - var tuples = [ - - // action, add listener, callbacks, - // ... .then handlers, argument index, [final state] - [ "notify", "progress", jQuery.Callbacks( "memory" ), - jQuery.Callbacks( "memory" ), 2 ], - [ "resolve", "done", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 0, "resolved" ], - [ "reject", "fail", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 1, "rejected" ] - ], - state = "pending", - promise = { - state: function() { - return state; - }, - always: function() { - deferred.done( arguments ).fail( arguments ); - return this; - }, - "catch": function( fn ) { - return promise.then( null, fn ); - }, - - // Keep pipe for back-compat - pipe: function( /* fnDone, fnFail, fnProgress */ ) { - var fns = arguments; - - return jQuery.Deferred( function( newDefer ) { - jQuery.each( tuples, function( i, tuple ) { - - // Map tuples (progress, done, fail) to arguments (done, fail, progress) - var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; - - // deferred.progress(function() { bind to newDefer or newDefer.notify }) - // deferred.done(function() { bind to newDefer or newDefer.resolve }) - // deferred.fail(function() { bind to newDefer or newDefer.reject }) - deferred[ tuple[ 1 ] ]( function() { - var returned = fn && fn.apply( this, arguments ); - if ( returned && isFunction( returned.promise ) ) { - returned.promise() - .progress( newDefer.notify ) - .done( newDefer.resolve ) - .fail( newDefer.reject ); - } else { - newDefer[ tuple[ 0 ] + "With" ]( - this, - fn ? [ returned ] : arguments - ); - } - } ); - } ); - fns = null; - } ).promise(); - }, - then: function( onFulfilled, onRejected, onProgress ) { - var maxDepth = 0; - function resolve( depth, deferred, handler, special ) { - return function() { - var that = this, - args = arguments, - mightThrow = function() { - var returned, then; - - // Support: Promises/A+ section 2.3.3.3.3 - // https://promisesaplus.com/#point-59 - // Ignore double-resolution attempts - if ( depth < maxDepth ) { - return; - } - - returned = handler.apply( that, args ); - - // Support: Promises/A+ section 2.3.1 - // https://promisesaplus.com/#point-48 - if ( returned === deferred.promise() ) { - throw new TypeError( "Thenable self-resolution" ); - } - - // Support: Promises/A+ sections 2.3.3.1, 3.5 - // https://promisesaplus.com/#point-54 - // https://promisesaplus.com/#point-75 - // Retrieve `then` only once - then = returned && - - // Support: Promises/A+ section 2.3.4 - // https://promisesaplus.com/#point-64 - // Only check objects and functions for thenability - ( typeof returned === "object" || - typeof returned === "function" ) && - returned.then; - - // Handle a returned thenable - if ( isFunction( then ) ) { - - // Special processors (notify) just wait for resolution - if ( special ) { - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ) - ); - - // Normal processors (resolve) also hook into progress - } else { - - // ...and disregard older resolution values - maxDepth++; - - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ), - resolve( maxDepth, deferred, Identity, - deferred.notifyWith ) - ); - } - - // Handle all other returned values - } else { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Identity ) { - that = undefined; - args = [ returned ]; - } - - // Process the value(s) - // Default process is resolve - ( special || deferred.resolveWith )( that, args ); - } - }, - - // Only normal processors (resolve) catch and reject exceptions - process = special ? - mightThrow : - function() { - try { - mightThrow(); - } catch ( e ) { - - if ( jQuery.Deferred.exceptionHook ) { - jQuery.Deferred.exceptionHook( e, - process.stackTrace ); - } - - // Support: Promises/A+ section 2.3.3.3.4.1 - // https://promisesaplus.com/#point-61 - // Ignore post-resolution exceptions - if ( depth + 1 >= maxDepth ) { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Thrower ) { - that = undefined; - args = [ e ]; - } - - deferred.rejectWith( that, args ); - } - } - }; - - // Support: Promises/A+ section 2.3.3.3.1 - // https://promisesaplus.com/#point-57 - // Re-resolve promises immediately to dodge false rejection from - // subsequent errors - if ( depth ) { - process(); - } else { - - // Call an optional hook to record the stack, in case of exception - // since it's otherwise lost when execution goes async - if ( jQuery.Deferred.getStackHook ) { - process.stackTrace = jQuery.Deferred.getStackHook(); - } - window.setTimeout( process ); - } - }; - } - - return jQuery.Deferred( function( newDefer ) { - - // progress_handlers.add( ... ) - tuples[ 0 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onProgress ) ? - onProgress : - Identity, - newDefer.notifyWith - ) - ); - - // fulfilled_handlers.add( ... ) - tuples[ 1 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onFulfilled ) ? - onFulfilled : - Identity - ) - ); - - // rejected_handlers.add( ... ) - tuples[ 2 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onRejected ) ? - onRejected : - Thrower - ) - ); - } ).promise(); - }, - - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - return obj != null ? jQuery.extend( obj, promise ) : promise; - } - }, - deferred = {}; - - // Add list-specific methods - jQuery.each( tuples, function( i, tuple ) { - var list = tuple[ 2 ], - stateString = tuple[ 5 ]; - - // promise.progress = list.add - // promise.done = list.add - // promise.fail = list.add - promise[ tuple[ 1 ] ] = list.add; - - // Handle state - if ( stateString ) { - list.add( - function() { - - // state = "resolved" (i.e., fulfilled) - // state = "rejected" - state = stateString; - }, - - // rejected_callbacks.disable - // fulfilled_callbacks.disable - tuples[ 3 - i ][ 2 ].disable, - - // rejected_handlers.disable - // fulfilled_handlers.disable - tuples[ 3 - i ][ 3 ].disable, - - // progress_callbacks.lock - tuples[ 0 ][ 2 ].lock, - - // progress_handlers.lock - tuples[ 0 ][ 3 ].lock - ); - } - - // progress_handlers.fire - // fulfilled_handlers.fire - // rejected_handlers.fire - list.add( tuple[ 3 ].fire ); - - // deferred.notify = function() { deferred.notifyWith(...) } - // deferred.resolve = function() { deferred.resolveWith(...) } - // deferred.reject = function() { deferred.rejectWith(...) } - deferred[ tuple[ 0 ] ] = function() { - deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); - return this; - }; - - // deferred.notifyWith = list.fireWith - // deferred.resolveWith = list.fireWith - // deferred.rejectWith = list.fireWith - deferred[ tuple[ 0 ] + "With" ] = list.fireWith; - } ); - - // Make the deferred a promise - promise.promise( deferred ); - - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - - // All done! - return deferred; - }, - - // Deferred helper - when: function( singleValue ) { - var - - // count of uncompleted subordinates - remaining = arguments.length, - - // count of unprocessed arguments - i = remaining, - - // subordinate fulfillment data - resolveContexts = Array( i ), - resolveValues = slice.call( arguments ), - - // the master Deferred - master = jQuery.Deferred(), - - // subordinate callback factory - updateFunc = function( i ) { - return function( value ) { - resolveContexts[ i ] = this; - resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; - if ( !( --remaining ) ) { - master.resolveWith( resolveContexts, resolveValues ); - } - }; - }; - - // Single- and empty arguments are adopted like Promise.resolve - if ( remaining <= 1 ) { - adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, - !remaining ); - - // Use .then() to unwrap secondary thenables (cf. gh-3000) - if ( master.state() === "pending" || - isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { - - return master.then(); - } - } - - // Multiple arguments are aggregated like Promise.all array elements - while ( i-- ) { - adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); - } - - return master.promise(); - } -} ); - - -// These usually indicate a programmer mistake during development, -// warn about them ASAP rather than swallowing them by default. -var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; - -jQuery.Deferred.exceptionHook = function( error, stack ) { - - // Support: IE 8 - 9 only - // Console exists when dev tools are open, which can happen at any time - if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { - window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); - } -}; - - - - -jQuery.readyException = function( error ) { - window.setTimeout( function() { - throw error; - } ); -}; - - - - -// The deferred used on DOM ready -var readyList = jQuery.Deferred(); - -jQuery.fn.ready = function( fn ) { - - readyList - .then( fn ) - - // Wrap jQuery.readyException in a function so that the lookup - // happens at the time of error handling instead of callback - // registration. - .catch( function( error ) { - jQuery.readyException( error ); - } ); - - return this; -}; - -jQuery.extend( { - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Handle when the DOM is ready - ready: function( wait ) { - - // Abort if there are pending holds or we're already ready - if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { - return; - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - } -} ); - -jQuery.ready.then = readyList.then; - -// The ready event handler and self cleanup method -function completed() { - document.removeEventListener( "DOMContentLoaded", completed ); - window.removeEventListener( "load", completed ); - jQuery.ready(); -} - -// Catch cases where $(document).ready() is called -// after the browser event has already occurred. -// Support: IE <=9 - 10 only -// Older IE sometimes signals "interactive" too soon -if ( document.readyState === "complete" || - ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { - - // Handle it asynchronously to allow scripts the opportunity to delay ready - window.setTimeout( jQuery.ready ); - -} else { - - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", completed ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", completed ); -} - - - - -// Multifunctional method to get and set values of a collection -// The value/s can optionally be executed if it's a function -var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { - var i = 0, - len = elems.length, - bulk = key == null; - - // Sets many values - if ( toType( key ) === "object" ) { - chainable = true; - for ( i in key ) { - access( elems, fn, i, key[ i ], true, emptyGet, raw ); - } - - // Sets one value - } else if ( value !== undefined ) { - chainable = true; - - if ( !isFunction( value ) ) { - raw = true; - } - - if ( bulk ) { - - // Bulk operations run against the entire set - if ( raw ) { - fn.call( elems, value ); - fn = null; - - // ...except when executing function values - } else { - bulk = fn; - fn = function( elem, key, value ) { - return bulk.call( jQuery( elem ), value ); - }; - } - } - - if ( fn ) { - for ( ; i < len; i++ ) { - fn( - elems[ i ], key, raw ? - value : - value.call( elems[ i ], i, fn( elems[ i ], key ) ) - ); - } - } - } - - if ( chainable ) { - return elems; - } - - // Gets - if ( bulk ) { - return fn.call( elems ); - } - - return len ? fn( elems[ 0 ], key ) : emptyGet; -}; - - -// Matches dashed string for camelizing -var rmsPrefix = /^-ms-/, - rdashAlpha = /-([a-z])/g; - -// Used by camelCase as callback to replace() -function fcamelCase( all, letter ) { - return letter.toUpperCase(); -} - -// Convert dashed to camelCase; used by the css and data modules -// Support: IE <=9 - 11, Edge 12 - 15 -// Microsoft forgot to hump their vendor prefix (#9572) -function camelCase( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); -} -var acceptData = function( owner ) { - - // Accepts only: - // - Node - // - Node.ELEMENT_NODE - // - Node.DOCUMENT_NODE - // - Object - // - Any - return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); -}; - - - - -function Data() { - this.expando = jQuery.expando + Data.uid++; -} - -Data.uid = 1; - -Data.prototype = { - - cache: function( owner ) { - - // Check if the owner object already has a cache - var value = owner[ this.expando ]; - - // If not, create one - if ( !value ) { - value = {}; - - // We can accept data for non-element nodes in modern browsers, - // but we should not, see #8335. - // Always return an empty object. - if ( acceptData( owner ) ) { - - // If it is a node unlikely to be stringify-ed or looped over - // use plain assignment - if ( owner.nodeType ) { - owner[ this.expando ] = value; - - // Otherwise secure it in a non-enumerable property - // configurable must be true to allow the property to be - // deleted when data is removed - } else { - Object.defineProperty( owner, this.expando, { - value: value, - configurable: true - } ); - } - } - } - - return value; - }, - set: function( owner, data, value ) { - var prop, - cache = this.cache( owner ); - - // Handle: [ owner, key, value ] args - // Always use camelCase key (gh-2257) - if ( typeof data === "string" ) { - cache[ camelCase( data ) ] = value; - - // Handle: [ owner, { properties } ] args - } else { - - // Copy the properties one-by-one to the cache object - for ( prop in data ) { - cache[ camelCase( prop ) ] = data[ prop ]; - } - } - return cache; - }, - get: function( owner, key ) { - return key === undefined ? - this.cache( owner ) : - - // Always use camelCase key (gh-2257) - owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; - }, - access: function( owner, key, value ) { - - // In cases where either: - // - // 1. No key was specified - // 2. A string key was specified, but no value provided - // - // Take the "read" path and allow the get method to determine - // which value to return, respectively either: - // - // 1. The entire cache object - // 2. The data stored at the key - // - if ( key === undefined || - ( ( key && typeof key === "string" ) && value === undefined ) ) { - - return this.get( owner, key ); - } - - // When the key is not a string, or both a key and value - // are specified, set or extend (existing objects) with either: - // - // 1. An object of properties - // 2. A key and value - // - this.set( owner, key, value ); - - // Since the "set" path can have two possible entry points - // return the expected data based on which path was taken[*] - return value !== undefined ? value : key; - }, - remove: function( owner, key ) { - var i, - cache = owner[ this.expando ]; - - if ( cache === undefined ) { - return; - } - - if ( key !== undefined ) { - - // Support array or space separated string of keys - if ( Array.isArray( key ) ) { - - // If key is an array of keys... - // We always set camelCase keys, so remove that. - key = key.map( camelCase ); - } else { - key = camelCase( key ); - - // If a key with the spaces exists, use it. - // Otherwise, create an array by matching non-whitespace - key = key in cache ? - [ key ] : - ( key.match( rnothtmlwhite ) || [] ); - } - - i = key.length; - - while ( i-- ) { - delete cache[ key[ i ] ]; - } - } - - // Remove the expando if there's no more data - if ( key === undefined || jQuery.isEmptyObject( cache ) ) { - - // Support: Chrome <=35 - 45 - // Webkit & Blink performance suffers when deleting properties - // from DOM nodes, so set to undefined instead - // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) - if ( owner.nodeType ) { - owner[ this.expando ] = undefined; - } else { - delete owner[ this.expando ]; - } - } - }, - hasData: function( owner ) { - var cache = owner[ this.expando ]; - return cache !== undefined && !jQuery.isEmptyObject( cache ); - } -}; -var dataPriv = new Data(); - -var dataUser = new Data(); - - - -// Implementation Summary -// -// 1. Enforce API surface and semantic compatibility with 1.9.x branch -// 2. Improve the module's maintainability by reducing the storage -// paths to a single mechanism. -// 3. Use the same single mechanism to support "private" and "user" data. -// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) -// 5. Avoid exposing implementation details on user objects (eg. expando properties) -// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 - -var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, - rmultiDash = /[A-Z]/g; - -function getData( data ) { - if ( data === "true" ) { - return true; - } - - if ( data === "false" ) { - return false; - } - - if ( data === "null" ) { - return null; - } - - // Only convert to a number if it doesn't change the string - if ( data === +data + "" ) { - return +data; - } - - if ( rbrace.test( data ) ) { - return JSON.parse( data ); - } - - return data; -} - -function dataAttr( elem, key, data ) { - var name; - - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); - data = elem.getAttribute( name ); - - if ( typeof data === "string" ) { - try { - data = getData( data ); - } catch ( e ) {} - - // Make sure we set the data so it isn't changed later - dataUser.set( elem, key, data ); - } else { - data = undefined; - } - } - return data; -} - -jQuery.extend( { - hasData: function( elem ) { - return dataUser.hasData( elem ) || dataPriv.hasData( elem ); - }, - - data: function( elem, name, data ) { - return dataUser.access( elem, name, data ); - }, - - removeData: function( elem, name ) { - dataUser.remove( elem, name ); - }, - - // TODO: Now that all calls to _data and _removeData have been replaced - // with direct calls to dataPriv methods, these can be deprecated. - _data: function( elem, name, data ) { - return dataPriv.access( elem, name, data ); - }, - - _removeData: function( elem, name ) { - dataPriv.remove( elem, name ); - } -} ); - -jQuery.fn.extend( { - data: function( key, value ) { - var i, name, data, - elem = this[ 0 ], - attrs = elem && elem.attributes; - - // Gets all values - if ( key === undefined ) { - if ( this.length ) { - data = dataUser.get( elem ); - - if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { - i = attrs.length; - while ( i-- ) { - - // Support: IE 11 only - // The attrs elements can be null (#14894) - if ( attrs[ i ] ) { - name = attrs[ i ].name; - if ( name.indexOf( "data-" ) === 0 ) { - name = camelCase( name.slice( 5 ) ); - dataAttr( elem, name, data[ name ] ); - } - } - } - dataPriv.set( elem, "hasDataAttrs", true ); - } - } - - return data; - } - - // Sets multiple values - if ( typeof key === "object" ) { - return this.each( function() { - dataUser.set( this, key ); - } ); - } - - return access( this, function( value ) { - var data; - - // The calling jQuery object (element matches) is not empty - // (and therefore has an element appears at this[ 0 ]) and the - // `value` parameter was not undefined. An empty jQuery object - // will result in `undefined` for elem = this[ 0 ] which will - // throw an exception if an attempt to read a data cache is made. - if ( elem && value === undefined ) { - - // Attempt to get data from the cache - // The key will always be camelCased in Data - data = dataUser.get( elem, key ); - if ( data !== undefined ) { - return data; - } - - // Attempt to "discover" the data in - // HTML5 custom data-* attrs - data = dataAttr( elem, key ); - if ( data !== undefined ) { - return data; - } - - // We tried really hard, but the data doesn't exist. - return; - } - - // Set the data... - this.each( function() { - - // We always store the camelCased key - dataUser.set( this, key, value ); - } ); - }, null, value, arguments.length > 1, null, true ); - }, - - removeData: function( key ) { - return this.each( function() { - dataUser.remove( this, key ); - } ); - } -} ); - - -jQuery.extend( { - queue: function( elem, type, data ) { - var queue; - - if ( elem ) { - type = ( type || "fx" ) + "queue"; - queue = dataPriv.get( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( data ) { - if ( !queue || Array.isArray( data ) ) { - queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); - } else { - queue.push( data ); - } - } - return queue || []; - } - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - startLength = queue.length, - fn = queue.shift(), - hooks = jQuery._queueHooks( elem, type ), - next = function() { - jQuery.dequeue( elem, type ); - }; - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - startLength--; - } - - if ( fn ) { - - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift( "inprogress" ); - } - - // Clear up the last queue stop function - delete hooks.stop; - fn.call( elem, next, hooks ); - } - - if ( !startLength && hooks ) { - hooks.empty.fire(); - } - }, - - // Not public - generate a queueHooks object, or return the current one - _queueHooks: function( elem, type ) { - var key = type + "queueHooks"; - return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { - empty: jQuery.Callbacks( "once memory" ).add( function() { - dataPriv.remove( elem, [ type + "queue", key ] ); - } ) - } ); - } -} ); - -jQuery.fn.extend( { - queue: function( type, data ) { - var setter = 2; - - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - setter--; - } - - if ( arguments.length < setter ) { - return jQuery.queue( this[ 0 ], type ); - } - - return data === undefined ? - this : - this.each( function() { - var queue = jQuery.queue( this, type, data ); - - // Ensure a hooks for this queue - jQuery._queueHooks( this, type ); - - if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - } ); - }, - dequeue: function( type ) { - return this.each( function() { - jQuery.dequeue( this, type ); - } ); - }, - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - }, - - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function( type, obj ) { - var tmp, - count = 1, - defer = jQuery.Deferred(), - elements = this, - i = this.length, - resolve = function() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); - } - }; - - if ( typeof type !== "string" ) { - obj = type; - type = undefined; - } - type = type || "fx"; - - while ( i-- ) { - tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); - if ( tmp && tmp.empty ) { - count++; - tmp.empty.add( resolve ); - } - } - resolve(); - return defer.promise( obj ); - } -} ); -var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; - -var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); - - -var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; - -var isHiddenWithinTree = function( elem, el ) { - - // isHiddenWithinTree might be called from jQuery#filter function; - // in that case, element will be second argument - elem = el || elem; - - // Inline style trumps all - return elem.style.display === "none" || - elem.style.display === "" && - - // Otherwise, check computed style - // Support: Firefox <=43 - 45 - // Disconnected elements can have computed display: none, so first confirm that elem is - // in the document. - jQuery.contains( elem.ownerDocument, elem ) && - - jQuery.css( elem, "display" ) === "none"; - }; - -var swap = function( elem, options, callback, args ) { - var ret, name, - old = {}; - - // Remember the old values, and insert the new ones - for ( name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - ret = callback.apply( elem, args || [] ); - - // Revert the old values - for ( name in options ) { - elem.style[ name ] = old[ name ]; - } - - return ret; -}; - - - - -function adjustCSS( elem, prop, valueParts, tween ) { - var adjusted, scale, - maxIterations = 20, - currentValue = tween ? - function() { - return tween.cur(); - } : - function() { - return jQuery.css( elem, prop, "" ); - }, - initial = currentValue(), - unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), - - // Starting value computation is required for potential unit mismatches - initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && - rcssNum.exec( jQuery.css( elem, prop ) ); - - if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { - - // Support: Firefox <=54 - // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) - initial = initial / 2; - - // Trust units reported by jQuery.css - unit = unit || initialInUnit[ 3 ]; - - // Iteratively approximate from a nonzero starting point - initialInUnit = +initial || 1; - - while ( maxIterations-- ) { - - // Evaluate and update our best guess (doubling guesses that zero out). - // Finish if the scale equals or crosses 1 (making the old*new product non-positive). - jQuery.style( elem, prop, initialInUnit + unit ); - if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { - maxIterations = 0; - } - initialInUnit = initialInUnit / scale; - - } - - initialInUnit = initialInUnit * 2; - jQuery.style( elem, prop, initialInUnit + unit ); - - // Make sure we update the tween properties later on - valueParts = valueParts || []; - } - - if ( valueParts ) { - initialInUnit = +initialInUnit || +initial || 0; - - // Apply relative offset (+=/-=) if specified - adjusted = valueParts[ 1 ] ? - initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : - +valueParts[ 2 ]; - if ( tween ) { - tween.unit = unit; - tween.start = initialInUnit; - tween.end = adjusted; - } - } - return adjusted; -} - - -var defaultDisplayMap = {}; - -function getDefaultDisplay( elem ) { - var temp, - doc = elem.ownerDocument, - nodeName = elem.nodeName, - display = defaultDisplayMap[ nodeName ]; - - if ( display ) { - return display; - } - - temp = doc.body.appendChild( doc.createElement( nodeName ) ); - display = jQuery.css( temp, "display" ); - - temp.parentNode.removeChild( temp ); - - if ( display === "none" ) { - display = "block"; - } - defaultDisplayMap[ nodeName ] = display; - - return display; -} - -function showHide( elements, show ) { - var display, elem, - values = [], - index = 0, - length = elements.length; - - // Determine new display value for elements that need to change - for ( ; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } - - display = elem.style.display; - if ( show ) { - - // Since we force visibility upon cascade-hidden elements, an immediate (and slow) - // check is required in this first loop unless we have a nonempty display value (either - // inline or about-to-be-restored) - if ( display === "none" ) { - values[ index ] = dataPriv.get( elem, "display" ) || null; - if ( !values[ index ] ) { - elem.style.display = ""; - } - } - if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { - values[ index ] = getDefaultDisplay( elem ); - } - } else { - if ( display !== "none" ) { - values[ index ] = "none"; - - // Remember what we're overwriting - dataPriv.set( elem, "display", display ); - } - } - } - - // Set the display of the elements in a second loop to avoid constant reflow - for ( index = 0; index < length; index++ ) { - if ( values[ index ] != null ) { - elements[ index ].style.display = values[ index ]; - } - } - - return elements; -} - -jQuery.fn.extend( { - show: function() { - return showHide( this, true ); - }, - hide: function() { - return showHide( this ); - }, - toggle: function( state ) { - if ( typeof state === "boolean" ) { - return state ? this.show() : this.hide(); - } - - return this.each( function() { - if ( isHiddenWithinTree( this ) ) { - jQuery( this ).show(); - } else { - jQuery( this ).hide(); - } - } ); - } -} ); -var rcheckableType = ( /^(?:checkbox|radio)$/i ); - -var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]+)/i ); - -var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); - - - -// We have to close these tags to support XHTML (#13200) -var wrapMap = { - - // Support: IE <=9 only - option: [ 1, "" ], - - // XHTML parsers do not magically insert elements in the - // same way that tag soup parsers do. So we cannot shorten - // this by omitting or other required elements. - thead: [ 1, "", "
    " ], - col: [ 2, "", "
    " ], - tr: [ 2, "", "
    " ], - td: [ 3, "", "
    " ], - - _default: [ 0, "", "" ] -}; - -// Support: IE <=9 only -wrapMap.optgroup = wrapMap.option; - -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - - -function getAll( context, tag ) { - - // Support: IE <=9 - 11 only - // Use typeof to avoid zero-argument method invocation on host objects (#15151) - var ret; - - if ( typeof context.getElementsByTagName !== "undefined" ) { - ret = context.getElementsByTagName( tag || "*" ); - - } else if ( typeof context.querySelectorAll !== "undefined" ) { - ret = context.querySelectorAll( tag || "*" ); - - } else { - ret = []; - } - - if ( tag === undefined || tag && nodeName( context, tag ) ) { - return jQuery.merge( [ context ], ret ); - } - - return ret; -} - - -// Mark scripts as having already been evaluated -function setGlobalEval( elems, refElements ) { - var i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - dataPriv.set( - elems[ i ], - "globalEval", - !refElements || dataPriv.get( refElements[ i ], "globalEval" ) - ); - } -} - - -var rhtml = /<|&#?\w+;/; - -function buildFragment( elems, context, scripts, selection, ignored ) { - var elem, tmp, tag, wrap, contains, j, - fragment = context.createDocumentFragment(), - nodes = [], - i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - elem = elems[ i ]; - - if ( elem || elem === 0 ) { - - // Add nodes directly - if ( toType( elem ) === "object" ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); - - // Convert non-html into a text node - } else if ( !rhtml.test( elem ) ) { - nodes.push( context.createTextNode( elem ) ); - - // Convert html into DOM nodes - } else { - tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); - - // Deserialize a standard representation - tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); - wrap = wrapMap[ tag ] || wrapMap._default; - tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; - - // Descend through wrappers to the right content - j = wrap[ 0 ]; - while ( j-- ) { - tmp = tmp.lastChild; - } - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, tmp.childNodes ); - - // Remember the top-level container - tmp = fragment.firstChild; - - // Ensure the created nodes are orphaned (#12392) - tmp.textContent = ""; - } - } - } - - // Remove wrapper from fragment - fragment.textContent = ""; - - i = 0; - while ( ( elem = nodes[ i++ ] ) ) { - - // Skip elements already in the context collection (trac-4087) - if ( selection && jQuery.inArray( elem, selection ) > -1 ) { - if ( ignored ) { - ignored.push( elem ); - } - continue; - } - - contains = jQuery.contains( elem.ownerDocument, elem ); - - // Append to fragment - tmp = getAll( fragment.appendChild( elem ), "script" ); - - // Preserve script evaluation history - if ( contains ) { - setGlobalEval( tmp ); - } - - // Capture executables - if ( scripts ) { - j = 0; - while ( ( elem = tmp[ j++ ] ) ) { - if ( rscriptType.test( elem.type || "" ) ) { - scripts.push( elem ); - } - } - } - } - - return fragment; -} - - -( function() { - var fragment = document.createDocumentFragment(), - div = fragment.appendChild( document.createElement( "div" ) ), - input = document.createElement( "input" ); - - // Support: Android 4.0 - 4.3 only - // Check state lost if the name is set (#11217) - // Support: Windows Web Apps (WWA) - // `name` and `type` must use .setAttribute for WWA (#14901) - input.setAttribute( "type", "radio" ); - input.setAttribute( "checked", "checked" ); - input.setAttribute( "name", "t" ); - - div.appendChild( input ); - - // Support: Android <=4.1 only - // Older WebKit doesn't clone checked state correctly in fragments - support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Support: IE <=11 only - // Make sure textarea (and checkbox) defaultValue is properly cloned - div.innerHTML = ""; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; -} )(); -var documentElement = document.documentElement; - - - -var - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, - rtypenamespace = /^([^.]*)(?:\.(.+)|)/; - -function returnTrue() { - return true; -} - -function returnFalse() { - return false; -} - -// Support: IE <=9 only -// See #13393 for more info -function safeActiveElement() { - try { - return document.activeElement; - } catch ( err ) { } -} - -function on( elem, types, selector, data, fn, one ) { - var origFn, type; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - on( elem, type, selector, data, types[ type ], one ); - } - return elem; - } - - if ( data == null && fn == null ) { - - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return elem; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return elem.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - } ); -} - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - global: {}, - - add: function( elem, types, handler, data, selector ) { - - var handleObjIn, eventHandle, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.get( elem ); - - // Don't attach events to noData or text/comment nodes (but allow plain objects) - if ( !elemData ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } - - // Ensure that invalid selectors throw exceptions at attach time - // Evaluate against documentElement in case elem is a non-element node (e.g., document) - if ( selector ) { - jQuery.find.matchesSelector( documentElement, selector ); - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - if ( !( events = elemData.events ) ) { - events = elemData.events = {}; - } - if ( !( eventHandle = elemData.handle ) ) { - eventHandle = elemData.handle = function( e ) { - - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? - jQuery.event.dispatch.apply( elem, arguments ) : undefined; - }; - } - - // Handle multiple events separated by a space - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // There *must* be a type, no attaching namespace-only handlers - if ( !type ) { - continue; - } - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend( { - type: type, - origType: origType, - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - needsContext: selector && jQuery.expr.match.needsContext.test( selector ), - namespace: namespaces.join( "." ) - }, handleObjIn ); - - // Init the event handler queue if we're the first - if ( !( handlers = events[ type ] ) ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener if the special events handler returns false - if ( !special.setup || - special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; - } - - }, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { - - var j, origCount, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); - - if ( !elemData || !( events = elemData.events ) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[ 2 ] && - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); - - // Remove matching events - origCount = j = handlers.length; - while ( j-- ) { - handleObj = handlers[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || - selector === "**" && handleObj.selector ) ) { - handlers.splice( j, 1 ); - - if ( handleObj.selector ) { - handlers.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( origCount && !handlers.length ) { - if ( !special.teardown || - special.teardown.call( elem, namespaces, elemData.handle ) === false ) { - - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove data and the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - dataPriv.remove( elem, "handle events" ); - } - }, - - dispatch: function( nativeEvent ) { - - // Make a writable jQuery.Event from the native event object - var event = jQuery.event.fix( nativeEvent ); - - var i, j, ret, matched, handleObj, handlerQueue, - args = new Array( arguments.length ), - handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[ 0 ] = event; - - for ( i = 1; i < arguments.length; i++ ) { - args[ i ] = arguments[ i ]; - } - - event.delegateTarget = this; - - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } - - // Determine handlers - handlerQueue = jQuery.event.handlers.call( this, event, handlers ); - - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { - event.currentTarget = matched.elem; - - j = 0; - while ( ( handleObj = matched.handlers[ j++ ] ) && - !event.isImmediatePropagationStopped() ) { - - // Triggered event must either 1) have no namespace, or 2) have namespace(s) - // a subset or equal to those in the bound event (both can have no namespace). - if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) { - - event.handleObj = handleObj; - event.data = handleObj.data; - - ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || - handleObj.handler ).apply( matched.elem, args ); - - if ( ret !== undefined ) { - if ( ( event.result = ret ) === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); - } - - return event.result; - }, - - handlers: function( event, handlers ) { - var i, handleObj, sel, matchedHandlers, matchedSelectors, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; - - // Find delegate handlers - if ( delegateCount && - - // Support: IE <=9 - // Black-hole SVG instance trees (trac-13180) - cur.nodeType && - - // Support: Firefox <=42 - // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) - // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click - // Support: IE 11 only - // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) - !( event.type === "click" && event.button >= 1 ) ) { - - for ( ; cur !== this; cur = cur.parentNode || this ) { - - // Don't check non-elements (#13208) - // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) - if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { - matchedHandlers = []; - matchedSelectors = {}; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - - // Don't conflict with Object.prototype properties (#13203) - sel = handleObj.selector + " "; - - if ( matchedSelectors[ sel ] === undefined ) { - matchedSelectors[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) > -1 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( matchedSelectors[ sel ] ) { - matchedHandlers.push( handleObj ); - } - } - if ( matchedHandlers.length ) { - handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); - } - } - } - } - - // Add the remaining (directly-bound) handlers - cur = this; - if ( delegateCount < handlers.length ) { - handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); - } - - return handlerQueue; - }, - - addProp: function( name, hook ) { - Object.defineProperty( jQuery.Event.prototype, name, { - enumerable: true, - configurable: true, - - get: isFunction( hook ) ? - function() { - if ( this.originalEvent ) { - return hook( this.originalEvent ); - } - } : - function() { - if ( this.originalEvent ) { - return this.originalEvent[ name ]; - } - }, - - set: function( value ) { - Object.defineProperty( this, name, { - enumerable: true, - configurable: true, - writable: true, - value: value - } ); - } - } ); - }, - - fix: function( originalEvent ) { - return originalEvent[ jQuery.expando ] ? - originalEvent : - new jQuery.Event( originalEvent ); - }, - - special: { - load: { - - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - focus: { - - // Fire native event if possible so blur/focus sequence is correct - trigger: function() { - if ( this !== safeActiveElement() && this.focus ) { - this.focus(); - return false; - } - }, - delegateType: "focusin" - }, - blur: { - trigger: function() { - if ( this === safeActiveElement() && this.blur ) { - this.blur(); - return false; - } - }, - delegateType: "focusout" - }, - click: { - - // For checkbox, fire native event so checked state will be right - trigger: function() { - if ( this.type === "checkbox" && this.click && nodeName( this, "input" ) ) { - this.click(); - return false; - } - }, - - // For cross-browser consistency, don't fire native .click() on links - _default: function( event ) { - return nodeName( event.target, "a" ); - } - }, - - beforeunload: { - postDispatch: function( event ) { - - // Support: Firefox 20+ - // Firefox doesn't alert if the returnValue field is not set. - if ( event.result !== undefined && event.originalEvent ) { - event.originalEvent.returnValue = event.result; - } - } - } - } -}; - -jQuery.removeEvent = function( elem, type, handle ) { - - // This "if" is needed for plain objects - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle ); - } -}; - -jQuery.Event = function( src, props ) { - - // Allow instantiation without the 'new' keyword - if ( !( this instanceof jQuery.Event ) ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = src.defaultPrevented || - src.defaultPrevented === undefined && - - // Support: Android <=2.3 only - src.returnValue === false ? - returnTrue : - returnFalse; - - // Create target properties - // Support: Safari <=6 - 7 only - // Target should not be a text node (#504, #13143) - this.target = ( src.target && src.target.nodeType === 3 ) ? - src.target.parentNode : - src.target; - - this.currentTarget = src.currentTarget; - this.relatedTarget = src.relatedTarget; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || Date.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - constructor: jQuery.Event, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, - isSimulated: false, - - preventDefault: function() { - var e = this.originalEvent; - - this.isDefaultPrevented = returnTrue; - - if ( e && !this.isSimulated ) { - e.preventDefault(); - } - }, - stopPropagation: function() { - var e = this.originalEvent; - - this.isPropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopPropagation(); - } - }, - stopImmediatePropagation: function() { - var e = this.originalEvent; - - this.isImmediatePropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopImmediatePropagation(); - } - - this.stopPropagation(); - } -}; - -// Includes all common event props including KeyEvent and MouseEvent specific props -jQuery.each( { - altKey: true, - bubbles: true, - cancelable: true, - changedTouches: true, - ctrlKey: true, - detail: true, - eventPhase: true, - metaKey: true, - pageX: true, - pageY: true, - shiftKey: true, - view: true, - "char": true, - charCode: true, - key: true, - keyCode: true, - button: true, - buttons: true, - clientX: true, - clientY: true, - offsetX: true, - offsetY: true, - pointerId: true, - pointerType: true, - screenX: true, - screenY: true, - targetTouches: true, - toElement: true, - touches: true, - - which: function( event ) { - var button = event.button; - - // Add which for key events - if ( event.which == null && rkeyEvent.test( event.type ) ) { - return event.charCode != null ? event.charCode : event.keyCode; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { - if ( button & 1 ) { - return 1; - } - - if ( button & 2 ) { - return 3; - } - - if ( button & 4 ) { - return 2; - } - - return 0; - } - - return event.which; - } -}, jQuery.event.addProp ); - -// Create mouseenter/leave events using mouseover/out and event-time checks -// so that event delegation works in jQuery. -// Do the same for pointerenter/pointerleave and pointerover/pointerout -// -// Support: Safari 7 only -// Safari sends mouseenter too often; see: -// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 -// for the description of the bug (it existed in older Chrome versions as well). -jQuery.each( { - mouseenter: "mouseover", - mouseleave: "mouseout", - pointerenter: "pointerover", - pointerleave: "pointerout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; - - // For mouseenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -} ); - -jQuery.fn.extend( { - - on: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn ); - }, - one: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - var handleObj, type; - if ( types && types.preventDefault && types.handleObj ) { - - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? - handleObj.origType + "." + handleObj.namespace : - handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - - // ( types-object [, selector] ) - for ( type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each( function() { - jQuery.event.remove( this, types, fn, selector ); - } ); - } -} ); - - -var - - /* eslint-disable max-len */ - - // See https://github.com/eslint/eslint/issues/3229 - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi, - - /* eslint-enable */ - - // Support: IE <=10 - 11, Edge 12 - 13 only - // In IE/Edge using regex groups here causes severe slowdowns. - // See https://connect.microsoft.com/IE/feedback/details/1736512/ - rnoInnerhtml = /\s*$/g; - -// Prefer a tbody over its parent table for containing new rows -function manipulationTarget( elem, content ) { - if ( nodeName( elem, "table" ) && - nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { - - return jQuery( elem ).children( "tbody" )[ 0 ] || elem; - } - - return elem; -} - -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { - elem.type = elem.type.slice( 5 ); - } else { - elem.removeAttribute( "type" ); - } - - return elem; -} - -function cloneCopyEvent( src, dest ) { - var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; - - if ( dest.nodeType !== 1 ) { - return; - } - - // 1. Copy private data: events, handlers, etc. - if ( dataPriv.hasData( src ) ) { - pdataOld = dataPriv.access( src ); - pdataCur = dataPriv.set( dest, pdataOld ); - events = pdataOld.events; - - if ( events ) { - delete pdataCur.handle; - pdataCur.events = {}; - - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } - } - } - - // 2. Copy user data - if ( dataUser.hasData( src ) ) { - udataOld = dataUser.access( src ); - udataCur = jQuery.extend( {}, udataOld ); - - dataUser.set( dest, udataCur ); - } -} - -// Fix IE bugs, see support tests -function fixInput( src, dest ) { - var nodeName = dest.nodeName.toLowerCase(); - - // Fails to persist the checked state of a cloned checkbox or radio button. - if ( nodeName === "input" && rcheckableType.test( src.type ) ) { - dest.checked = src.checked; - - // Fails to return the selected option to the default selected state when cloning options - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } -} - -function domManip( collection, args, callback, ignored ) { - - // Flatten any nested arrays - args = concat.apply( [], args ); - - var fragment, first, scripts, hasScripts, node, doc, - i = 0, - l = collection.length, - iNoClone = l - 1, - value = args[ 0 ], - valueIsFunction = isFunction( value ); - - // We can't cloneNode fragments that contain checked, in WebKit - if ( valueIsFunction || - ( l > 1 && typeof value === "string" && - !support.checkClone && rchecked.test( value ) ) ) { - return collection.each( function( index ) { - var self = collection.eq( index ); - if ( valueIsFunction ) { - args[ 0 ] = value.call( this, index, self.html() ); - } - domManip( self, args, callback, ignored ); - } ); - } - - if ( l ) { - fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = first; - } - - // Require either new content or an interest in ignored elements to invoke the callback - if ( first || ignored ) { - scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); - hasScripts = scripts.length; - - // Use the original fragment for the last item - // instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). - for ( ; i < l; i++ ) { - node = fragment; - - if ( i !== iNoClone ) { - node = jQuery.clone( node, true, true ); - - // Keep references to cloned scripts for later restoration - if ( hasScripts ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( scripts, getAll( node, "script" ) ); - } - } - - callback.call( collection[ i ], node, i ); - } - - if ( hasScripts ) { - doc = scripts[ scripts.length - 1 ].ownerDocument; - - // Reenable scripts - jQuery.map( scripts, restoreScript ); - - // Evaluate executable scripts on first document insertion - for ( i = 0; i < hasScripts; i++ ) { - node = scripts[ i ]; - if ( rscriptType.test( node.type || "" ) && - !dataPriv.access( node, "globalEval" ) && - jQuery.contains( doc, node ) ) { - - if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { - - // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl ) { - jQuery._evalUrl( node.src ); - } - } else { - DOMEval( node.textContent.replace( rcleanScript, "" ), doc, node ); - } - } - } - } - } - } - - return collection; -} - -function remove( elem, selector, keepData ) { - var node, - nodes = selector ? jQuery.filter( selector, elem ) : elem, - i = 0; - - for ( ; ( node = nodes[ i ] ) != null; i++ ) { - if ( !keepData && node.nodeType === 1 ) { - jQuery.cleanData( getAll( node ) ); - } - - if ( node.parentNode ) { - if ( keepData && jQuery.contains( node.ownerDocument, node ) ) { - setGlobalEval( getAll( node, "script" ) ); - } - node.parentNode.removeChild( node ); - } - } - - return elem; -} - -jQuery.extend( { - htmlPrefilter: function( html ) { - return html.replace( rxhtmlTag, "<$1>" ); - }, - - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var i, l, srcElements, destElements, - clone = elem.cloneNode( true ), - inPage = jQuery.contains( elem.ownerDocument, elem ); - - // Fix IE cloning issues - if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && - !jQuery.isXMLDoc( elem ) ) { - - // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 - destElements = getAll( clone ); - srcElements = getAll( elem ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - fixInput( srcElements[ i ], destElements[ i ] ); - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - if ( deepDataAndEvents ) { - srcElements = srcElements || getAll( elem ); - destElements = destElements || getAll( clone ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - cloneCopyEvent( srcElements[ i ], destElements[ i ] ); - } - } else { - cloneCopyEvent( elem, clone ); - } - } - - // Preserve script evaluation history - destElements = getAll( clone, "script" ); - if ( destElements.length > 0 ) { - setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); - } - - // Return the cloned set - return clone; - }, - - cleanData: function( elems ) { - var data, elem, type, - special = jQuery.event.special, - i = 0; - - for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { - if ( acceptData( elem ) ) { - if ( ( data = elem[ dataPriv.expando ] ) ) { - if ( data.events ) { - for ( type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - } - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataPriv.expando ] = undefined; - } - if ( elem[ dataUser.expando ] ) { - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataUser.expando ] = undefined; - } - } - } - } -} ); - -jQuery.fn.extend( { - detach: function( selector ) { - return remove( this, selector, true ); - }, - - remove: function( selector ) { - return remove( this, selector ); - }, - - text: function( value ) { - return access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().each( function() { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - this.textContent = value; - } - } ); - }, null, value, arguments.length ); - }, - - append: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.appendChild( elem ); - } - } ); - }, - - prepend: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.insertBefore( elem, target.firstChild ); - } - } ); - }, - - before: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this ); - } - } ); - }, - - after: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - } - } ); - }, - - empty: function() { - var elem, - i = 0; - - for ( ; ( elem = this[ i ] ) != null; i++ ) { - if ( elem.nodeType === 1 ) { - - // Prevent memory leaks - jQuery.cleanData( getAll( elem, false ) ); - - // Remove any remaining nodes - elem.textContent = ""; - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map( function() { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - } ); - }, - - html: function( value ) { - return access( this, function( value ) { - var elem = this[ 0 ] || {}, - i = 0, - l = this.length; - - if ( value === undefined && elem.nodeType === 1 ) { - return elem.innerHTML; - } - - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { - - value = jQuery.htmlPrefilter( value ); - - try { - for ( ; i < l; i++ ) { - elem = this[ i ] || {}; - - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - elem.innerHTML = value; - } - } - - elem = 0; - - // If using innerHTML throws an exception, use the fallback method - } catch ( e ) {} - } - - if ( elem ) { - this.empty().append( value ); - } - }, null, value, arguments.length ); - }, - - replaceWith: function() { - var ignored = []; - - // Make the changes, replacing each non-ignored context element with the new content - return domManip( this, arguments, function( elem ) { - var parent = this.parentNode; - - if ( jQuery.inArray( this, ignored ) < 0 ) { - jQuery.cleanData( getAll( this ) ); - if ( parent ) { - parent.replaceChild( elem, this ); - } - } - - // Force callback invocation - }, ignored ); - } -} ); - -jQuery.each( { - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var elems, - ret = [], - insert = jQuery( selector ), - last = insert.length - 1, - i = 0; - - for ( ; i <= last; i++ ) { - elems = i === last ? this : this.clone( true ); - jQuery( insert[ i ] )[ original ]( elems ); - - // Support: Android <=4.0 only, PhantomJS 1 only - // .get() because push.apply(_, arraylike) throws on ancient WebKit - push.apply( ret, elems.get() ); - } - - return this.pushStack( ret ); - }; -} ); -var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); - -var getStyles = function( elem ) { - - // Support: IE <=11 only, Firefox <=30 (#15098, #14150) - // IE throws on elements created in popups - // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" - var view = elem.ownerDocument.defaultView; - - if ( !view || !view.opener ) { - view = window; - } - - return view.getComputedStyle( elem ); - }; - -var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); - - - -( function() { - - // Executing both pixelPosition & boxSizingReliable tests require only one layout - // so they're executed at the same time to save the second computation. - function computeStyleTests() { - - // This is a singleton, we need to execute it only once - if ( !div ) { - return; - } - - container.style.cssText = "position:absolute;left:-11111px;width:60px;" + - "margin-top:1px;padding:0;border:0"; - div.style.cssText = - "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + - "margin:auto;border:1px;padding:1px;" + - "width:60%;top:1%"; - documentElement.appendChild( container ).appendChild( div ); - - var divStyle = window.getComputedStyle( div ); - pixelPositionVal = divStyle.top !== "1%"; - - // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 - reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; - - // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 - // Some styles come back with percentage values, even though they shouldn't - div.style.right = "60%"; - pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; - - // Support: IE 9 - 11 only - // Detect misreporting of content dimensions for box-sizing:border-box elements - boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; - - // Support: IE 9 only - // Detect overflow:scroll screwiness (gh-3699) - div.style.position = "absolute"; - scrollboxSizeVal = div.offsetWidth === 36 || "absolute"; - - documentElement.removeChild( container ); - - // Nullify the div so it wouldn't be stored in the memory and - // it will also be a sign that checks already performed - div = null; - } - - function roundPixelMeasures( measure ) { - return Math.round( parseFloat( measure ) ); - } - - var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, - reliableMarginLeftVal, - container = document.createElement( "div" ), - div = document.createElement( "div" ); - - // Finish early in limited (non-browser) environments - if ( !div.style ) { - return; - } - - // Support: IE <=9 - 11 only - // Style of cloned element affects source element cloned (#8908) - div.style.backgroundClip = "content-box"; - div.cloneNode( true ).style.backgroundClip = ""; - support.clearCloneStyle = div.style.backgroundClip === "content-box"; - - jQuery.extend( support, { - boxSizingReliable: function() { - computeStyleTests(); - return boxSizingReliableVal; - }, - pixelBoxStyles: function() { - computeStyleTests(); - return pixelBoxStylesVal; - }, - pixelPosition: function() { - computeStyleTests(); - return pixelPositionVal; - }, - reliableMarginLeft: function() { - computeStyleTests(); - return reliableMarginLeftVal; - }, - scrollboxSize: function() { - computeStyleTests(); - return scrollboxSizeVal; - } - } ); -} )(); - - -function curCSS( elem, name, computed ) { - var width, minWidth, maxWidth, ret, - - // Support: Firefox 51+ - // Retrieving style before computed somehow - // fixes an issue with getting wrong values - // on detached elements - style = elem.style; - - computed = computed || getStyles( elem ); - - // getPropertyValue is needed for: - // .css('filter') (IE 9 only, #12537) - // .css('--customProperty) (#3144) - if ( computed ) { - ret = computed.getPropertyValue( name ) || computed[ name ]; - - if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { - ret = jQuery.style( elem, name ); - } - - // A tribute to the "awesome hack by Dean Edwards" - // Android Browser returns percentage for some values, - // but width seems to be reliably pixels. - // This is against the CSSOM draft spec: - // https://drafts.csswg.org/cssom/#resolved-values - if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { - - // Remember the original values - width = style.width; - minWidth = style.minWidth; - maxWidth = style.maxWidth; - - // Put in the new values to get a computed value out - style.minWidth = style.maxWidth = style.width = ret; - ret = computed.width; - - // Revert the changed values - style.width = width; - style.minWidth = minWidth; - style.maxWidth = maxWidth; - } - } - - return ret !== undefined ? - - // Support: IE <=9 - 11 only - // IE returns zIndex value as an integer. - ret + "" : - ret; -} - - -function addGetHookIf( conditionFn, hookFn ) { - - // Define the hook, we'll check on the first run if it's really needed. - return { - get: function() { - if ( conditionFn() ) { - - // Hook not needed (or it's not possible to use it due - // to missing dependency), remove it. - delete this.get; - return; - } - - // Hook needed; redefine it so that the support test is not executed again. - return ( this.get = hookFn ).apply( this, arguments ); - } - }; -} - - -var - - // Swappable if display is none or starts with table - // except "table", "table-cell", or "table-caption" - // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - rcustomProp = /^--/, - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: "0", - fontWeight: "400" - }, - - cssPrefixes = [ "Webkit", "Moz", "ms" ], - emptyStyle = document.createElement( "div" ).style; - -// Return a css property mapped to a potentially vendor prefixed property -function vendorPropName( name ) { - - // Shortcut for names that are not vendor prefixed - if ( name in emptyStyle ) { - return name; - } - - // Check for vendor prefixed names - var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), - i = cssPrefixes.length; - - while ( i-- ) { - name = cssPrefixes[ i ] + capName; - if ( name in emptyStyle ) { - return name; - } - } -} - -// Return a property mapped along what jQuery.cssProps suggests or to -// a vendor prefixed property. -function finalPropName( name ) { - var ret = jQuery.cssProps[ name ]; - if ( !ret ) { - ret = jQuery.cssProps[ name ] = vendorPropName( name ) || name; - } - return ret; -} - -function setPositiveNumber( elem, value, subtract ) { - - // Any relative (+/-) values have already been - // normalized at this point - var matches = rcssNum.exec( value ); - return matches ? - - // Guard against undefined "subtract", e.g., when used as in cssHooks - Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : - value; -} - -function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { - var i = dimension === "width" ? 1 : 0, - extra = 0, - delta = 0; - - // Adjustment may not be necessary - if ( box === ( isBorderBox ? "border" : "content" ) ) { - return 0; - } - - for ( ; i < 4; i += 2 ) { - - // Both box models exclude margin - if ( box === "margin" ) { - delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); - } - - // If we get here with a content-box, we're seeking "padding" or "border" or "margin" - if ( !isBorderBox ) { - - // Add padding - delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - - // For "border" or "margin", add border - if ( box !== "padding" ) { - delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - - // But still keep track of it otherwise - } else { - extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - - // If we get here with a border-box (content + padding + border), we're seeking "content" or - // "padding" or "margin" - } else { - - // For "content", subtract padding - if ( box === "content" ) { - delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - } - - // For "content" or "padding", subtract border - if ( box !== "margin" ) { - delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } - } - - // Account for positive content-box scroll gutter when requested by providing computedVal - if ( !isBorderBox && computedVal >= 0 ) { - - // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border - // Assuming integer scroll gutter, subtract the rest and round down - delta += Math.max( 0, Math.ceil( - elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - - computedVal - - delta - - extra - - 0.5 - ) ); - } - - return delta; -} - -function getWidthOrHeight( elem, dimension, extra ) { - - // Start with computed style - var styles = getStyles( elem ), - val = curCSS( elem, dimension, styles ), - isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - valueIsBorderBox = isBorderBox; - - // Support: Firefox <=54 - // Return a confounding non-pixel value or feign ignorance, as appropriate. - if ( rnumnonpx.test( val ) ) { - if ( !extra ) { - return val; - } - val = "auto"; - } - - // Check for style in case a browser which returns unreliable values - // for getComputedStyle silently falls back to the reliable elem.style - valueIsBorderBox = valueIsBorderBox && - ( support.boxSizingReliable() || val === elem.style[ dimension ] ); - - // Fall back to offsetWidth/offsetHeight when value is "auto" - // This happens for inline elements with no explicit setting (gh-3571) - // Support: Android <=4.1 - 4.3 only - // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) - if ( val === "auto" || - !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) { - - val = elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ]; - - // offsetWidth/offsetHeight provide border-box values - valueIsBorderBox = true; - } - - // Normalize "" and auto - val = parseFloat( val ) || 0; - - // Adjust for the element's box model - return ( val + - boxModelAdjustment( - elem, - dimension, - extra || ( isBorderBox ? "border" : "content" ), - valueIsBorderBox, - styles, - - // Provide the current computed size to request scroll gutter calculation (gh-3589) - val - ) - ) + "px"; -} - -jQuery.extend( { - - // Add in style property hooks for overriding the default - // behavior of getting and setting a style property - cssHooks: { - opacity: { - get: function( elem, computed ) { - if ( computed ) { - - // We should always get a number back from opacity - var ret = curCSS( elem, "opacity" ); - return ret === "" ? "1" : ret; - } - } - } - }, - - // Don't automatically add "px" to these possibly-unitless properties - cssNumber: { - "animationIterationCount": true, - "columnCount": true, - "fillOpacity": true, - "flexGrow": true, - "flexShrink": true, - "fontWeight": true, - "lineHeight": true, - "opacity": true, - "order": true, - "orphans": true, - "widows": true, - "zIndex": true, - "zoom": true - }, - - // Add in properties whose names you wish to fix before - // setting or getting the value - cssProps: {}, - - // Get and set the style property on a DOM Node - style: function( elem, name, value, extra ) { - - // Don't set styles on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { - return; - } - - // Make sure that we're working with the right name - var ret, type, hooks, - origName = camelCase( name ), - isCustomProp = rcustomProp.test( name ), - style = elem.style; - - // Make sure that we're working with the right name. We don't - // want to query the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Gets hook for the prefixed version, then unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // Check if we're setting a value - if ( value !== undefined ) { - type = typeof value; - - // Convert "+=" or "-=" to relative numbers (#7345) - if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { - value = adjustCSS( elem, name, ret ); - - // Fixes bug #9237 - type = "number"; - } - - // Make sure that null and NaN values aren't set (#7116) - if ( value == null || value !== value ) { - return; - } - - // If a number was passed in, add the unit (except for certain CSS properties) - if ( type === "number" ) { - value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); - } - - // background-* props affect original clone's values - if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { - style[ name ] = "inherit"; - } - - // If a hook was provided, use that value, otherwise just set the specified value - if ( !hooks || !( "set" in hooks ) || - ( value = hooks.set( elem, value, extra ) ) !== undefined ) { - - if ( isCustomProp ) { - style.setProperty( name, value ); - } else { - style[ name ] = value; - } - } - - } else { - - // If a hook was provided get the non-computed value from there - if ( hooks && "get" in hooks && - ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { - - return ret; - } - - // Otherwise just get the value from the style object - return style[ name ]; - } - }, - - css: function( elem, name, extra, styles ) { - var val, num, hooks, - origName = camelCase( name ), - isCustomProp = rcustomProp.test( name ); - - // Make sure that we're working with the right name. We don't - // want to modify the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Try prefixed name followed by the unprefixed name - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // If a hook was provided get the computed value from there - if ( hooks && "get" in hooks ) { - val = hooks.get( elem, true, extra ); - } - - // Otherwise, if a way to get the computed value exists, use that - if ( val === undefined ) { - val = curCSS( elem, name, styles ); - } - - // Convert "normal" to computed value - if ( val === "normal" && name in cssNormalTransform ) { - val = cssNormalTransform[ name ]; - } - - // Make numeric if forced or a qualifier was provided and val looks numeric - if ( extra === "" || extra ) { - num = parseFloat( val ); - return extra === true || isFinite( num ) ? num || 0 : val; - } - - return val; - } -} ); - -jQuery.each( [ "height", "width" ], function( i, dimension ) { - jQuery.cssHooks[ dimension ] = { - get: function( elem, computed, extra ) { - if ( computed ) { - - // Certain elements can have dimension info if we invisibly show them - // but it must have a current display style that would benefit - return rdisplayswap.test( jQuery.css( elem, "display" ) ) && - - // Support: Safari 8+ - // Table columns in Safari have non-zero offsetWidth & zero - // getBoundingClientRect().width unless display is changed. - // Support: IE <=11 only - // Running getBoundingClientRect on a disconnected node - // in IE throws an error. - ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? - swap( elem, cssShow, function() { - return getWidthOrHeight( elem, dimension, extra ); - } ) : - getWidthOrHeight( elem, dimension, extra ); - } - }, - - set: function( elem, value, extra ) { - var matches, - styles = getStyles( elem ), - isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - subtract = extra && boxModelAdjustment( - elem, - dimension, - extra, - isBorderBox, - styles - ); - - // Account for unreliable border-box dimensions by comparing offset* to computed and - // faking a content-box to get border and padding (gh-3699) - if ( isBorderBox && support.scrollboxSize() === styles.position ) { - subtract -= Math.ceil( - elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - - parseFloat( styles[ dimension ] ) - - boxModelAdjustment( elem, dimension, "border", false, styles ) - - 0.5 - ); - } - - // Convert to pixels if value adjustment is needed - if ( subtract && ( matches = rcssNum.exec( value ) ) && - ( matches[ 3 ] || "px" ) !== "px" ) { - - elem.style[ dimension ] = value; - value = jQuery.css( elem, dimension ); - } - - return setPositiveNumber( elem, value, subtract ); - } - }; -} ); - -jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, - function( elem, computed ) { - if ( computed ) { - return ( parseFloat( curCSS( elem, "marginLeft" ) ) || - elem.getBoundingClientRect().left - - swap( elem, { marginLeft: 0 }, function() { - return elem.getBoundingClientRect().left; - } ) - ) + "px"; - } - } -); - -// These hooks are used by animate to expand properties -jQuery.each( { - margin: "", - padding: "", - border: "Width" -}, function( prefix, suffix ) { - jQuery.cssHooks[ prefix + suffix ] = { - expand: function( value ) { - var i = 0, - expanded = {}, - - // Assumes a single number if not a string - parts = typeof value === "string" ? value.split( " " ) : [ value ]; - - for ( ; i < 4; i++ ) { - expanded[ prefix + cssExpand[ i ] + suffix ] = - parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; - } - - return expanded; - } - }; - - if ( prefix !== "margin" ) { - jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; - } -} ); - -jQuery.fn.extend( { - css: function( name, value ) { - return access( this, function( elem, name, value ) { - var styles, len, - map = {}, - i = 0; - - if ( Array.isArray( name ) ) { - styles = getStyles( elem ); - len = name.length; - - for ( ; i < len; i++ ) { - map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); - } - - return map; - } - - return value !== undefined ? - jQuery.style( elem, name, value ) : - jQuery.css( elem, name ); - }, name, value, arguments.length > 1 ); - } -} ); - - -function Tween( elem, options, prop, end, easing ) { - return new Tween.prototype.init( elem, options, prop, end, easing ); -} -jQuery.Tween = Tween; - -Tween.prototype = { - constructor: Tween, - init: function( elem, options, prop, end, easing, unit ) { - this.elem = elem; - this.prop = prop; - this.easing = easing || jQuery.easing._default; - this.options = options; - this.start = this.now = this.cur(); - this.end = end; - this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); - }, - cur: function() { - var hooks = Tween.propHooks[ this.prop ]; - - return hooks && hooks.get ? - hooks.get( this ) : - Tween.propHooks._default.get( this ); - }, - run: function( percent ) { - var eased, - hooks = Tween.propHooks[ this.prop ]; - - if ( this.options.duration ) { - this.pos = eased = jQuery.easing[ this.easing ]( - percent, this.options.duration * percent, 0, 1, this.options.duration - ); - } else { - this.pos = eased = percent; - } - this.now = ( this.end - this.start ) * eased + this.start; - - if ( this.options.step ) { - this.options.step.call( this.elem, this.now, this ); - } - - if ( hooks && hooks.set ) { - hooks.set( this ); - } else { - Tween.propHooks._default.set( this ); - } - return this; - } -}; - -Tween.prototype.init.prototype = Tween.prototype; - -Tween.propHooks = { - _default: { - get: function( tween ) { - var result; - - // Use a property on the element directly when it is not a DOM element, - // or when there is no matching style property that exists. - if ( tween.elem.nodeType !== 1 || - tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { - return tween.elem[ tween.prop ]; - } - - // Passing an empty string as a 3rd parameter to .css will automatically - // attempt a parseFloat and fallback to a string if the parse fails. - // Simple values such as "10px" are parsed to Float; - // complex values such as "rotate(1rad)" are returned as-is. - result = jQuery.css( tween.elem, tween.prop, "" ); - - // Empty strings, null, undefined and "auto" are converted to 0. - return !result || result === "auto" ? 0 : result; - }, - set: function( tween ) { - - // Use step hook for back compat. - // Use cssHook if its there. - // Use .style if available and use plain properties where available. - if ( jQuery.fx.step[ tween.prop ] ) { - jQuery.fx.step[ tween.prop ]( tween ); - } else if ( tween.elem.nodeType === 1 && - ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || - jQuery.cssHooks[ tween.prop ] ) ) { - jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); - } else { - tween.elem[ tween.prop ] = tween.now; - } - } - } -}; - -// Support: IE <=9 only -// Panic based approach to setting things on disconnected nodes -Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { - set: function( tween ) { - if ( tween.elem.nodeType && tween.elem.parentNode ) { - tween.elem[ tween.prop ] = tween.now; - } - } -}; - -jQuery.easing = { - linear: function( p ) { - return p; - }, - swing: function( p ) { - return 0.5 - Math.cos( p * Math.PI ) / 2; - }, - _default: "swing" -}; - -jQuery.fx = Tween.prototype.init; - -// Back compat <1.8 extension point -jQuery.fx.step = {}; - - - - -var - fxNow, inProgress, - rfxtypes = /^(?:toggle|show|hide)$/, - rrun = /queueHooks$/; - -function schedule() { - if ( inProgress ) { - if ( document.hidden === false && window.requestAnimationFrame ) { - window.requestAnimationFrame( schedule ); - } else { - window.setTimeout( schedule, jQuery.fx.interval ); - } - - jQuery.fx.tick(); - } -} - -// Animations created synchronously will run synchronously -function createFxNow() { - window.setTimeout( function() { - fxNow = undefined; - } ); - return ( fxNow = Date.now() ); -} - -// Generate parameters to create a standard animation -function genFx( type, includeWidth ) { - var which, - i = 0, - attrs = { height: type }; - - // If we include width, step value is 1 to do all cssExpand values, - // otherwise step value is 2 to skip over Left and Right - includeWidth = includeWidth ? 1 : 0; - for ( ; i < 4; i += 2 - includeWidth ) { - which = cssExpand[ i ]; - attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; - } - - if ( includeWidth ) { - attrs.opacity = attrs.width = type; - } - - return attrs; -} - -function createTween( value, prop, animation ) { - var tween, - collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), - index = 0, - length = collection.length; - for ( ; index < length; index++ ) { - if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { - - // We're done with this property - return tween; - } - } -} - -function defaultPrefilter( elem, props, opts ) { - var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, - isBox = "width" in props || "height" in props, - anim = this, - orig = {}, - style = elem.style, - hidden = elem.nodeType && isHiddenWithinTree( elem ), - dataShow = dataPriv.get( elem, "fxshow" ); - - // Queue-skipping animations hijack the fx hooks - if ( !opts.queue ) { - hooks = jQuery._queueHooks( elem, "fx" ); - if ( hooks.unqueued == null ) { - hooks.unqueued = 0; - oldfire = hooks.empty.fire; - hooks.empty.fire = function() { - if ( !hooks.unqueued ) { - oldfire(); - } - }; - } - hooks.unqueued++; - - anim.always( function() { - - // Ensure the complete handler is called before this completes - anim.always( function() { - hooks.unqueued--; - if ( !jQuery.queue( elem, "fx" ).length ) { - hooks.empty.fire(); - } - } ); - } ); - } - - // Detect show/hide animations - for ( prop in props ) { - value = props[ prop ]; - if ( rfxtypes.test( value ) ) { - delete props[ prop ]; - toggle = toggle || value === "toggle"; - if ( value === ( hidden ? "hide" : "show" ) ) { - - // Pretend to be hidden if this is a "show" and - // there is still data from a stopped show/hide - if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { - hidden = true; - - // Ignore all other no-op show/hide data - } else { - continue; - } - } - orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); - } - } - - // Bail out if this is a no-op like .hide().hide() - propTween = !jQuery.isEmptyObject( props ); - if ( !propTween && jQuery.isEmptyObject( orig ) ) { - return; - } - - // Restrict "overflow" and "display" styles during box animations - if ( isBox && elem.nodeType === 1 ) { - - // Support: IE <=9 - 11, Edge 12 - 15 - // Record all 3 overflow attributes because IE does not infer the shorthand - // from identically-valued overflowX and overflowY and Edge just mirrors - // the overflowX value there. - opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; - - // Identify a display type, preferring old show/hide data over the CSS cascade - restoreDisplay = dataShow && dataShow.display; - if ( restoreDisplay == null ) { - restoreDisplay = dataPriv.get( elem, "display" ); - } - display = jQuery.css( elem, "display" ); - if ( display === "none" ) { - if ( restoreDisplay ) { - display = restoreDisplay; - } else { - - // Get nonempty value(s) by temporarily forcing visibility - showHide( [ elem ], true ); - restoreDisplay = elem.style.display || restoreDisplay; - display = jQuery.css( elem, "display" ); - showHide( [ elem ] ); - } - } - - // Animate inline elements as inline-block - if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { - if ( jQuery.css( elem, "float" ) === "none" ) { - - // Restore the original display value at the end of pure show/hide animations - if ( !propTween ) { - anim.done( function() { - style.display = restoreDisplay; - } ); - if ( restoreDisplay == null ) { - display = style.display; - restoreDisplay = display === "none" ? "" : display; - } - } - style.display = "inline-block"; - } - } - } - - if ( opts.overflow ) { - style.overflow = "hidden"; - anim.always( function() { - style.overflow = opts.overflow[ 0 ]; - style.overflowX = opts.overflow[ 1 ]; - style.overflowY = opts.overflow[ 2 ]; - } ); - } - - // Implement show/hide animations - propTween = false; - for ( prop in orig ) { - - // General show/hide setup for this element animation - if ( !propTween ) { - if ( dataShow ) { - if ( "hidden" in dataShow ) { - hidden = dataShow.hidden; - } - } else { - dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); - } - - // Store hidden/visible for toggle so `.stop().toggle()` "reverses" - if ( toggle ) { - dataShow.hidden = !hidden; - } - - // Show elements before animating them - if ( hidden ) { - showHide( [ elem ], true ); - } - - /* eslint-disable no-loop-func */ - - anim.done( function() { - - /* eslint-enable no-loop-func */ - - // The final step of a "hide" animation is actually hiding the element - if ( !hidden ) { - showHide( [ elem ] ); - } - dataPriv.remove( elem, "fxshow" ); - for ( prop in orig ) { - jQuery.style( elem, prop, orig[ prop ] ); - } - } ); - } - - // Per-property setup - propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); - if ( !( prop in dataShow ) ) { - dataShow[ prop ] = propTween.start; - if ( hidden ) { - propTween.end = propTween.start; - propTween.start = 0; - } - } - } -} - -function propFilter( props, specialEasing ) { - var index, name, easing, value, hooks; - - // camelCase, specialEasing and expand cssHook pass - for ( index in props ) { - name = camelCase( index ); - easing = specialEasing[ name ]; - value = props[ index ]; - if ( Array.isArray( value ) ) { - easing = value[ 1 ]; - value = props[ index ] = value[ 0 ]; - } - - if ( index !== name ) { - props[ name ] = value; - delete props[ index ]; - } - - hooks = jQuery.cssHooks[ name ]; - if ( hooks && "expand" in hooks ) { - value = hooks.expand( value ); - delete props[ name ]; - - // Not quite $.extend, this won't overwrite existing keys. - // Reusing 'index' because we have the correct "name" - for ( index in value ) { - if ( !( index in props ) ) { - props[ index ] = value[ index ]; - specialEasing[ index ] = easing; - } - } - } else { - specialEasing[ name ] = easing; - } - } -} - -function Animation( elem, properties, options ) { - var result, - stopped, - index = 0, - length = Animation.prefilters.length, - deferred = jQuery.Deferred().always( function() { - - // Don't match elem in the :animated selector - delete tick.elem; - } ), - tick = function() { - if ( stopped ) { - return false; - } - var currentTime = fxNow || createFxNow(), - remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), - - // Support: Android 2.3 only - // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) - temp = remaining / animation.duration || 0, - percent = 1 - temp, - index = 0, - length = animation.tweens.length; - - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( percent ); - } - - deferred.notifyWith( elem, [ animation, percent, remaining ] ); - - // If there's more to do, yield - if ( percent < 1 && length ) { - return remaining; - } - - // If this was an empty animation, synthesize a final progress notification - if ( !length ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - } - - // Resolve the animation and report its conclusion - deferred.resolveWith( elem, [ animation ] ); - return false; - }, - animation = deferred.promise( { - elem: elem, - props: jQuery.extend( {}, properties ), - opts: jQuery.extend( true, { - specialEasing: {}, - easing: jQuery.easing._default - }, options ), - originalProperties: properties, - originalOptions: options, - startTime: fxNow || createFxNow(), - duration: options.duration, - tweens: [], - createTween: function( prop, end ) { - var tween = jQuery.Tween( elem, animation.opts, prop, end, - animation.opts.specialEasing[ prop ] || animation.opts.easing ); - animation.tweens.push( tween ); - return tween; - }, - stop: function( gotoEnd ) { - var index = 0, - - // If we are going to the end, we want to run all the tweens - // otherwise we skip this part - length = gotoEnd ? animation.tweens.length : 0; - if ( stopped ) { - return this; - } - stopped = true; - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( 1 ); - } - - // Resolve when we played the last frame; otherwise, reject - if ( gotoEnd ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - deferred.resolveWith( elem, [ animation, gotoEnd ] ); - } else { - deferred.rejectWith( elem, [ animation, gotoEnd ] ); - } - return this; - } - } ), - props = animation.props; - - propFilter( props, animation.opts.specialEasing ); - - for ( ; index < length; index++ ) { - result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); - if ( result ) { - if ( isFunction( result.stop ) ) { - jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = - result.stop.bind( result ); - } - return result; - } - } - - jQuery.map( props, createTween, animation ); - - if ( isFunction( animation.opts.start ) ) { - animation.opts.start.call( elem, animation ); - } - - // Attach callbacks from options - animation - .progress( animation.opts.progress ) - .done( animation.opts.done, animation.opts.complete ) - .fail( animation.opts.fail ) - .always( animation.opts.always ); - - jQuery.fx.timer( - jQuery.extend( tick, { - elem: elem, - anim: animation, - queue: animation.opts.queue - } ) - ); - - return animation; -} - -jQuery.Animation = jQuery.extend( Animation, { - - tweeners: { - "*": [ function( prop, value ) { - var tween = this.createTween( prop, value ); - adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); - return tween; - } ] - }, - - tweener: function( props, callback ) { - if ( isFunction( props ) ) { - callback = props; - props = [ "*" ]; - } else { - props = props.match( rnothtmlwhite ); - } - - var prop, - index = 0, - length = props.length; - - for ( ; index < length; index++ ) { - prop = props[ index ]; - Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; - Animation.tweeners[ prop ].unshift( callback ); - } - }, - - prefilters: [ defaultPrefilter ], - - prefilter: function( callback, prepend ) { - if ( prepend ) { - Animation.prefilters.unshift( callback ); - } else { - Animation.prefilters.push( callback ); - } - } -} ); - -jQuery.speed = function( speed, easing, fn ) { - var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { - complete: fn || !fn && easing || - isFunction( speed ) && speed, - duration: speed, - easing: fn && easing || easing && !isFunction( easing ) && easing - }; - - // Go to the end state if fx are off - if ( jQuery.fx.off ) { - opt.duration = 0; - - } else { - if ( typeof opt.duration !== "number" ) { - if ( opt.duration in jQuery.fx.speeds ) { - opt.duration = jQuery.fx.speeds[ opt.duration ]; - - } else { - opt.duration = jQuery.fx.speeds._default; - } - } - } - - // Normalize opt.queue - true/undefined/null -> "fx" - if ( opt.queue == null || opt.queue === true ) { - opt.queue = "fx"; - } - - // Queueing - opt.old = opt.complete; - - opt.complete = function() { - if ( isFunction( opt.old ) ) { - opt.old.call( this ); - } - - if ( opt.queue ) { - jQuery.dequeue( this, opt.queue ); - } - }; - - return opt; -}; - -jQuery.fn.extend( { - fadeTo: function( speed, to, easing, callback ) { - - // Show any hidden elements after setting opacity to 0 - return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() - - // Animate to the value specified - .end().animate( { opacity: to }, speed, easing, callback ); - }, - animate: function( prop, speed, easing, callback ) { - var empty = jQuery.isEmptyObject( prop ), - optall = jQuery.speed( speed, easing, callback ), - doAnimation = function() { - - // Operate on a copy of prop so per-property easing won't be lost - var anim = Animation( this, jQuery.extend( {}, prop ), optall ); - - // Empty animations, or finishing resolves immediately - if ( empty || dataPriv.get( this, "finish" ) ) { - anim.stop( true ); - } - }; - doAnimation.finish = doAnimation; - - return empty || optall.queue === false ? - this.each( doAnimation ) : - this.queue( optall.queue, doAnimation ); - }, - stop: function( type, clearQueue, gotoEnd ) { - var stopQueue = function( hooks ) { - var stop = hooks.stop; - delete hooks.stop; - stop( gotoEnd ); - }; - - if ( typeof type !== "string" ) { - gotoEnd = clearQueue; - clearQueue = type; - type = undefined; - } - if ( clearQueue && type !== false ) { - this.queue( type || "fx", [] ); - } - - return this.each( function() { - var dequeue = true, - index = type != null && type + "queueHooks", - timers = jQuery.timers, - data = dataPriv.get( this ); - - if ( index ) { - if ( data[ index ] && data[ index ].stop ) { - stopQueue( data[ index ] ); - } - } else { - for ( index in data ) { - if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { - stopQueue( data[ index ] ); - } - } - } - - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && - ( type == null || timers[ index ].queue === type ) ) { - - timers[ index ].anim.stop( gotoEnd ); - dequeue = false; - timers.splice( index, 1 ); - } - } - - // Start the next in the queue if the last step wasn't forced. - // Timers currently will call their complete callbacks, which - // will dequeue but only if they were gotoEnd. - if ( dequeue || !gotoEnd ) { - jQuery.dequeue( this, type ); - } - } ); - }, - finish: function( type ) { - if ( type !== false ) { - type = type || "fx"; - } - return this.each( function() { - var index, - data = dataPriv.get( this ), - queue = data[ type + "queue" ], - hooks = data[ type + "queueHooks" ], - timers = jQuery.timers, - length = queue ? queue.length : 0; - - // Enable finishing flag on private data - data.finish = true; - - // Empty the queue first - jQuery.queue( this, type, [] ); - - if ( hooks && hooks.stop ) { - hooks.stop.call( this, true ); - } - - // Look for any active animations, and finish them - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && timers[ index ].queue === type ) { - timers[ index ].anim.stop( true ); - timers.splice( index, 1 ); - } - } - - // Look for any animations in the old queue and finish them - for ( index = 0; index < length; index++ ) { - if ( queue[ index ] && queue[ index ].finish ) { - queue[ index ].finish.call( this ); - } - } - - // Turn off finishing flag - delete data.finish; - } ); - } -} ); - -jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) { - var cssFn = jQuery.fn[ name ]; - jQuery.fn[ name ] = function( speed, easing, callback ) { - return speed == null || typeof speed === "boolean" ? - cssFn.apply( this, arguments ) : - this.animate( genFx( name, true ), speed, easing, callback ); - }; -} ); - -// Generate shortcuts for custom animations -jQuery.each( { - slideDown: genFx( "show" ), - slideUp: genFx( "hide" ), - slideToggle: genFx( "toggle" ), - fadeIn: { opacity: "show" }, - fadeOut: { opacity: "hide" }, - fadeToggle: { opacity: "toggle" } -}, function( name, props ) { - jQuery.fn[ name ] = function( speed, easing, callback ) { - return this.animate( props, speed, easing, callback ); - }; -} ); - -jQuery.timers = []; -jQuery.fx.tick = function() { - var timer, - i = 0, - timers = jQuery.timers; - - fxNow = Date.now(); - - for ( ; i < timers.length; i++ ) { - timer = timers[ i ]; - - // Run the timer and safely remove it when done (allowing for external removal) - if ( !timer() && timers[ i ] === timer ) { - timers.splice( i--, 1 ); - } - } - - if ( !timers.length ) { - jQuery.fx.stop(); - } - fxNow = undefined; -}; - -jQuery.fx.timer = function( timer ) { - jQuery.timers.push( timer ); - jQuery.fx.start(); -}; - -jQuery.fx.interval = 13; -jQuery.fx.start = function() { - if ( inProgress ) { - return; - } - - inProgress = true; - schedule(); -}; - -jQuery.fx.stop = function() { - inProgress = null; -}; - -jQuery.fx.speeds = { - slow: 600, - fast: 200, - - // Default speed - _default: 400 -}; - - -// Based off of the plugin by Clint Helfers, with permission. -// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ -jQuery.fn.delay = function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; - type = type || "fx"; - - return this.queue( type, function( next, hooks ) { - var timeout = window.setTimeout( next, time ); - hooks.stop = function() { - window.clearTimeout( timeout ); - }; - } ); -}; - - -( function() { - var input = document.createElement( "input" ), - select = document.createElement( "select" ), - opt = select.appendChild( document.createElement( "option" ) ); - - input.type = "checkbox"; - - // Support: Android <=4.3 only - // Default value for a checkbox should be "on" - support.checkOn = input.value !== ""; - - // Support: IE <=11 only - // Must access selectedIndex to make default options select - support.optSelected = opt.selected; - - // Support: IE <=11 only - // An input loses its value after becoming a radio - input = document.createElement( "input" ); - input.value = "t"; - input.type = "radio"; - support.radioValue = input.value === "t"; -} )(); - - -var boolHook, - attrHandle = jQuery.expr.attrHandle; - -jQuery.fn.extend( { - attr: function( name, value ) { - return access( this, jQuery.attr, name, value, arguments.length > 1 ); - }, - - removeAttr: function( name ) { - return this.each( function() { - jQuery.removeAttr( this, name ); - } ); - } -} ); - -jQuery.extend( { - attr: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set attributes on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === "undefined" ) { - return jQuery.prop( elem, name, value ); - } - - // Attribute hooks are determined by the lowercase version - // Grab necessary hook if one is defined - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - hooks = jQuery.attrHooks[ name.toLowerCase() ] || - ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); - } - - if ( value !== undefined ) { - if ( value === null ) { - jQuery.removeAttr( elem, name ); - return; - } - - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - elem.setAttribute( name, value + "" ); - return value; - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - ret = jQuery.find.attr( elem, name ); - - // Non-existent attributes return null, we normalize to undefined - return ret == null ? undefined : ret; - }, - - attrHooks: { - type: { - set: function( elem, value ) { - if ( !support.radioValue && value === "radio" && - nodeName( elem, "input" ) ) { - var val = elem.value; - elem.setAttribute( "type", value ); - if ( val ) { - elem.value = val; - } - return value; - } - } - } - }, - - removeAttr: function( elem, value ) { - var name, - i = 0, - - // Attribute names can contain non-HTML whitespace characters - // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 - attrNames = value && value.match( rnothtmlwhite ); - - if ( attrNames && elem.nodeType === 1 ) { - while ( ( name = attrNames[ i++ ] ) ) { - elem.removeAttribute( name ); - } - } - } -} ); - -// Hooks for boolean attributes -boolHook = { - set: function( elem, value, name ) { - if ( value === false ) { - - // Remove boolean attributes when set to false - jQuery.removeAttr( elem, name ); - } else { - elem.setAttribute( name, name ); - } - return name; - } -}; - -jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { - var getter = attrHandle[ name ] || jQuery.find.attr; - - attrHandle[ name ] = function( elem, name, isXML ) { - var ret, handle, - lowercaseName = name.toLowerCase(); - - if ( !isXML ) { - - // Avoid an infinite loop by temporarily removing this function from the getter - handle = attrHandle[ lowercaseName ]; - attrHandle[ lowercaseName ] = ret; - ret = getter( elem, name, isXML ) != null ? - lowercaseName : - null; - attrHandle[ lowercaseName ] = handle; - } - return ret; - }; -} ); - - - - -var rfocusable = /^(?:input|select|textarea|button)$/i, - rclickable = /^(?:a|area)$/i; - -jQuery.fn.extend( { - prop: function( name, value ) { - return access( this, jQuery.prop, name, value, arguments.length > 1 ); - }, - - removeProp: function( name ) { - return this.each( function() { - delete this[ jQuery.propFix[ name ] || name ]; - } ); - } -} ); - -jQuery.extend( { - prop: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set properties on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - - // Fix name and attach hooks - name = jQuery.propFix[ name ] || name; - hooks = jQuery.propHooks[ name ]; - } - - if ( value !== undefined ) { - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - return ( elem[ name ] = value ); - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - return elem[ name ]; - }, - - propHooks: { - tabIndex: { - get: function( elem ) { - - // Support: IE <=9 - 11 only - // elem.tabIndex doesn't always return the - // correct value when it hasn't been explicitly set - // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - // Use proper attribute retrieval(#12072) - var tabindex = jQuery.find.attr( elem, "tabindex" ); - - if ( tabindex ) { - return parseInt( tabindex, 10 ); - } - - if ( - rfocusable.test( elem.nodeName ) || - rclickable.test( elem.nodeName ) && - elem.href - ) { - return 0; - } - - return -1; - } - } - }, - - propFix: { - "for": "htmlFor", - "class": "className" - } -} ); - -// Support: IE <=11 only -// Accessing the selectedIndex property -// forces the browser to respect setting selected -// on the option -// The getter ensures a default option is selected -// when in an optgroup -// eslint rule "no-unused-expressions" is disabled for this code -// since it considers such accessions noop -if ( !support.optSelected ) { - jQuery.propHooks.selected = { - get: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent && parent.parentNode ) { - parent.parentNode.selectedIndex; - } - return null; - }, - set: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; - - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - } - }; -} - -jQuery.each( [ - "tabIndex", - "readOnly", - "maxLength", - "cellSpacing", - "cellPadding", - "rowSpan", - "colSpan", - "useMap", - "frameBorder", - "contentEditable" -], function() { - jQuery.propFix[ this.toLowerCase() ] = this; -} ); - - - - - // Strip and collapse whitespace according to HTML spec - // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace - function stripAndCollapse( value ) { - var tokens = value.match( rnothtmlwhite ) || []; - return tokens.join( " " ); - } - - -function getClass( elem ) { - return elem.getAttribute && elem.getAttribute( "class" ) || ""; -} - -function classesToArray( value ) { - if ( Array.isArray( value ) ) { - return value; - } - if ( typeof value === "string" ) { - return value.match( rnothtmlwhite ) || []; - } - return []; -} - -jQuery.fn.extend( { - addClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - classes = classesToArray( value ); - - if ( classes.length ) { - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - if ( cur.indexOf( " " + clazz + " " ) < 0 ) { - cur += clazz + " "; - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - removeClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - if ( !arguments.length ) { - return this.attr( "class", "" ); - } - - classes = classesToArray( value ); - - if ( classes.length ) { - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - - // This expression is here for better compressibility (see addClass) - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - - // Remove *all* instances - while ( cur.indexOf( " " + clazz + " " ) > -1 ) { - cur = cur.replace( " " + clazz + " ", " " ); - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - var type = typeof value, - isValidValue = type === "string" || Array.isArray( value ); - - if ( typeof stateVal === "boolean" && isValidValue ) { - return stateVal ? this.addClass( value ) : this.removeClass( value ); - } - - if ( isFunction( value ) ) { - return this.each( function( i ) { - jQuery( this ).toggleClass( - value.call( this, i, getClass( this ), stateVal ), - stateVal - ); - } ); - } - - return this.each( function() { - var className, i, self, classNames; - - if ( isValidValue ) { - - // Toggle individual class names - i = 0; - self = jQuery( this ); - classNames = classesToArray( value ); - - while ( ( className = classNames[ i++ ] ) ) { - - // Check each className given, space separated list - if ( self.hasClass( className ) ) { - self.removeClass( className ); - } else { - self.addClass( className ); - } - } - - // Toggle whole class name - } else if ( value === undefined || type === "boolean" ) { - className = getClass( this ); - if ( className ) { - - // Store className if set - dataPriv.set( this, "__className__", className ); - } - - // If the element has a class name or if we're passed `false`, - // then remove the whole classname (if there was one, the above saved it). - // Otherwise bring back whatever was previously saved (if anything), - // falling back to the empty string if nothing was stored. - if ( this.setAttribute ) { - this.setAttribute( "class", - className || value === false ? - "" : - dataPriv.get( this, "__className__" ) || "" - ); - } - } - } ); - }, - - hasClass: function( selector ) { - var className, elem, - i = 0; - - className = " " + selector + " "; - while ( ( elem = this[ i++ ] ) ) { - if ( elem.nodeType === 1 && - ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { - return true; - } - } - - return false; - } -} ); - - - - -var rreturn = /\r/g; - -jQuery.fn.extend( { - val: function( value ) { - var hooks, ret, valueIsFunction, - elem = this[ 0 ]; - - if ( !arguments.length ) { - if ( elem ) { - hooks = jQuery.valHooks[ elem.type ] || - jQuery.valHooks[ elem.nodeName.toLowerCase() ]; - - if ( hooks && - "get" in hooks && - ( ret = hooks.get( elem, "value" ) ) !== undefined - ) { - return ret; - } - - ret = elem.value; - - // Handle most common string cases - if ( typeof ret === "string" ) { - return ret.replace( rreturn, "" ); - } - - // Handle cases where value is null/undef or number - return ret == null ? "" : ret; - } - - return; - } - - valueIsFunction = isFunction( value ); - - return this.each( function( i ) { - var val; - - if ( this.nodeType !== 1 ) { - return; - } - - if ( valueIsFunction ) { - val = value.call( this, i, jQuery( this ).val() ); - } else { - val = value; - } - - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - - } else if ( typeof val === "number" ) { - val += ""; - - } else if ( Array.isArray( val ) ) { - val = jQuery.map( val, function( value ) { - return value == null ? "" : value + ""; - } ); - } - - hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; - - // If set returns undefined, fall back to normal setting - if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { - this.value = val; - } - } ); - } -} ); - -jQuery.extend( { - valHooks: { - option: { - get: function( elem ) { - - var val = jQuery.find.attr( elem, "value" ); - return val != null ? - val : - - // Support: IE <=10 - 11 only - // option.text throws exceptions (#14686, #14858) - // Strip and collapse whitespace - // https://html.spec.whatwg.org/#strip-and-collapse-whitespace - stripAndCollapse( jQuery.text( elem ) ); - } - }, - select: { - get: function( elem ) { - var value, option, i, - options = elem.options, - index = elem.selectedIndex, - one = elem.type === "select-one", - values = one ? null : [], - max = one ? index + 1 : options.length; - - if ( index < 0 ) { - i = max; - - } else { - i = one ? index : 0; - } - - // Loop through all the selected options - for ( ; i < max; i++ ) { - option = options[ i ]; - - // Support: IE <=9 only - // IE8-9 doesn't update selected after form reset (#2551) - if ( ( option.selected || i === index ) && - - // Don't return options that are disabled or in a disabled optgroup - !option.disabled && - ( !option.parentNode.disabled || - !nodeName( option.parentNode, "optgroup" ) ) ) { - - // Get the specific value for the option - value = jQuery( option ).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - }, - - set: function( elem, value ) { - var optionSet, option, - options = elem.options, - values = jQuery.makeArray( value ), - i = options.length; - - while ( i-- ) { - option = options[ i ]; - - /* eslint-disable no-cond-assign */ - - if ( option.selected = - jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 - ) { - optionSet = true; - } - - /* eslint-enable no-cond-assign */ - } - - // Force browsers to behave consistently when non-matching value is set - if ( !optionSet ) { - elem.selectedIndex = -1; - } - return values; - } - } - } -} ); - -// Radios and checkboxes getter/setter -jQuery.each( [ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = { - set: function( elem, value ) { - if ( Array.isArray( value ) ) { - return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); - } - } - }; - if ( !support.checkOn ) { - jQuery.valHooks[ this ].get = function( elem ) { - return elem.getAttribute( "value" ) === null ? "on" : elem.value; - }; - } -} ); - - - - -// Return jQuery for attributes-only inclusion - - -support.focusin = "onfocusin" in window; - - -var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - stopPropagationCallback = function( e ) { - e.stopPropagation(); - }; - -jQuery.extend( jQuery.event, { - - trigger: function( event, data, elem, onlyHandlers ) { - - var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, - eventPath = [ elem || document ], - type = hasOwn.call( event, "type" ) ? event.type : event, - namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; - - cur = lastElement = tmp = elem = elem || document; - - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - if ( type.indexOf( "." ) > -1 ) { - - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split( "." ); - type = namespaces.shift(); - namespaces.sort(); - } - ontype = type.indexOf( ":" ) < 0 && "on" + type; - - // Caller can pass in a jQuery.Event object, Object, or just an event type string - event = event[ jQuery.expando ] ? - event : - new jQuery.Event( type, typeof event === "object" && event ); - - // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) - event.isTrigger = onlyHandlers ? 2 : 3; - event.namespace = namespaces.join( "." ); - event.rnamespace = event.namespace ? - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : - null; - - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data == null ? - [ event ] : - jQuery.makeArray( data, [ event ] ); - - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - if ( !rfocusMorph.test( bubbleType + type ) ) { - cur = cur.parentNode; - } - for ( ; cur; cur = cur.parentNode ) { - eventPath.push( cur ); - tmp = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( tmp === ( elem.ownerDocument || document ) ) { - eventPath.push( tmp.defaultView || tmp.parentWindow || window ); - } - } - - // Fire handlers on the event path - i = 0; - while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { - lastElement = cur; - event.type = i > 1 ? - bubbleType : - special.bindType || type; - - // jQuery handler - handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] && - dataPriv.get( cur, "handle" ); - if ( handle ) { - handle.apply( cur, data ); - } - - // Native handler - handle = ontype && cur[ ontype ]; - if ( handle && handle.apply && acceptData( cur ) ) { - event.result = handle.apply( cur, data ); - if ( event.result === false ) { - event.preventDefault(); - } - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { - - if ( ( !special._default || - special._default.apply( eventPath.pop(), data ) === false ) && - acceptData( elem ) ) { - - // Call a native DOM method on the target with the same name as the event. - // Don't do default actions on window, that's where global variables be (#6170) - if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { - - // Don't re-trigger an onFOO event when we call its FOO() method - tmp = elem[ ontype ]; - - if ( tmp ) { - elem[ ontype ] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - - if ( event.isPropagationStopped() ) { - lastElement.addEventListener( type, stopPropagationCallback ); - } - - elem[ type ](); - - if ( event.isPropagationStopped() ) { - lastElement.removeEventListener( type, stopPropagationCallback ); - } - - jQuery.event.triggered = undefined; - - if ( tmp ) { - elem[ ontype ] = tmp; - } - } - } - } - - return event.result; - }, - - // Piggyback on a donor event to simulate a different one - // Used only for `focus(in | out)` events - simulate: function( type, elem, event ) { - var e = jQuery.extend( - new jQuery.Event(), - event, - { - type: type, - isSimulated: true - } - ); - - jQuery.event.trigger( e, null, elem ); - } - -} ); - -jQuery.fn.extend( { - - trigger: function( type, data ) { - return this.each( function() { - jQuery.event.trigger( type, data, this ); - } ); - }, - triggerHandler: function( type, data ) { - var elem = this[ 0 ]; - if ( elem ) { - return jQuery.event.trigger( type, data, elem, true ); - } - } -} ); - - -// Support: Firefox <=44 -// Firefox doesn't have focus(in | out) events -// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 -// -// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 -// focus(in | out) events fire after focus & blur events, -// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order -// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 -if ( !support.focusin ) { - jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { - - // Attach a single capturing handler on the document while someone wants focusin/focusout - var handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); - }; - - jQuery.event.special[ fix ] = { - setup: function() { - var doc = this.ownerDocument || this, - attaches = dataPriv.access( doc, fix ); - - if ( !attaches ) { - doc.addEventListener( orig, handler, true ); - } - dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); - }, - teardown: function() { - var doc = this.ownerDocument || this, - attaches = dataPriv.access( doc, fix ) - 1; - - if ( !attaches ) { - doc.removeEventListener( orig, handler, true ); - dataPriv.remove( doc, fix ); - - } else { - dataPriv.access( doc, fix, attaches ); - } - } - }; - } ); -} -var location = window.location; - -var nonce = Date.now(); - -var rquery = ( /\?/ ); - - - -// Cross-browser xml parsing -jQuery.parseXML = function( data ) { - var xml; - if ( !data || typeof data !== "string" ) { - return null; - } - - // Support: IE 9 - 11 only - // IE throws on parseFromString with invalid input. - try { - xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); - } catch ( e ) { - xml = undefined; - } - - if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { - jQuery.error( "Invalid XML: " + data ); - } - return xml; -}; - - -var - rbracket = /\[\]$/, - rCRLF = /\r?\n/g, - rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, - rsubmittable = /^(?:input|select|textarea|keygen)/i; - -function buildParams( prefix, obj, traditional, add ) { - var name; - - if ( Array.isArray( obj ) ) { - - // Serialize array item. - jQuery.each( obj, function( i, v ) { - if ( traditional || rbracket.test( prefix ) ) { - - // Treat each array item as a scalar. - add( prefix, v ); - - } else { - - // Item is non-scalar (array or object), encode its numeric index. - buildParams( - prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", - v, - traditional, - add - ); - } - } ); - - } else if ( !traditional && toType( obj ) === "object" ) { - - // Serialize object item. - for ( name in obj ) { - buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); - } - - } else { - - // Serialize scalar item. - add( prefix, obj ); - } -} - -// Serialize an array of form elements or a set of -// key/values into a query string -jQuery.param = function( a, traditional ) { - var prefix, - s = [], - add = function( key, valueOrFunction ) { - - // If value is a function, invoke it and use its return value - var value = isFunction( valueOrFunction ) ? - valueOrFunction() : - valueOrFunction; - - s[ s.length ] = encodeURIComponent( key ) + "=" + - encodeURIComponent( value == null ? "" : value ); - }; - - // If an array was passed in, assume that it is an array of form elements. - if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { - - // Serialize the form elements - jQuery.each( a, function() { - add( this.name, this.value ); - } ); - - } else { - - // If traditional, encode the "old" way (the way 1.3.2 or older - // did it), otherwise encode params recursively. - for ( prefix in a ) { - buildParams( prefix, a[ prefix ], traditional, add ); - } - } - - // Return the resulting serialization - return s.join( "&" ); -}; - -jQuery.fn.extend( { - serialize: function() { - return jQuery.param( this.serializeArray() ); - }, - serializeArray: function() { - return this.map( function() { - - // Can add propHook for "elements" to filter or add form elements - var elements = jQuery.prop( this, "elements" ); - return elements ? jQuery.makeArray( elements ) : this; - } ) - .filter( function() { - var type = this.type; - - // Use .is( ":disabled" ) so that fieldset[disabled] works - return this.name && !jQuery( this ).is( ":disabled" ) && - rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && - ( this.checked || !rcheckableType.test( type ) ); - } ) - .map( function( i, elem ) { - var val = jQuery( this ).val(); - - if ( val == null ) { - return null; - } - - if ( Array.isArray( val ) ) { - return jQuery.map( val, function( val ) { - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ); - } - - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ).get(); - } -} ); - - -var - r20 = /%20/g, - rhash = /#.*$/, - rantiCache = /([?&])_=[^&]*/, - rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, - - // #7653, #8125, #8152: local protocol detection - rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, - rnoContent = /^(?:GET|HEAD)$/, - rprotocol = /^\/\//, - - /* Prefilters - * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) - * 2) These are called: - * - BEFORE asking for a transport - * - AFTER param serialization (s.data is a string if s.processData is true) - * 3) key is the dataType - * 4) the catchall symbol "*" can be used - * 5) execution will start with transport dataType and THEN continue down to "*" if needed - */ - prefilters = {}, - - /* Transports bindings - * 1) key is the dataType - * 2) the catchall symbol "*" can be used - * 3) selection will start with transport dataType and THEN go to "*" if needed - */ - transports = {}, - - // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression - allTypes = "*/".concat( "*" ), - - // Anchor tag for parsing the document origin - originAnchor = document.createElement( "a" ); - originAnchor.href = location.href; - -// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport -function addToPrefiltersOrTransports( structure ) { - - // dataTypeExpression is optional and defaults to "*" - return function( dataTypeExpression, func ) { - - if ( typeof dataTypeExpression !== "string" ) { - func = dataTypeExpression; - dataTypeExpression = "*"; - } - - var dataType, - i = 0, - dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; - - if ( isFunction( func ) ) { - - // For each dataType in the dataTypeExpression - while ( ( dataType = dataTypes[ i++ ] ) ) { - - // Prepend if requested - if ( dataType[ 0 ] === "+" ) { - dataType = dataType.slice( 1 ) || "*"; - ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); - - // Otherwise append - } else { - ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); - } - } - } - }; -} - -// Base inspection function for prefilters and transports -function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { - - var inspected = {}, - seekingTransport = ( structure === transports ); - - function inspect( dataType ) { - var selected; - inspected[ dataType ] = true; - jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { - var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); - if ( typeof dataTypeOrTransport === "string" && - !seekingTransport && !inspected[ dataTypeOrTransport ] ) { - - options.dataTypes.unshift( dataTypeOrTransport ); - inspect( dataTypeOrTransport ); - return false; - } else if ( seekingTransport ) { - return !( selected = dataTypeOrTransport ); - } - } ); - return selected; - } - - return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); -} - -// A special extend for ajax options -// that takes "flat" options (not to be deep extended) -// Fixes #9887 -function ajaxExtend( target, src ) { - var key, deep, - flatOptions = jQuery.ajaxSettings.flatOptions || {}; - - for ( key in src ) { - if ( src[ key ] !== undefined ) { - ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; - } - } - if ( deep ) { - jQuery.extend( true, target, deep ); - } - - return target; -} - -/* Handles responses to an ajax request: - * - finds the right dataType (mediates between content-type and expected dataType) - * - returns the corresponding response - */ -function ajaxHandleResponses( s, jqXHR, responses ) { - - var ct, type, finalDataType, firstDataType, - contents = s.contents, - dataTypes = s.dataTypes; - - // Remove auto dataType and get content-type in the process - while ( dataTypes[ 0 ] === "*" ) { - dataTypes.shift(); - if ( ct === undefined ) { - ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); - } - } - - // Check if we're dealing with a known content-type - if ( ct ) { - for ( type in contents ) { - if ( contents[ type ] && contents[ type ].test( ct ) ) { - dataTypes.unshift( type ); - break; - } - } - } - - // Check to see if we have a response for the expected dataType - if ( dataTypes[ 0 ] in responses ) { - finalDataType = dataTypes[ 0 ]; - } else { - - // Try convertible dataTypes - for ( type in responses ) { - if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { - finalDataType = type; - break; - } - if ( !firstDataType ) { - firstDataType = type; - } - } - - // Or just use first one - finalDataType = finalDataType || firstDataType; - } - - // If we found a dataType - // We add the dataType to the list if needed - // and return the corresponding response - if ( finalDataType ) { - if ( finalDataType !== dataTypes[ 0 ] ) { - dataTypes.unshift( finalDataType ); - } - return responses[ finalDataType ]; - } -} - -/* Chain conversions given the request and the original response - * Also sets the responseXXX fields on the jqXHR instance - */ -function ajaxConvert( s, response, jqXHR, isSuccess ) { - var conv2, current, conv, tmp, prev, - converters = {}, - - // Work with a copy of dataTypes in case we need to modify it for conversion - dataTypes = s.dataTypes.slice(); - - // Create converters map with lowercased keys - if ( dataTypes[ 1 ] ) { - for ( conv in s.converters ) { - converters[ conv.toLowerCase() ] = s.converters[ conv ]; - } - } - - current = dataTypes.shift(); - - // Convert to each sequential dataType - while ( current ) { - - if ( s.responseFields[ current ] ) { - jqXHR[ s.responseFields[ current ] ] = response; - } - - // Apply the dataFilter if provided - if ( !prev && isSuccess && s.dataFilter ) { - response = s.dataFilter( response, s.dataType ); - } - - prev = current; - current = dataTypes.shift(); - - if ( current ) { - - // There's only work to do if current dataType is non-auto - if ( current === "*" ) { - - current = prev; - - // Convert response if prev dataType is non-auto and differs from current - } else if ( prev !== "*" && prev !== current ) { - - // Seek a direct converter - conv = converters[ prev + " " + current ] || converters[ "* " + current ]; - - // If none found, seek a pair - if ( !conv ) { - for ( conv2 in converters ) { - - // If conv2 outputs current - tmp = conv2.split( " " ); - if ( tmp[ 1 ] === current ) { - - // If prev can be converted to accepted input - conv = converters[ prev + " " + tmp[ 0 ] ] || - converters[ "* " + tmp[ 0 ] ]; - if ( conv ) { - - // Condense equivalence converters - if ( conv === true ) { - conv = converters[ conv2 ]; - - // Otherwise, insert the intermediate dataType - } else if ( converters[ conv2 ] !== true ) { - current = tmp[ 0 ]; - dataTypes.unshift( tmp[ 1 ] ); - } - break; - } - } - } - } - - // Apply converter (if not an equivalence) - if ( conv !== true ) { - - // Unless errors are allowed to bubble, catch and return them - if ( conv && s.throws ) { - response = conv( response ); - } else { - try { - response = conv( response ); - } catch ( e ) { - return { - state: "parsererror", - error: conv ? e : "No conversion from " + prev + " to " + current - }; - } - } - } - } - } - } - - return { state: "success", data: response }; -} - -jQuery.extend( { - - // Counter for holding the number of active queries - active: 0, - - // Last-Modified header cache for next request - lastModified: {}, - etag: {}, - - ajaxSettings: { - url: location.href, - type: "GET", - isLocal: rlocalProtocol.test( location.protocol ), - global: true, - processData: true, - async: true, - contentType: "application/x-www-form-urlencoded; charset=UTF-8", - - /* - timeout: 0, - data: null, - dataType: null, - username: null, - password: null, - cache: null, - throws: false, - traditional: false, - headers: {}, - */ - - accepts: { - "*": allTypes, - text: "text/plain", - html: "text/html", - xml: "application/xml, text/xml", - json: "application/json, text/javascript" - }, - - contents: { - xml: /\bxml\b/, - html: /\bhtml/, - json: /\bjson\b/ - }, - - responseFields: { - xml: "responseXML", - text: "responseText", - json: "responseJSON" - }, - - // Data converters - // Keys separate source (or catchall "*") and destination types with a single space - converters: { - - // Convert anything to text - "* text": String, - - // Text to html (true = no transformation) - "text html": true, - - // Evaluate text as a json expression - "text json": JSON.parse, - - // Parse text as xml - "text xml": jQuery.parseXML - }, - - // For options that shouldn't be deep extended: - // you can add your own custom options here if - // and when you create one that shouldn't be - // deep extended (see ajaxExtend) - flatOptions: { - url: true, - context: true - } - }, - - // Creates a full fledged settings object into target - // with both ajaxSettings and settings fields. - // If target is omitted, writes into ajaxSettings. - ajaxSetup: function( target, settings ) { - return settings ? - - // Building a settings object - ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : - - // Extending ajaxSettings - ajaxExtend( jQuery.ajaxSettings, target ); - }, - - ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), - ajaxTransport: addToPrefiltersOrTransports( transports ), - - // Main method - ajax: function( url, options ) { - - // If url is an object, simulate pre-1.5 signature - if ( typeof url === "object" ) { - options = url; - url = undefined; - } - - // Force options to be an object - options = options || {}; - - var transport, - - // URL without anti-cache param - cacheURL, - - // Response headers - responseHeadersString, - responseHeaders, - - // timeout handle - timeoutTimer, - - // Url cleanup var - urlAnchor, - - // Request state (becomes false upon send and true upon completion) - completed, - - // To know if global events are to be dispatched - fireGlobals, - - // Loop variable - i, - - // uncached part of the url - uncached, - - // Create the final options object - s = jQuery.ajaxSetup( {}, options ), - - // Callbacks context - callbackContext = s.context || s, - - // Context for global events is callbackContext if it is a DOM node or jQuery collection - globalEventContext = s.context && - ( callbackContext.nodeType || callbackContext.jquery ) ? - jQuery( callbackContext ) : - jQuery.event, - - // Deferreds - deferred = jQuery.Deferred(), - completeDeferred = jQuery.Callbacks( "once memory" ), - - // Status-dependent callbacks - statusCode = s.statusCode || {}, - - // Headers (they are sent all at once) - requestHeaders = {}, - requestHeadersNames = {}, - - // Default abort message - strAbort = "canceled", - - // Fake xhr - jqXHR = { - readyState: 0, - - // Builds headers hashtable if needed - getResponseHeader: function( key ) { - var match; - if ( completed ) { - if ( !responseHeaders ) { - responseHeaders = {}; - while ( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; - } - } - match = responseHeaders[ key.toLowerCase() ]; - } - return match == null ? null : match; - }, - - // Raw string - getAllResponseHeaders: function() { - return completed ? responseHeadersString : null; - }, - - // Caches the header - setRequestHeader: function( name, value ) { - if ( completed == null ) { - name = requestHeadersNames[ name.toLowerCase() ] = - requestHeadersNames[ name.toLowerCase() ] || name; - requestHeaders[ name ] = value; - } - return this; - }, - - // Overrides response content-type header - overrideMimeType: function( type ) { - if ( completed == null ) { - s.mimeType = type; - } - return this; - }, - - // Status-dependent callbacks - statusCode: function( map ) { - var code; - if ( map ) { - if ( completed ) { - - // Execute the appropriate callbacks - jqXHR.always( map[ jqXHR.status ] ); - } else { - - // Lazy-add the new callbacks in a way that preserves old ones - for ( code in map ) { - statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; - } - } - } - return this; - }, - - // Cancel the request - abort: function( statusText ) { - var finalText = statusText || strAbort; - if ( transport ) { - transport.abort( finalText ); - } - done( 0, finalText ); - return this; - } - }; - - // Attach deferreds - deferred.promise( jqXHR ); - - // Add protocol if not provided (prefilters might expect it) - // Handle falsy url in the settings object (#10093: consistency with old signature) - // We also use the url parameter if available - s.url = ( ( url || s.url || location.href ) + "" ) - .replace( rprotocol, location.protocol + "//" ); - - // Alias method option to type as per ticket #12004 - s.type = options.method || options.type || s.method || s.type; - - // Extract dataTypes list - s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; - - // A cross-domain request is in order when the origin doesn't match the current origin. - if ( s.crossDomain == null ) { - urlAnchor = document.createElement( "a" ); - - // Support: IE <=8 - 11, Edge 12 - 15 - // IE throws exception on accessing the href property if url is malformed, - // e.g. http://example.com:80x/ - try { - urlAnchor.href = s.url; - - // Support: IE <=8 - 11 only - // Anchor's host property isn't correctly set when s.url is relative - urlAnchor.href = urlAnchor.href; - s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== - urlAnchor.protocol + "//" + urlAnchor.host; - } catch ( e ) { - - // If there is an error parsing the URL, assume it is crossDomain, - // it can be rejected by the transport if it is invalid - s.crossDomain = true; - } - } - - // Convert data if not already a string - if ( s.data && s.processData && typeof s.data !== "string" ) { - s.data = jQuery.param( s.data, s.traditional ); - } - - // Apply prefilters - inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); - - // If request was aborted inside a prefilter, stop there - if ( completed ) { - return jqXHR; - } - - // We can fire global events as of now if asked to - // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) - fireGlobals = jQuery.event && s.global; - - // Watch for a new set of requests - if ( fireGlobals && jQuery.active++ === 0 ) { - jQuery.event.trigger( "ajaxStart" ); - } - - // Uppercase the type - s.type = s.type.toUpperCase(); - - // Determine if request has content - s.hasContent = !rnoContent.test( s.type ); - - // Save the URL in case we're toying with the If-Modified-Since - // and/or If-None-Match header later on - // Remove hash to simplify url manipulation - cacheURL = s.url.replace( rhash, "" ); - - // More options handling for requests with no content - if ( !s.hasContent ) { - - // Remember the hash so we can put it back - uncached = s.url.slice( cacheURL.length ); - - // If data is available and should be processed, append data to url - if ( s.data && ( s.processData || typeof s.data === "string" ) ) { - cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; - - // #9682: remove data so that it's not used in an eventual retry - delete s.data; - } - - // Add or update anti-cache param if needed - if ( s.cache === false ) { - cacheURL = cacheURL.replace( rantiCache, "$1" ); - uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached; - } - - // Put hash and anti-cache on the URL that will be requested (gh-1732) - s.url = cacheURL + uncached; - - // Change '%20' to '+' if this is encoded form body content (gh-2658) - } else if ( s.data && s.processData && - ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { - s.data = s.data.replace( r20, "+" ); - } - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - if ( jQuery.lastModified[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); - } - if ( jQuery.etag[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); - } - } - - // Set the correct header, if data is being sent - if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { - jqXHR.setRequestHeader( "Content-Type", s.contentType ); - } - - // Set the Accepts header for the server, depending on the dataType - jqXHR.setRequestHeader( - "Accept", - s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? - s.accepts[ s.dataTypes[ 0 ] ] + - ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : - s.accepts[ "*" ] - ); - - // Check for headers option - for ( i in s.headers ) { - jqXHR.setRequestHeader( i, s.headers[ i ] ); - } - - // Allow custom headers/mimetypes and early abort - if ( s.beforeSend && - ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { - - // Abort if not done already and return - return jqXHR.abort(); - } - - // Aborting is no longer a cancellation - strAbort = "abort"; - - // Install callbacks on deferreds - completeDeferred.add( s.complete ); - jqXHR.done( s.success ); - jqXHR.fail( s.error ); - - // Get transport - transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); - - // If no transport, we auto-abort - if ( !transport ) { - done( -1, "No Transport" ); - } else { - jqXHR.readyState = 1; - - // Send global event - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); - } - - // If request was aborted inside ajaxSend, stop there - if ( completed ) { - return jqXHR; - } - - // Timeout - if ( s.async && s.timeout > 0 ) { - timeoutTimer = window.setTimeout( function() { - jqXHR.abort( "timeout" ); - }, s.timeout ); - } - - try { - completed = false; - transport.send( requestHeaders, done ); - } catch ( e ) { - - // Rethrow post-completion exceptions - if ( completed ) { - throw e; - } - - // Propagate others as results - done( -1, e ); - } - } - - // Callback for when everything is done - function done( status, nativeStatusText, responses, headers ) { - var isSuccess, success, error, response, modified, - statusText = nativeStatusText; - - // Ignore repeat invocations - if ( completed ) { - return; - } - - completed = true; - - // Clear timeout if it exists - if ( timeoutTimer ) { - window.clearTimeout( timeoutTimer ); - } - - // Dereference transport for early garbage collection - // (no matter how long the jqXHR object will be used) - transport = undefined; - - // Cache response headers - responseHeadersString = headers || ""; - - // Set readyState - jqXHR.readyState = status > 0 ? 4 : 0; - - // Determine if successful - isSuccess = status >= 200 && status < 300 || status === 304; - - // Get response data - if ( responses ) { - response = ajaxHandleResponses( s, jqXHR, responses ); - } - - // Convert no matter what (that way responseXXX fields are always set) - response = ajaxConvert( s, response, jqXHR, isSuccess ); - - // If successful, handle type chaining - if ( isSuccess ) { - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - modified = jqXHR.getResponseHeader( "Last-Modified" ); - if ( modified ) { - jQuery.lastModified[ cacheURL ] = modified; - } - modified = jqXHR.getResponseHeader( "etag" ); - if ( modified ) { - jQuery.etag[ cacheURL ] = modified; - } - } - - // if no content - if ( status === 204 || s.type === "HEAD" ) { - statusText = "nocontent"; - - // if not modified - } else if ( status === 304 ) { - statusText = "notmodified"; - - // If we have data, let's convert it - } else { - statusText = response.state; - success = response.data; - error = response.error; - isSuccess = !error; - } - } else { - - // Extract error from statusText and normalize for non-aborts - error = statusText; - if ( status || !statusText ) { - statusText = "error"; - if ( status < 0 ) { - status = 0; - } - } - } - - // Set data for the fake xhr object - jqXHR.status = status; - jqXHR.statusText = ( nativeStatusText || statusText ) + ""; - - // Success/Error - if ( isSuccess ) { - deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); - } else { - deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); - } - - // Status-dependent callbacks - jqXHR.statusCode( statusCode ); - statusCode = undefined; - - if ( fireGlobals ) { - globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", - [ jqXHR, s, isSuccess ? success : error ] ); - } - - // Complete - completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); - - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); - - // Handle the global AJAX counter - if ( !( --jQuery.active ) ) { - jQuery.event.trigger( "ajaxStop" ); - } - } - } - - return jqXHR; - }, - - getJSON: function( url, data, callback ) { - return jQuery.get( url, data, callback, "json" ); - }, - - getScript: function( url, callback ) { - return jQuery.get( url, undefined, callback, "script" ); - } -} ); - -jQuery.each( [ "get", "post" ], function( i, method ) { - jQuery[ method ] = function( url, data, callback, type ) { - - // Shift arguments if data argument was omitted - if ( isFunction( data ) ) { - type = type || callback; - callback = data; - data = undefined; - } - - // The url can be an options object (which then must have .url) - return jQuery.ajax( jQuery.extend( { - url: url, - type: method, - dataType: type, - data: data, - success: callback - }, jQuery.isPlainObject( url ) && url ) ); - }; -} ); - - -jQuery._evalUrl = function( url ) { - return jQuery.ajax( { - url: url, - - // Make this explicit, since user can override this through ajaxSetup (#11264) - type: "GET", - dataType: "script", - cache: true, - async: false, - global: false, - "throws": true - } ); -}; - - -jQuery.fn.extend( { - wrapAll: function( html ) { - var wrap; - - if ( this[ 0 ] ) { - if ( isFunction( html ) ) { - html = html.call( this[ 0 ] ); - } - - // The elements to wrap the target around - wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); - - if ( this[ 0 ].parentNode ) { - wrap.insertBefore( this[ 0 ] ); - } - - wrap.map( function() { - var elem = this; - - while ( elem.firstElementChild ) { - elem = elem.firstElementChild; - } - - return elem; - } ).append( this ); - } - - return this; - }, - - wrapInner: function( html ) { - if ( isFunction( html ) ) { - return this.each( function( i ) { - jQuery( this ).wrapInner( html.call( this, i ) ); - } ); - } - - return this.each( function() { - var self = jQuery( this ), - contents = self.contents(); - - if ( contents.length ) { - contents.wrapAll( html ); - - } else { - self.append( html ); - } - } ); - }, - - wrap: function( html ) { - var htmlIsFunction = isFunction( html ); - - return this.each( function( i ) { - jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); - } ); - }, - - unwrap: function( selector ) { - this.parent( selector ).not( "body" ).each( function() { - jQuery( this ).replaceWith( this.childNodes ); - } ); - return this; - } -} ); - - -jQuery.expr.pseudos.hidden = function( elem ) { - return !jQuery.expr.pseudos.visible( elem ); -}; -jQuery.expr.pseudos.visible = function( elem ) { - return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); -}; - - - - -jQuery.ajaxSettings.xhr = function() { - try { - return new window.XMLHttpRequest(); - } catch ( e ) {} -}; - -var xhrSuccessStatus = { - - // File protocol always yields status code 0, assume 200 - 0: 200, - - // Support: IE <=9 only - // #1450: sometimes IE returns 1223 when it should be 204 - 1223: 204 - }, - xhrSupported = jQuery.ajaxSettings.xhr(); - -support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); -support.ajax = xhrSupported = !!xhrSupported; - -jQuery.ajaxTransport( function( options ) { - var callback, errorCallback; - - // Cross domain only allowed if supported through XMLHttpRequest - if ( support.cors || xhrSupported && !options.crossDomain ) { - return { - send: function( headers, complete ) { - var i, - xhr = options.xhr(); - - xhr.open( - options.type, - options.url, - options.async, - options.username, - options.password - ); - - // Apply custom fields if provided - if ( options.xhrFields ) { - for ( i in options.xhrFields ) { - xhr[ i ] = options.xhrFields[ i ]; - } - } - - // Override mime type if needed - if ( options.mimeType && xhr.overrideMimeType ) { - xhr.overrideMimeType( options.mimeType ); - } - - // X-Requested-With header - // For cross-domain requests, seeing as conditions for a preflight are - // akin to a jigsaw puzzle, we simply never set it to be sure. - // (it can always be set on a per-request basis or even using ajaxSetup) - // For same-domain requests, won't change header if already provided. - if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { - headers[ "X-Requested-With" ] = "XMLHttpRequest"; - } - - // Set headers - for ( i in headers ) { - xhr.setRequestHeader( i, headers[ i ] ); - } - - // Callback - callback = function( type ) { - return function() { - if ( callback ) { - callback = errorCallback = xhr.onload = - xhr.onerror = xhr.onabort = xhr.ontimeout = - xhr.onreadystatechange = null; - - if ( type === "abort" ) { - xhr.abort(); - } else if ( type === "error" ) { - - // Support: IE <=9 only - // On a manual native abort, IE9 throws - // errors on any property access that is not readyState - if ( typeof xhr.status !== "number" ) { - complete( 0, "error" ); - } else { - complete( - - // File: protocol always yields status 0; see #8605, #14207 - xhr.status, - xhr.statusText - ); - } - } else { - complete( - xhrSuccessStatus[ xhr.status ] || xhr.status, - xhr.statusText, - - // Support: IE <=9 only - // IE9 has no XHR2 but throws on binary (trac-11426) - // For XHR2 non-text, let the caller handle it (gh-2498) - ( xhr.responseType || "text" ) !== "text" || - typeof xhr.responseText !== "string" ? - { binary: xhr.response } : - { text: xhr.responseText }, - xhr.getAllResponseHeaders() - ); - } - } - }; - }; - - // Listen to events - xhr.onload = callback(); - errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); - - // Support: IE 9 only - // Use onreadystatechange to replace onabort - // to handle uncaught aborts - if ( xhr.onabort !== undefined ) { - xhr.onabort = errorCallback; - } else { - xhr.onreadystatechange = function() { - - // Check readyState before timeout as it changes - if ( xhr.readyState === 4 ) { - - // Allow onerror to be called first, - // but that will not handle a native abort - // Also, save errorCallback to a variable - // as xhr.onerror cannot be accessed - window.setTimeout( function() { - if ( callback ) { - errorCallback(); - } - } ); - } - }; - } - - // Create the abort callback - callback = callback( "abort" ); - - try { - - // Do send the request (this may raise an exception) - xhr.send( options.hasContent && options.data || null ); - } catch ( e ) { - - // #14683: Only rethrow if this hasn't been notified as an error yet - if ( callback ) { - throw e; - } - } - }, - - abort: function() { - if ( callback ) { - callback(); - } - } - }; - } -} ); - - - - -// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) -jQuery.ajaxPrefilter( function( s ) { - if ( s.crossDomain ) { - s.contents.script = false; - } -} ); - -// Install script dataType -jQuery.ajaxSetup( { - accepts: { - script: "text/javascript, application/javascript, " + - "application/ecmascript, application/x-ecmascript" - }, - contents: { - script: /\b(?:java|ecma)script\b/ - }, - converters: { - "text script": function( text ) { - jQuery.globalEval( text ); - return text; - } - } -} ); - -// Handle cache's special case and crossDomain -jQuery.ajaxPrefilter( "script", function( s ) { - if ( s.cache === undefined ) { - s.cache = false; - } - if ( s.crossDomain ) { - s.type = "GET"; - } -} ); - -// Bind script tag hack transport -jQuery.ajaxTransport( "script", function( s ) { - - // This transport only deals with cross domain requests - if ( s.crossDomain ) { - var script, callback; - return { - send: function( _, complete ) { - script = jQuery( " - -``` - -```html - - -``` - -### Usage - -Wrap your items (`div`, `a`, `img`, `span`, `li` etc.) with a container element (`div`, `ul` etc.). Only the class `owl-carousel` is mandatory to apply proper styles: - -```html - -``` -**NOTE:** The `owl-theme` class is optional, but without it, you will need to style navigation features on your own. - - -Call the [plugin](https://learn.jquery.com/plugins/) function and your carousel is ready. - -```javascript -$(document).ready(function(){ - $('.owl-carousel').owlCarousel(); -}); -``` - -## Documentation - -The documentation, included in this repo in the root directory, is built with [Assemble](http://assemble.io/) and publicly available at https://owlcarousel2.github.io/OwlCarousel2/. The documentation may also be run locally. - -## Building - -This package comes with [Grunt](http://gruntjs.com/) and [Bower](http://bower.io/). The following tasks are available: - - * `default` compiles the CSS and JS into `/dist` and builds the doc. - * `dist` compiles the CSS and JS into `/dist` only. - * `watch` watches source files and builds them automatically whenever you save. - * `test` runs [JSHint](http://www.jshint.com/) and [QUnit](http://qunitjs.com/) tests headlessly in [PhantomJS](http://phantomjs.org/). - -To define which plugins are build into the distribution just edit `/_config.json` to fit your needs. - -## Contributing - -Please read [CONTRIBUTING.md](CONTRIBUTING.md). - -## Roadmap - -Please make sure to check out our [Roadmap Discussion](https://github.com/OwlCarousel2/OwlCarousel2/issues/1756). - - -## License - -The code and the documentation are released under the [MIT License](LICENSE). diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/assets/ajax-loader.gif b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/assets/ajax-loader.gif deleted file mode 100644 index d3962f9699..0000000000 Binary files a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/assets/ajax-loader.gif and /dev/null differ diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/assets/owl.carousel.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/assets/owl.carousel.css deleted file mode 100644 index 40237bc65f..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/assets/owl.carousel.css +++ /dev/null @@ -1,186 +0,0 @@ -/** - * Owl Carousel v2.3.4 - * Copyright 2013-2018 David Deutsch - * Licensed under: SEE LICENSE IN https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE - */ -/* - * Owl Carousel - Core - */ -.owl-carousel { - display: none; - width: 100%; - -webkit-tap-highlight-color: transparent; - /* position relative and z-index fix webkit rendering fonts issue */ - position: relative; - z-index: 1; } - .owl-carousel .owl-stage { - position: relative; - -ms-touch-action: pan-Y; - touch-action: manipulation; - -moz-backface-visibility: hidden; - /* fix firefox animation glitch */ } - .owl-carousel .owl-stage:after { - content: "."; - display: block; - clear: both; - visibility: hidden; - line-height: 0; - height: 0; } - .owl-carousel .owl-stage-outer { - position: relative; - overflow: hidden; - /* fix for flashing background */ - -webkit-transform: translate3d(0px, 0px, 0px); } - .owl-carousel .owl-wrapper, - .owl-carousel .owl-item { - -webkit-backface-visibility: hidden; - -moz-backface-visibility: hidden; - -ms-backface-visibility: hidden; - -webkit-transform: translate3d(0, 0, 0); - -moz-transform: translate3d(0, 0, 0); - -ms-transform: translate3d(0, 0, 0); } - .owl-carousel .owl-item { - position: relative; - min-height: 1px; - float: left; - -webkit-backface-visibility: hidden; - -webkit-tap-highlight-color: transparent; - -webkit-touch-callout: none; } - .owl-carousel .owl-item img { - display: block; - width: 100%; } - .owl-carousel .owl-nav.disabled, - .owl-carousel .owl-dots.disabled { - display: none; } - .owl-carousel .owl-nav .owl-prev, - .owl-carousel .owl-nav .owl-next, - .owl-carousel .owl-dot { - cursor: pointer; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; } - .owl-carousel .owl-nav button.owl-prev, - .owl-carousel .owl-nav button.owl-next, - .owl-carousel button.owl-dot { - background: none; - color: inherit; - border: none; - padding: 0 !important; - font: inherit; } - .owl-carousel.owl-loaded { - display: block; } - .owl-carousel.owl-loading { - opacity: 0; - display: block; } - .owl-carousel.owl-hidden { - opacity: 0; } - .owl-carousel.owl-refresh .owl-item { - visibility: hidden; } - .owl-carousel.owl-drag .owl-item { - -ms-touch-action: pan-y; - touch-action: pan-y; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; } - .owl-carousel.owl-grab { - cursor: move; - cursor: grab; } - .owl-carousel.owl-rtl { - direction: rtl; } - .owl-carousel.owl-rtl .owl-item { - float: right; } - -/* No Js */ -.no-js .owl-carousel { - display: block; } - -/* - * Owl Carousel - Animate Plugin - */ -.owl-carousel .animated { - animation-duration: 1000ms; - animation-fill-mode: both; } - -.owl-carousel .owl-animated-in { - z-index: 0; } - -.owl-carousel .owl-animated-out { - z-index: 1; } - -.owl-carousel .fadeOut { - animation-name: fadeOut; } - -@keyframes fadeOut { - 0% { - opacity: 1; } - 100% { - opacity: 0; } } - -/* - * Owl Carousel - Auto Height Plugin - */ -.owl-height { - transition: height 500ms ease-in-out; } - -/* - * Owl Carousel - Lazy Load Plugin - */ -.owl-carousel .owl-item { - /** - This is introduced due to a bug in IE11 where lazy loading combined with autoheight plugin causes a wrong - calculation of the height of the owl-item that breaks page layouts - */ } - .owl-carousel .owl-item .owl-lazy { - opacity: 0; - transition: opacity 400ms ease; } - .owl-carousel .owl-item .owl-lazy[src^=""], .owl-carousel .owl-item .owl-lazy:not([src]) { - max-height: 0; } - .owl-carousel .owl-item img.owl-lazy { - transform-style: preserve-3d; } - -/* - * Owl Carousel - Video Plugin - */ -.owl-carousel .owl-video-wrapper { - position: relative; - height: 100%; - background: #000; } - -.owl-carousel .owl-video-play-icon { - position: absolute; - height: 80px; - width: 80px; - left: 50%; - top: 50%; - margin-left: -40px; - margin-top: -40px; - background: url("owl.video.play.png") no-repeat; - cursor: pointer; - z-index: 1; - -webkit-backface-visibility: hidden; - transition: transform 100ms ease; } - -.owl-carousel .owl-video-play-icon:hover { - -ms-transform: scale(1.3, 1.3); - transform: scale(1.3, 1.3); } - -.owl-carousel .owl-video-playing .owl-video-tn, -.owl-carousel .owl-video-playing .owl-video-play-icon { - display: none; } - -.owl-carousel .owl-video-tn { - opacity: 0; - height: 100%; - background-position: center center; - background-repeat: no-repeat; - background-size: contain; - transition: opacity 400ms ease; } - -.owl-carousel .owl-video-frame { - position: relative; - z-index: 1; - height: 100%; - width: 100%; } diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/assets/owl.carousel.min.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/assets/owl.carousel.min.css deleted file mode 100644 index a71df11c01..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/assets/owl.carousel.min.css +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Owl Carousel v2.3.4 - * Copyright 2013-2018 David Deutsch - * Licensed under: SEE LICENSE IN https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE - */ -.owl-carousel,.owl-carousel .owl-item{-webkit-tap-highlight-color:transparent;position:relative}.owl-carousel{display:none;width:100%;z-index:1}.owl-carousel .owl-stage{position:relative;-ms-touch-action:pan-Y;touch-action:manipulation;-moz-backface-visibility:hidden}.owl-carousel .owl-stage:after{content:".";display:block;clear:both;visibility:hidden;line-height:0;height:0}.owl-carousel .owl-stage-outer{position:relative;overflow:hidden;-webkit-transform:translate3d(0,0,0)}.owl-carousel .owl-item,.owl-carousel .owl-wrapper{-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0)}.owl-carousel .owl-item{min-height:1px;float:left;-webkit-backface-visibility:hidden;-webkit-touch-callout:none}.owl-carousel .owl-item img{display:block;width:100%}.owl-carousel .owl-dots.disabled,.owl-carousel .owl-nav.disabled{display:none}.no-js .owl-carousel,.owl-carousel.owl-loaded{display:block}.owl-carousel .owl-dot,.owl-carousel .owl-nav .owl-next,.owl-carousel .owl-nav .owl-prev{cursor:pointer;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.owl-carousel .owl-nav button.owl-next,.owl-carousel .owl-nav button.owl-prev,.owl-carousel button.owl-dot{background:0 0;color:inherit;border:none;padding:0!important;font:inherit}.owl-carousel.owl-loading{opacity:0;display:block}.owl-carousel.owl-hidden{opacity:0}.owl-carousel.owl-refresh .owl-item{visibility:hidden}.owl-carousel.owl-drag .owl-item{-ms-touch-action:pan-y;touch-action:pan-y;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.owl-carousel.owl-grab{cursor:move;cursor:grab}.owl-carousel.owl-rtl{direction:rtl}.owl-carousel.owl-rtl .owl-item{float:right}.owl-carousel .animated{animation-duration:1s;animation-fill-mode:both}.owl-carousel .owl-animated-in{z-index:0}.owl-carousel .owl-animated-out{z-index:1}.owl-carousel .fadeOut{animation-name:fadeOut}@keyframes fadeOut{0%{opacity:1}100%{opacity:0}}.owl-height{transition:height .5s ease-in-out}.owl-carousel .owl-item .owl-lazy{opacity:0;transition:opacity .4s ease}.owl-carousel .owl-item .owl-lazy:not([src]),.owl-carousel .owl-item .owl-lazy[src^=""]{max-height:0}.owl-carousel .owl-item img.owl-lazy{transform-style:preserve-3d}.owl-carousel .owl-video-wrapper{position:relative;height:100%;background:#000}.owl-carousel .owl-video-play-icon{position:absolute;height:80px;width:80px;left:50%;top:50%;margin-left:-40px;margin-top:-40px;background:url(owl.video.play.png) no-repeat;cursor:pointer;z-index:1;-webkit-backface-visibility:hidden;transition:transform .1s ease}.owl-carousel .owl-video-play-icon:hover{-ms-transform:scale(1.3,1.3);transform:scale(1.3,1.3)}.owl-carousel .owl-video-playing .owl-video-play-icon,.owl-carousel .owl-video-playing .owl-video-tn{display:none}.owl-carousel .owl-video-tn{opacity:0;height:100%;background-position:center center;background-repeat:no-repeat;background-size:contain;transition:opacity .4s ease}.owl-carousel .owl-video-frame{position:relative;z-index:1;height:100%;width:100%} \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/assets/owl.theme.default.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/assets/owl.theme.default.css deleted file mode 100644 index e2020fb101..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/assets/owl.theme.default.css +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Owl Carousel v2.3.4 - * Copyright 2013-2018 David Deutsch - * Licensed under: SEE LICENSE IN https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE - */ -/* - * Default theme - Owl Carousel CSS File - */ -.owl-theme .owl-nav { - margin-top: 10px; - text-align: center; - -webkit-tap-highlight-color: transparent; } - .owl-theme .owl-nav [class*='owl-'] { - color: #FFF; - font-size: 14px; - margin: 5px; - padding: 4px 7px; - background: #D6D6D6; - display: inline-block; - cursor: pointer; - border-radius: 3px; } - .owl-theme .owl-nav [class*='owl-']:hover { - background: #869791; - color: #FFF; - text-decoration: none; } - .owl-theme .owl-nav .disabled { - opacity: 0.5; - cursor: default; } - -.owl-theme .owl-nav.disabled + .owl-dots { - margin-top: 10px; } - -.owl-theme .owl-dots { - text-align: center; - -webkit-tap-highlight-color: transparent; } - .owl-theme .owl-dots .owl-dot { - display: inline-block; - zoom: 1; - *display: inline; } - .owl-theme .owl-dots .owl-dot span { - width: 10px; - height: 10px; - margin: 5px 7px; - background: #D6D6D6; - display: block; - -webkit-backface-visibility: visible; - transition: opacity 200ms ease; - border-radius: 30px; } - .owl-theme .owl-dots .owl-dot.active span, .owl-theme .owl-dots .owl-dot:hover span { - background: #869791; } diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/assets/owl.theme.default.min.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/assets/owl.theme.default.min.css deleted file mode 100644 index 487088d2e3..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/assets/owl.theme.default.min.css +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Owl Carousel v2.3.4 - * Copyright 2013-2018 David Deutsch - * Licensed under: SEE LICENSE IN https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE - */ -.owl-theme .owl-dots,.owl-theme .owl-nav{text-align:center;-webkit-tap-highlight-color:transparent}.owl-theme .owl-nav{margin-top:10px}.owl-theme .owl-nav [class*=owl-]{color:#FFF;font-size:14px;margin:5px;padding:4px 7px;background:#D6D6D6;display:inline-block;cursor:pointer;border-radius:3px}.owl-theme .owl-nav [class*=owl-]:hover{background:#869791;color:#FFF;text-decoration:none}.owl-theme .owl-nav .disabled{opacity:.5;cursor:default}.owl-theme .owl-nav.disabled+.owl-dots{margin-top:10px}.owl-theme .owl-dots .owl-dot{display:inline-block;zoom:1}.owl-theme .owl-dots .owl-dot span{width:10px;height:10px;margin:5px 7px;background:#D6D6D6;display:block;-webkit-backface-visibility:visible;transition:opacity .2s ease;border-radius:30px}.owl-theme .owl-dots .owl-dot.active span,.owl-theme .owl-dots .owl-dot:hover span{background:#869791} \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/assets/owl.theme.green.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/assets/owl.theme.green.css deleted file mode 100644 index 5235fbe3bc..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/assets/owl.theme.green.css +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Owl Carousel v2.3.4 - * Copyright 2013-2018 David Deutsch - * Licensed under: SEE LICENSE IN https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE - */ -/* - * Green theme - Owl Carousel CSS File - */ -.owl-theme .owl-nav { - margin-top: 10px; - text-align: center; - -webkit-tap-highlight-color: transparent; } - .owl-theme .owl-nav [class*='owl-'] { - color: #FFF; - font-size: 14px; - margin: 5px; - padding: 4px 7px; - background: #D6D6D6; - display: inline-block; - cursor: pointer; - border-radius: 3px; } - .owl-theme .owl-nav [class*='owl-']:hover { - background: #4DC7A0; - color: #FFF; - text-decoration: none; } - .owl-theme .owl-nav .disabled { - opacity: 0.5; - cursor: default; } - -.owl-theme .owl-nav.disabled + .owl-dots { - margin-top: 10px; } - -.owl-theme .owl-dots { - text-align: center; - -webkit-tap-highlight-color: transparent; } - .owl-theme .owl-dots .owl-dot { - display: inline-block; - zoom: 1; - *display: inline; } - .owl-theme .owl-dots .owl-dot span { - width: 10px; - height: 10px; - margin: 5px 7px; - background: #D6D6D6; - display: block; - -webkit-backface-visibility: visible; - transition: opacity 200ms ease; - border-radius: 30px; } - .owl-theme .owl-dots .owl-dot.active span, .owl-theme .owl-dots .owl-dot:hover span { - background: #4DC7A0; } diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/assets/owl.theme.green.min.css b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/assets/owl.theme.green.min.css deleted file mode 100644 index 187bea0879..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/assets/owl.theme.green.min.css +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Owl Carousel v2.3.4 - * Copyright 2013-2018 David Deutsch - * Licensed under: SEE LICENSE IN https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE - */ -.owl-theme .owl-dots,.owl-theme .owl-nav{text-align:center;-webkit-tap-highlight-color:transparent}.owl-theme .owl-nav{margin-top:10px}.owl-theme .owl-nav [class*=owl-]{color:#FFF;font-size:14px;margin:5px;padding:4px 7px;background:#D6D6D6;display:inline-block;cursor:pointer;border-radius:3px}.owl-theme .owl-nav [class*=owl-]:hover{background:#4DC7A0;color:#FFF;text-decoration:none}.owl-theme .owl-nav .disabled{opacity:.5;cursor:default}.owl-theme .owl-nav.disabled+.owl-dots{margin-top:10px}.owl-theme .owl-dots .owl-dot{display:inline-block;zoom:1}.owl-theme .owl-dots .owl-dot span{width:10px;height:10px;margin:5px 7px;background:#D6D6D6;display:block;-webkit-backface-visibility:visible;transition:opacity .2s ease;border-radius:30px}.owl-theme .owl-dots .owl-dot.active span,.owl-theme .owl-dots .owl-dot:hover span{background:#4DC7A0} \ No newline at end of file diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/assets/owl.video.play.png b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/assets/owl.video.play.png deleted file mode 100644 index 5d0218d466..0000000000 Binary files a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/assets/owl.video.play.png and /dev/null differ diff --git a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/owl.carousel.js b/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/owl.carousel.js deleted file mode 100644 index 66c67ebe09..0000000000 --- a/samples/MicroserviceDemo/applications/PublicWebSite.Host/wwwroot/libs/owl.carousel/owl.carousel.js +++ /dev/null @@ -1,3448 +0,0 @@ -/** - * Owl Carousel v2.3.4 - * Copyright 2013-2018 David Deutsch - * Licensed under: SEE LICENSE IN https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE - */ -/** - * Owl carousel - * @version 2.3.4 - * @author Bartosz Wojciechowski - * @author David Deutsch - * @license The MIT License (MIT) - * @todo Lazy Load Icon - * @todo prevent animationend bubling - * @todo itemsScaleUp - * @todo Test Zepto - * @todo stagePadding calculate wrong active classes - */ -;(function($, window, document, undefined) { - - /** - * Creates a carousel. - * @class The Owl Carousel. - * @public - * @param {HTMLElement|jQuery} element - The element to create the carousel for. - * @param {Object} [options] - The options - */ - function Owl(element, options) { - - /** - * Current settings for the carousel. - * @public - */ - this.settings = null; - - /** - * Current options set by the caller including defaults. - * @public - */ - this.options = $.extend({}, Owl.Defaults, options); - - /** - * Plugin element. - * @public - */ - this.$element = $(element); - - /** - * Proxied event handlers. - * @protected - */ - this._handlers = {}; - - /** - * References to the running plugins of this carousel. - * @protected - */ - this._plugins = {}; - - /** - * Currently suppressed events to prevent them from being retriggered. - * @protected - */ - this._supress = {}; - - /** - * Absolute current position. - * @protected - */ - this._current = null; - - /** - * Animation speed in milliseconds. - * @protected - */ - this._speed = null; - - /** - * Coordinates of all items in pixel. - * @todo The name of this member is missleading. - * @protected - */ - this._coordinates = []; - - /** - * Current breakpoint. - * @todo Real media queries would be nice. - * @protected - */ - this._breakpoint = null; - - /** - * Current width of the plugin element. - */ - this._width = null; - - /** - * All real items. - * @protected - */ - this._items = []; - - /** - * All cloned items. - * @protected - */ - this._clones = []; - - /** - * Merge values of all items. - * @todo Maybe this could be part of a plugin. - * @protected - */ - this._mergers = []; - - /** - * Widths of all items. - */ - this._widths = []; - - /** - * Invalidated parts within the update process. - * @protected - */ - this._invalidated = {}; - - /** - * Ordered list of workers for the update process. - * @protected - */ - this._pipe = []; - - /** - * Current state information for the drag operation. - * @todo #261 - * @protected - */ - this._drag = { - time: null, - target: null, - pointer: null, - stage: { - start: null, - current: null - }, - direction: null - }; - - /** - * Current state information and their tags. - * @type {Object} - * @protected - */ - this._states = { - current: {}, - tags: { - 'initializing': [ 'busy' ], - 'animating': [ 'busy' ], - 'dragging': [ 'interacting' ] - } - }; - - $.each([ 'onResize', 'onThrottledResize' ], $.proxy(function(i, handler) { - this._handlers[handler] = $.proxy(this[handler], this); - }, this)); - - $.each(Owl.Plugins, $.proxy(function(key, plugin) { - this._plugins[key.charAt(0).toLowerCase() + key.slice(1)] - = new plugin(this); - }, this)); - - $.each(Owl.Workers, $.proxy(function(priority, worker) { - this._pipe.push({ - 'filter': worker.filter, - 'run': $.proxy(worker.run, this) - }); - }, this)); - - this.setup(); - this.initialize(); - } - - /** - * Default options for the carousel. - * @public - */ - Owl.Defaults = { - items: 3, - loop: false, - center: false, - rewind: false, - checkVisibility: true, - - mouseDrag: true, - touchDrag: true, - pullDrag: true, - freeDrag: false, - - margin: 0, - stagePadding: 0, - - merge: false, - mergeFit: true, - autoWidth: false, - - startPosition: 0, - rtl: false, - - smartSpeed: 250, - fluidSpeed: false, - dragEndSpeed: false, - - responsive: {}, - responsiveRefreshRate: 200, - responsiveBaseElement: window, - - fallbackEasing: 'swing', - slideTransition: '', - - info: false, - - nestedItemSelector: false, - itemElement: 'div', - stageElement: 'div', - - refreshClass: 'owl-refresh', - loadedClass: 'owl-loaded', - loadingClass: 'owl-loading', - rtlClass: 'owl-rtl', - responsiveClass: 'owl-responsive', - dragClass: 'owl-drag', - itemClass: 'owl-item', - stageClass: 'owl-stage', - stageOuterClass: 'owl-stage-outer', - grabClass: 'owl-grab' - }; - - /** - * Enumeration for width. - * @public - * @readonly - * @enum {String} - */ - Owl.Width = { - Default: 'default', - Inner: 'inner', - Outer: 'outer' - }; - - /** - * Enumeration for types. - * @public - * @readonly - * @enum {String} - */ - Owl.Type = { - Event: 'event', - State: 'state' - }; - - /** - * Contains all registered plugins. - * @public - */ - Owl.Plugins = {}; - - /** - * List of workers involved in the update process. - */ - Owl.Workers = [ { - filter: [ 'width', 'settings' ], - run: function() { - this._width = this.$element.width(); - } - }, { - filter: [ 'width', 'items', 'settings' ], - run: function(cache) { - cache.current = this._items && this._items[this.relative(this._current)]; - } - }, { - filter: [ 'items', 'settings' ], - run: function() { - this.$stage.children('.cloned').remove(); - } - }, { - filter: [ 'width', 'items', 'settings' ], - run: function(cache) { - var margin = this.settings.margin || '', - grid = !this.settings.autoWidth, - rtl = this.settings.rtl, - css = { - 'width': 'auto', - 'margin-left': rtl ? margin : '', - 'margin-right': rtl ? '' : margin - }; - - !grid && this.$stage.children().css(css); - - cache.css = css; - } - }, { - filter: [ 'width', 'items', 'settings' ], - run: function(cache) { - var width = (this.width() / this.settings.items).toFixed(3) - this.settings.margin, - merge = null, - iterator = this._items.length, - grid = !this.settings.autoWidth, - widths = []; - - cache.items = { - merge: false, - width: width - }; - - while (iterator--) { - merge = this._mergers[iterator]; - merge = this.settings.mergeFit && Math.min(merge, this.settings.items) || merge; - - cache.items.merge = merge > 1 || cache.items.merge; - - widths[iterator] = !grid ? this._items[iterator].width() : width * merge; - } - - this._widths = widths; - } - }, { - filter: [ 'items', 'settings' ], - run: function() { - var clones = [], - items = this._items, - settings = this.settings, - // TODO: Should be computed from number of min width items in stage - view = Math.max(settings.items * 2, 4), - size = Math.ceil(items.length / 2) * 2, - repeat = settings.loop && items.length ? settings.rewind ? view : Math.max(view, size) : 0, - append = '', - prepend = ''; - - repeat /= 2; - - while (repeat > 0) { - // Switch to only using appended clones - clones.push(this.normalize(clones.length / 2, true)); - append = append + items[clones[clones.length - 1]][0].outerHTML; - clones.push(this.normalize(items.length - 1 - (clones.length - 1) / 2, true)); - prepend = items[clones[clones.length - 1]][0].outerHTML + prepend; - repeat -= 1; - } - - this._clones = clones; - - $(append).addClass('cloned').appendTo(this.$stage); - $(prepend).addClass('cloned').prependTo(this.$stage); - } - }, { - filter: [ 'width', 'items', 'settings' ], - run: function() { - var rtl = this.settings.rtl ? 1 : -1, - size = this._clones.length + this._items.length, - iterator = -1, - previous = 0, - current = 0, - coordinates = []; - - while (++iterator < size) { - previous = coordinates[iterator - 1] || 0; - current = this._widths[this.relative(iterator)] + this.settings.margin; - coordinates.push(previous + current * rtl); - } - - this._coordinates = coordinates; - } - }, { - filter: [ 'width', 'items', 'settings' ], - run: function() { - var padding = this.settings.stagePadding, - coordinates = this._coordinates, - css = { - 'width': Math.ceil(Math.abs(coordinates[coordinates.length - 1])) + padding * 2, - 'padding-left': padding || '', - 'padding-right': padding || '' - }; - - this.$stage.css(css); - } - }, { - filter: [ 'width', 'items', 'settings' ], - run: function(cache) { - var iterator = this._coordinates.length, - grid = !this.settings.autoWidth, - items = this.$stage.children(); - - if (grid && cache.items.merge) { - while (iterator--) { - cache.css.width = this._widths[this.relative(iterator)]; - items.eq(iterator).css(cache.css); - } - } else if (grid) { - cache.css.width = cache.items.width; - items.css(cache.css); - } - } - }, { - filter: [ 'items' ], - run: function() { - this._coordinates.length < 1 && this.$stage.removeAttr('style'); - } - }, { - filter: [ 'width', 'items', 'settings' ], - run: function(cache) { - cache.current = cache.current ? this.$stage.children().index(cache.current) : 0; - cache.current = Math.max(this.minimum(), Math.min(this.maximum(), cache.current)); - this.reset(cache.current); - } - }, { - filter: [ 'position' ], - run: function() { - this.animate(this.coordinates(this._current)); - } - }, { - filter: [ 'width', 'position', 'items', 'settings' ], - run: function() { - var rtl = this.settings.rtl ? 1 : -1, - padding = this.settings.stagePadding * 2, - begin = this.coordinates(this.current()) + padding, - end = begin + this.width() * rtl, - inner, outer, matches = [], i, n; - - for (i = 0, n = this._coordinates.length; i < n; i++) { - inner = this._coordinates[i - 1] || 0; - outer = Math.abs(this._coordinates[i]) + padding * rtl; - - if ((this.op(inner, '<=', begin) && (this.op(inner, '>', end))) - || (this.op(outer, '<', begin) && this.op(outer, '>', end))) { - matches.push(i); - } - } - - this.$stage.children('.active').removeClass('active'); - this.$stage.children(':eq(' + matches.join('), :eq(') + ')').addClass('active'); - - this.$stage.children('.center').removeClass('center'); - if (this.settings.center) { - this.$stage.children().eq(this.current()).addClass('center'); - } - } - } ]; - - /** - * Create the stage DOM element - */ - Owl.prototype.initializeStage = function() { - this.$stage = this.$element.find('.' + this.settings.stageClass); - - // if the stage is already in the DOM, grab it and skip stage initialization - if (this.$stage.length) { - return; - } - - this.$element.addClass(this.options.loadingClass); - - // create stage - this.$stage = $('<' + this.settings.stageElement + '>', { - "class": this.settings.stageClass - }).wrap( $( '
    ', { - "class": this.settings.stageOuterClass - })); - - // append stage - this.$element.append(this.$stage.parent()); - }; - - /** - * Create item DOM elements - */ - Owl.prototype.initializeItems = function() { - var $items = this.$element.find('.owl-item'); - - // if the items are already in the DOM, grab them and skip item initialization - if ($items.length) { - this._items = $items.get().map(function(item) { - return $(item); - }); - - this._mergers = this._items.map(function() { - return 1; - }); - - this.refresh(); - - return; - } - - // append content - this.replace(this.$element.children().not(this.$stage.parent())); - - // check visibility - if (this.isVisible()) { - // update view - this.refresh(); - } else { - // invalidate width - this.invalidate('width'); - } - - this.$element - .removeClass(this.options.loadingClass) - .addClass(this.options.loadedClass); - }; - - /** - * Initializes the carousel. - * @protected - */ - Owl.prototype.initialize = function() { - this.enter('initializing'); - this.trigger('initialize'); - - this.$element.toggleClass(this.settings.rtlClass, this.settings.rtl); - - if (this.settings.autoWidth && !this.is('pre-loading')) { - var imgs, nestedSelector, width; - imgs = this.$element.find('img'); - nestedSelector = this.settings.nestedItemSelector ? '.' + this.settings.nestedItemSelector : undefined; - width = this.$element.children(nestedSelector).width(); - - if (imgs.length && width <= 0) { - this.preloadAutoWidthImages(imgs); - } - } - - this.initializeStage(); - this.initializeItems(); - - // register event handlers - this.registerEventHandlers(); - - this.leave('initializing'); - this.trigger('initialized'); - }; - - /** - * @returns {Boolean} visibility of $element - * if you know the carousel will always be visible you can set `checkVisibility` to `false` to - * prevent the expensive browser layout forced reflow the $element.is(':visible') does - */ - Owl.prototype.isVisible = function() { - return this.settings.checkVisibility - ? this.$element.is(':visible') - : true; - }; - - /** - * Setups the current settings. - * @todo Remove responsive classes. Why should adaptive designs be brought into IE8? - * @todo Support for media queries by using `matchMedia` would be nice. - * @public - */ - Owl.prototype.setup = function() { - var viewport = this.viewport(), - overwrites = this.options.responsive, - match = -1, - settings = null; - - if (!overwrites) { - settings = $.extend({}, this.options); - } else { - $.each(overwrites, function(breakpoint) { - if (breakpoint <= viewport && breakpoint > match) { - match = Number(breakpoint); - } - }); - - settings = $.extend({}, this.options, overwrites[match]); - if (typeof settings.stagePadding === 'function') { - settings.stagePadding = settings.stagePadding(); - } - delete settings.responsive; - - // responsive class - if (settings.responsiveClass) { - this.$element.attr('class', - this.$element.attr('class').replace(new RegExp('(' + this.options.responsiveClass + '-)\\S+\\s', 'g'), '$1' + match) - ); - } - } - - this.trigger('change', { property: { name: 'settings', value: settings } }); - this._breakpoint = match; - this.settings = settings; - this.invalidate('settings'); - this.trigger('changed', { property: { name: 'settings', value: this.settings } }); - }; - - /** - * Updates option logic if necessery. - * @protected - */ - Owl.prototype.optionsLogic = function() { - if (this.settings.autoWidth) { - this.settings.stagePadding = false; - this.settings.merge = false; - } - }; - - /** - * Prepares an item before add. - * @todo Rename event parameter `content` to `item`. - * @protected - * @returns {jQuery|HTMLElement} - The item container. - */ - Owl.prototype.prepare = function(item) { - var event = this.trigger('prepare', { content: item }); - - if (!event.data) { - event.data = $('<' + this.settings.itemElement + '/>') - .addClass(this.options.itemClass).append(item) - } - - this.trigger('prepared', { content: event.data }); - - return event.data; - }; - - /** - * Updates the view. - * @public - */ - Owl.prototype.update = function() { - var i = 0, - n = this._pipe.length, - filter = $.proxy(function(p) { return this[p] }, this._invalidated), - cache = {}; - - while (i < n) { - if (this._invalidated.all || $.grep(this._pipe[i].filter, filter).length > 0) { - this._pipe[i].run(cache); - } - i++; - } - - this._invalidated = {}; - - !this.is('valid') && this.enter('valid'); - }; - - /** - * Gets the width of the view. - * @public - * @param {Owl.Width} [dimension=Owl.Width.Default] - The dimension to return. - * @returns {Number} - The width of the view in pixel. - */ - Owl.prototype.width = function(dimension) { - dimension = dimension || Owl.Width.Default; - switch (dimension) { - case Owl.Width.Inner: - case Owl.Width.Outer: - return this._width; - default: - return this._width - this.settings.stagePadding * 2 + this.settings.margin; - } - }; - - /** - * Refreshes the carousel primarily for adaptive purposes. - * @public - */ - Owl.prototype.refresh = function() { - this.enter('refreshing'); - this.trigger('refresh'); - - this.setup(); - - this.optionsLogic(); - - this.$element.addClass(this.options.refreshClass); - - this.update(); - - this.$element.removeClass(this.options.refreshClass); - - this.leave('refreshing'); - this.trigger('refreshed'); - }; - - /** - * Checks window `resize` event. - * @protected - */ - Owl.prototype.onThrottledResize = function() { - window.clearTimeout(this.resizeTimer); - this.resizeTimer = window.setTimeout(this._handlers.onResize, this.settings.responsiveRefreshRate); - }; - - /** - * Checks window `resize` event. - * @protected - */ - Owl.prototype.onResize = function() { - if (!this._items.length) { - return false; - } - - if (this._width === this.$element.width()) { - return false; - } - - if (!this.isVisible()) { - return false; - } - - this.enter('resizing'); - - if (this.trigger('resize').isDefaultPrevented()) { - this.leave('resizing'); - return false; - } - - this.invalidate('width'); - - this.refresh(); - - this.leave('resizing'); - this.trigger('resized'); - }; - - /** - * Registers event handlers. - * @todo Check `msPointerEnabled` - * @todo #261 - * @protected - */ - Owl.prototype.registerEventHandlers = function() { - if ($.support.transition) { - this.$stage.on($.support.transition.end + '.owl.core', $.proxy(this.onTransitionEnd, this)); - } - - if (this.settings.responsive !== false) { - this.on(window, 'resize', this._handlers.onThrottledResize); - } - - if (this.settings.mouseDrag) { - this.$element.addClass(this.options.dragClass); - this.$stage.on('mousedown.owl.core', $.proxy(this.onDragStart, this)); - this.$stage.on('dragstart.owl.core selectstart.owl.core', function() { return false }); - } - - if (this.settings.touchDrag){ - this.$stage.on('touchstart.owl.core', $.proxy(this.onDragStart, this)); - this.$stage.on('touchcancel.owl.core', $.proxy(this.onDragEnd, this)); - } - }; - - /** - * Handles `touchstart` and `mousedown` events. - * @todo Horizontal swipe threshold as option - * @todo #261 - * @protected - * @param {Event} event - The event arguments. - */ - Owl.prototype.onDragStart = function(event) { - var stage = null; - - if (event.which === 3) { - return; - } - - if ($.support.transform) { - stage = this.$stage.css('transform').replace(/.*\(|\)| /g, '').split(','); - stage = { - x: stage[stage.length === 16 ? 12 : 4], - y: stage[stage.length === 16 ? 13 : 5] - }; - } else { - stage = this.$stage.position(); - stage = { - x: this.settings.rtl ? - stage.left + this.$stage.width() - this.width() + this.settings.margin : - stage.left, - y: stage.top - }; - } - - if (this.is('animating')) { - $.support.transform ? this.animate(stage.x) : this.$stage.stop() - this.invalidate('position'); - } - - this.$element.toggleClass(this.options.grabClass, event.type === 'mousedown'); - - this.speed(0); - - this._drag.time = new Date().getTime(); - this._drag.target = $(event.target); - this._drag.stage.start = stage; - this._drag.stage.current = stage; - this._drag.pointer = this.pointer(event); - - $(document).on('mouseup.owl.core touchend.owl.core', $.proxy(this.onDragEnd, this)); - - $(document).one('mousemove.owl.core touchmove.owl.core', $.proxy(function(event) { - var delta = this.difference(this._drag.pointer, this.pointer(event)); - - $(document).on('mousemove.owl.core touchmove.owl.core', $.proxy(this.onDragMove, this)); - - if (Math.abs(delta.x) < Math.abs(delta.y) && this.is('valid')) { - return; - } - - event.preventDefault(); - - this.enter('dragging'); - this.trigger('drag'); - }, this)); - }; - - /** - * Handles the `touchmove` and `mousemove` events. - * @todo #261 - * @protected - * @param {Event} event - The event arguments. - */ - Owl.prototype.onDragMove = function(event) { - var minimum = null, - maximum = null, - pull = null, - delta = this.difference(this._drag.pointer, this.pointer(event)), - stage = this.difference(this._drag.stage.start, delta); - - if (!this.is('dragging')) { - return; - } - - event.preventDefault(); - - if (this.settings.loop) { - minimum = this.coordinates(this.minimum()); - maximum = this.coordinates(this.maximum() + 1) - minimum; - stage.x = (((stage.x - minimum) % maximum + maximum) % maximum) + minimum; - } else { - minimum = this.settings.rtl ? this.coordinates(this.maximum()) : this.coordinates(this.minimum()); - maximum = this.settings.rtl ? this.coordinates(this.minimum()) : this.coordinates(this.maximum()); - pull = this.settings.pullDrag ? -1 * delta.x / 5 : 0; - stage.x = Math.max(Math.min(stage.x, minimum + pull), maximum + pull); - } - - this._drag.stage.current = stage; - - this.animate(stage.x); - }; - - /** - * Handles the `touchend` and `mouseup` events. - * @todo #261 - * @todo Threshold for click event - * @protected - * @param {Event} event - The event arguments. - */ - Owl.prototype.onDragEnd = function(event) { - var delta = this.difference(this._drag.pointer, this.pointer(event)), - stage = this._drag.stage.current, - direction = delta.x > 0 ^ this.settings.rtl ? 'left' : 'right'; - - $(document).off('.owl.core'); - - this.$element.removeClass(this.options.grabClass); - - if (delta.x !== 0 && this.is('dragging') || !this.is('valid')) { - this.speed(this.settings.dragEndSpeed || this.settings.smartSpeed); - this.current(this.closest(stage.x, delta.x !== 0 ? direction : this._drag.direction)); - this.invalidate('position'); - this.update(); - - this._drag.direction = direction; - - if (Math.abs(delta.x) > 3 || new Date().getTime() - this._drag.time > 300) { - this._drag.target.one('click.owl.core', function() { return false; }); - } - } - - if (!this.is('dragging')) { - return; - } - - this.leave('dragging'); - this.trigger('dragged'); - }; - - /** - * Gets absolute position of the closest item for a coordinate. - * @todo Setting `freeDrag` makes `closest` not reusable. See #165. - * @protected - * @param {Number} coordinate - The coordinate in pixel. - * @param {String} direction - The direction to check for the closest item. Ether `left` or `right`. - * @return {Number} - The absolute position of the closest item. - */ - Owl.prototype.closest = function(coordinate, direction) { - var position = -1, - pull = 30, - width = this.width(), - coordinates = this.coordinates(); - - if (!this.settings.freeDrag) { - // check closest item - $.each(coordinates, $.proxy(function(index, value) { - // on a left pull, check on current index - if (direction === 'left' && coordinate > value - pull && coordinate < value + pull) { - position = index; - // on a right pull, check on previous index - // to do so, subtract width from value and set position = index + 1 - } else if (direction === 'right' && coordinate > value - width - pull && coordinate < value - width + pull) { - position = index + 1; - } else if (this.op(coordinate, '<', value) - && this.op(coordinate, '>', coordinates[index + 1] !== undefined ? coordinates[index + 1] : value - width)) { - position = direction === 'left' ? index + 1 : index; - } - return position === -1; - }, this)); - } - - if (!this.settings.loop) { - // non loop boundries - if (this.op(coordinate, '>', coordinates[this.minimum()])) { - position = coordinate = this.minimum(); - } else if (this.op(coordinate, '<', coordinates[this.maximum()])) { - position = coordinate = this.maximum(); - } - } - - return position; - }; - - /** - * Animates the stage. - * @todo #270 - * @public - * @param {Number} coordinate - The coordinate in pixels. - */ - Owl.prototype.animate = function(coordinate) { - var animate = this.speed() > 0; - - this.is('animating') && this.onTransitionEnd(); - - if (animate) { - this.enter('animating'); - this.trigger('translate'); - } - - if ($.support.transform3d && $.support.transition) { - this.$stage.css({ - transform: 'translate3d(' + coordinate + 'px,0px,0px)', - transition: (this.speed() / 1000) + 's' + ( - this.settings.slideTransition ? ' ' + this.settings.slideTransition : '' - ) - }); - } else if (animate) { - this.$stage.animate({ - left: coordinate + 'px' - }, this.speed(), this.settings.fallbackEasing, $.proxy(this.onTransitionEnd, this)); - } else { - this.$stage.css({ - left: coordinate + 'px' - }); - } - }; - - /** - * Checks whether the carousel is in a specific state or not. - * @param {String} state - The state to check. - * @returns {Boolean} - The flag which indicates if the carousel is busy. - */ - Owl.prototype.is = function(state) { - return this._states.current[state] && this._states.current[state] > 0; - }; - - /** - * Sets the absolute position of the current item. - * @public - * @param {Number} [position] - The new absolute position or nothing to leave it unchanged. - * @returns {Number} - The absolute position of the current item. - */ - Owl.prototype.current = function(position) { - if (position === undefined) { - return this._current; - } - - if (this._items.length === 0) { - return undefined; - } - - position = this.normalize(position); - - if (this._current !== position) { - var event = this.trigger('change', { property: { name: 'position', value: position } }); - - if (event.data !== undefined) { - position = this.normalize(event.data); - } - - this._current = position; - - this.invalidate('position'); - - this.trigger('changed', { property: { name: 'position', value: this._current } }); - } - - return this._current; - }; - - /** - * Invalidates the given part of the update routine. - * @param {String} [part] - The part to invalidate. - * @returns {Array.} - The invalidated parts. - */ - Owl.prototype.invalidate = function(part) { - if ($.type(part) === 'string') { - this._invalidated[part] = true; - this.is('valid') && this.leave('valid'); - } - return $.map(this._invalidated, function(v, i) { return i }); - }; - - /** - * Resets the absolute position of the current item. - * @public - * @param {Number} position - The absolute position of the new item. - */ - Owl.prototype.reset = function(position) { - position = this.normalize(position); - - if (position === undefined) { - return; - } - - this._speed = 0; - this._current = position; - - this.suppress([ 'translate', 'translated' ]); - - this.animate(this.coordinates(position)); - - this.release([ 'translate', 'translated' ]); - }; - - /** - * Normalizes an absolute or a relative position of an item. - * @public - * @param {Number} position - The absolute or relative position to normalize. - * @param {Boolean} [relative=false] - Whether the given position is relative or not. - * @returns {Number} - The normalized position. - */ - Owl.prototype.normalize = function(position, relative) { - var n = this._items.length, - m = relative ? 0 : this._clones.length; - - if (!this.isNumeric(position) || n < 1) { - position = undefined; - } else if (position < 0 || position >= n + m) { - position = ((position - m / 2) % n + n) % n + m / 2; - } - - return position; - }; - - /** - * Converts an absolute position of an item into a relative one. - * @public - * @param {Number} position - The absolute position to convert. - * @returns {Number} - The converted position. - */ - Owl.prototype.relative = function(position) { - position -= this._clones.length / 2; - return this.normalize(position, true); - }; - - /** - * Gets the maximum position for the current item. - * @public - * @param {Boolean} [relative=false] - Whether to return an absolute position or a relative position. - * @returns {Number} - */ - Owl.prototype.maximum = function(relative) { - var settings = this.settings, - maximum = this._coordinates.length, - iterator, - reciprocalItemsWidth, - elementWidth; - - if (settings.loop) { - maximum = this._clones.length / 2 + this._items.length - 1; - } else if (settings.autoWidth || settings.merge) { - iterator = this._items.length; - if (iterator) { - reciprocalItemsWidth = this._items[--iterator].width(); - elementWidth = this.$element.width(); - while (iterator--) { - reciprocalItemsWidth += this._items[iterator].width() + this.settings.margin; - if (reciprocalItemsWidth > elementWidth) { - break; - } - } - } - maximum = iterator + 1; - } else if (settings.center) { - maximum = this._items.length - 1; - } else { - maximum = this._items.length - settings.items; - } - - if (relative) { - maximum -= this._clones.length / 2; - } - - return Math.max(maximum, 0); - }; - - /** - * Gets the minimum position for the current item. - * @public - * @param {Boolean} [relative=false] - Whether to return an absolute position or a relative position. - * @returns {Number} - */ - Owl.prototype.minimum = function(relative) { - return relative ? 0 : this._clones.length / 2; - }; - - /** - * Gets an item at the specified relative position. - * @public - * @param {Number} [position] - The relative position of the item. - * @return {jQuery|Array.} - The item at the given position or all items if no position was given. - */ - Owl.prototype.items = function(position) { - if (position === undefined) { - return this._items.slice(); - } - - position = this.normalize(position, true); - return this._items[position]; - }; - - /** - * Gets an item at the specified relative position. - * @public - * @param {Number} [position] - The relative position of the item. - * @return {jQuery|Array.} - The item at the given position or all items if no position was given. - */ - Owl.prototype.mergers = function(position) { - if (position === undefined) { - return this._mergers.slice(); - } - - position = this.normalize(position, true); - return this._mergers[position]; - }; - - /** - * Gets the absolute positions of clones for an item. - * @public - * @param {Number} [position] - The relative position of the item. - * @returns {Array.} - The absolute positions of clones for the item or all if no position was given. - */ - Owl.prototype.clones = function(position) { - var odd = this._clones.length / 2, - even = odd + this._items.length, - map = function(index) { return index % 2 === 0 ? even + index / 2 : odd - (index + 1) / 2 }; - - if (position === undefined) { - return $.map(this._clones, function(v, i) { return map(i) }); - } - - return $.map(this._clones, function(v, i) { return v === position ? map(i) : null }); - }; - - /** - * Sets the current animation speed. - * @public - * @param {Number} [speed] - The animation speed in milliseconds or nothing to leave it unchanged. - * @returns {Number} - The current animation speed in milliseconds. - */ - Owl.prototype.speed = function(speed) { - if (speed !== undefined) { - this._speed = speed; - } - - return this._speed; - }; - - /** - * Gets the coordinate of an item. - * @todo The name of this method is missleanding. - * @public - * @param {Number} position - The absolute position of the item within `minimum()` and `maximum()`. - * @returns {Number|Array.} - The coordinate of the item in pixel or all coordinates. - */ - Owl.prototype.coordinates = function(position) { - var multiplier = 1, - newPosition = position - 1, - coordinate; - - if (position === undefined) { - return $.map(this._coordinates, $.proxy(function(coordinate, index) { - return this.coordinates(index); - }, this)); - } - - if (this.settings.center) { - if (this.settings.rtl) { - multiplier = -1; - newPosition = position + 1; - } - - coordinate = this._coordinates[position]; - coordinate += (this.width() - coordinate + (this._coordinates[newPosition] || 0)) / 2 * multiplier; - } else { - coordinate = this._coordinates[newPosition] || 0; - } - - coordinate = Math.ceil(coordinate); - - return coordinate; - }; - - /** - * Calculates the speed for a translation. - * @protected - * @param {Number} from - The absolute position of the start item. - * @param {Number} to - The absolute position of the target item. - * @param {Number} [factor=undefined] - The time factor in milliseconds. - * @returns {Number} - The time in milliseconds for the translation. - */ - Owl.prototype.duration = function(from, to, factor) { - if (factor === 0) { - return 0; - } - - return Math.min(Math.max(Math.abs(to - from), 1), 6) * Math.abs((factor || this.settings.smartSpeed)); - }; - - /** - * Slides to the specified item. - * @public - * @param {Number} position - The position of the item. - * @param {Number} [speed] - The time in milliseconds for the transition. - */ - Owl.prototype.to = function(position, speed) { - var current = this.current(), - revert = null, - distance = position - this.relative(current), - direction = (distance > 0) - (distance < 0), - items = this._items.length, - minimum = this.minimum(), - maximum = this.maximum(); - - if (this.settings.loop) { - if (!this.settings.rewind && Math.abs(distance) > items / 2) { - distance += direction * -1 * items; - } - - position = current + distance; - revert = ((position - minimum) % items + items) % items + minimum; - - if (revert !== position && revert - distance <= maximum && revert - distance > 0) { - current = revert - distance; - position = revert; - this.reset(current); - } - } else if (this.settings.rewind) { - maximum += 1; - position = (position % maximum + maximum) % maximum; - } else { - position = Math.max(minimum, Math.min(maximum, position)); - } - - this.speed(this.duration(current, position, speed)); - this.current(position); - - if (this.isVisible()) { - this.update(); - } - }; - - /** - * Slides to the next item. - * @public - * @param {Number} [speed] - The time in milliseconds for the transition. - */ - Owl.prototype.next = function(speed) { - speed = speed || false; - this.to(this.relative(this.current()) + 1, speed); - }; - - /** - * Slides to the previous item. - * @public - * @param {Number} [speed] - The time in milliseconds for the transition. - */ - Owl.prototype.prev = function(speed) { - speed = speed || false; - this.to(this.relative(this.current()) - 1, speed); - }; - - /** - * Handles the end of an animation. - * @protected - * @param {Event} event - The event arguments. - */ - Owl.prototype.onTransitionEnd = function(event) { - - // if css2 animation then event object is undefined - if (event !== undefined) { - event.stopPropagation(); - - // Catch only owl-stage transitionEnd event - if ((event.target || event.srcElement || event.originalTarget) !== this.$stage.get(0)) { - return false; - } - } - - this.leave('animating'); - this.trigger('translated'); - }; - - /** - * Gets viewport width. - * @protected - * @return {Number} - The width in pixel. - */ - Owl.prototype.viewport = function() { - var width; - if (this.options.responsiveBaseElement !== window) { - width = $(this.options.responsiveBaseElement).width(); - } else if (window.innerWidth) { - width = window.innerWidth; - } else if (document.documentElement && document.documentElement.clientWidth) { - width = document.documentElement.clientWidth; - } else { - console.warn('Can not detect viewport width.'); - } - return width; - }; - - /** - * Replaces the current content. - * @public - * @param {HTMLElement|jQuery|String} content - The new content. - */ - Owl.prototype.replace = function(content) { - this.$stage.empty(); - this._items = []; - - if (content) { - content = (content instanceof jQuery) ? content : $(content); - } - - if (this.settings.nestedItemSelector) { - content = content.find('.' + this.settings.nestedItemSelector); - } - - content.filter(function() { - return this.nodeType === 1; - }).each($.proxy(function(index, item) { - item = this.prepare(item); - this.$stage.append(item); - this._items.push(item); - this._mergers.push(item.find('[data-merge]').addBack('[data-merge]').attr('data-merge') * 1 || 1); - }, this)); - - this.reset(this.isNumeric(this.settings.startPosition) ? this.settings.startPosition : 0); - - this.invalidate('items'); - }; - - /** - * Adds an item. - * @todo Use `item` instead of `content` for the event arguments. - * @public - * @param {HTMLElement|jQuery|String} content - The item content to add. - * @param {Number} [position] - The relative position at which to insert the item otherwise the item will be added to the end. - */ - Owl.prototype.add = function(content, position) { - var current = this.relative(this._current); - - position = position === undefined ? this._items.length : this.normalize(position, true); - content = content instanceof jQuery ? content : $(content); - - this.trigger('add', { content: content, position: position }); - - content = this.prepare(content); - - if (this._items.length === 0 || position === this._items.length) { - this._items.length === 0 && this.$stage.append(content); - this._items.length !== 0 && this._items[position - 1].after(content); - this._items.push(content); - this._mergers.push(content.find('[data-merge]').addBack('[data-merge]').attr('data-merge') * 1 || 1); - } else { - this._items[position].before(content); - this._items.splice(position, 0, content); - this._mergers.splice(position, 0, content.find('[data-merge]').addBack('[data-merge]').attr('data-merge') * 1 || 1); - } - - this._items[current] && this.reset(this._items[current].index()); - - this.invalidate('items'); - - this.trigger('added', { content: content, position: position }); - }; - - /** - * Removes an item by its position. - * @todo Use `item` instead of `content` for the event arguments. - * @public - * @param {Number} position - The relative position of the item to remove. - */ - Owl.prototype.remove = function(position) { - position = this.normalize(position, true); - - if (position === undefined) { - return; - } - - this.trigger('remove', { content: this._items[position], position: position }); - - this._items[position].remove(); - this._items.splice(position, 1); - this._mergers.splice(position, 1); - - this.invalidate('items'); - - this.trigger('removed', { content: null, position: position }); - }; - - /** - * Preloads images with auto width. - * @todo Replace by a more generic approach - * @protected - */ - Owl.prototype.preloadAutoWidthImages = function(images) { - images.each($.proxy(function(i, element) { - this.enter('pre-loading'); - element = $(element); - $(new Image()).one('load', $.proxy(function(e) { - element.attr('src', e.target.src); - element.css('opacity', 1); - this.leave('pre-loading'); - !this.is('pre-loading') && !this.is('initializing') && this.refresh(); - }, this)).attr('src', element.attr('src') || element.attr('data-src') || element.attr('data-src-retina')); - }, this)); - }; - - /** - * Destroys the carousel. - * @public - */ - Owl.prototype.destroy = function() { - - this.$element.off('.owl.core'); - this.$stage.off('.owl.core'); - $(document).off('.owl.core'); - - if (this.settings.responsive !== false) { - window.clearTimeout(this.resizeTimer); - this.off(window, 'resize', this._handlers.onThrottledResize); - } - - for (var i in this._plugins) { - this._plugins[i].destroy(); - } - - this.$stage.children('.cloned').remove(); - - this.$stage.unwrap(); - this.$stage.children().contents().unwrap(); - this.$stage.children().unwrap(); - this.$stage.remove(); - this.$element - .removeClass(this.options.refreshClass) - .removeClass(this.options.loadingClass) - .removeClass(this.options.loadedClass) - .removeClass(this.options.rtlClass) - .removeClass(this.options.dragClass) - .removeClass(this.options.grabClass) - .attr('class', this.$element.attr('class').replace(new RegExp(this.options.responsiveClass + '-\\S+\\s', 'g'), '')) - .removeData('owl.carousel'); - }; - - /** - * Operators to calculate right-to-left and left-to-right. - * @protected - * @param {Number} [a] - The left side operand. - * @param {String} [o] - The operator. - * @param {Number} [b] - The right side operand. - */ - Owl.prototype.op = function(a, o, b) { - var rtl = this.settings.rtl; - switch (o) { - case '<': - return rtl ? a > b : a < b; - case '>': - return rtl ? a < b : a > b; - case '>=': - return rtl ? a <= b : a >= b; - case '<=': - return rtl ? a >= b : a <= b; - default: - break; - } - }; - - /** - * Attaches to an internal event. - * @protected - * @param {HTMLElement} element - The event source. - * @param {String} event - The event name. - * @param {Function} listener - The event handler to attach. - * @param {Boolean} capture - Wether the event should be handled at the capturing phase or not. - */ - Owl.prototype.on = function(element, event, listener, capture) { - if (element.addEventListener) { - element.addEventListener(event, listener, capture); - } else if (element.attachEvent) { - element.attachEvent('on' + event, listener); - } - }; - - /** - * Detaches from an internal event. - * @protected - * @param {HTMLElement} element - The event source. - * @param {String} event - The event name. - * @param {Function} listener - The attached event handler to detach. - * @param {Boolean} capture - Wether the attached event handler was registered as a capturing listener or not. - */ - Owl.prototype.off = function(element, event, listener, capture) { - if (element.removeEventListener) { - element.removeEventListener(event, listener, capture); - } else if (element.detachEvent) { - element.detachEvent('on' + event, listener); - } - }; - - /** - * Triggers a public event. - * @todo Remove `status`, `relatedTarget` should be used instead. - * @protected - * @param {String} name - The event name. - * @param {*} [data=null] - The event data. - * @param {String} [namespace=carousel] - The event namespace. - * @param {String} [state] - The state which is associated with the event. - * @param {Boolean} [enter=false] - Indicates if the call enters the specified state or not. - * @returns {Event} - The event arguments. - */ - Owl.prototype.trigger = function(name, data, namespace, state, enter) { - var status = { - item: { count: this._items.length, index: this.current() } - }, handler = $.camelCase( - $.grep([ 'on', name, namespace ], function(v) { return v }) - .join('-').toLowerCase() - ), event = $.Event( - [ name, 'owl', namespace || 'carousel' ].join('.').toLowerCase(), - $.extend({ relatedTarget: this }, status, data) - ); - - if (!this._supress[name]) { - $.each(this._plugins, function(name, plugin) { - if (plugin.onTrigger) { - plugin.onTrigger(event); - } - }); - - this.register({ type: Owl.Type.Event, name: name }); - this.$element.trigger(event); - - if (this.settings && typeof this.settings[handler] === 'function') { - this.settings[handler].call(this, event); - } - } - - return event; - }; - - /** - * Enters a state. - * @param name - The state name. - */ - Owl.prototype.enter = function(name) { - $.each([ name ].concat(this._states.tags[name] || []), $.proxy(function(i, name) { - if (this._states.current[name] === undefined) { - this._states.current[name] = 0; - } - - this._states.current[name]++; - }, this)); - }; - - /** - * Leaves a state. - * @param name - The state name. - */ - Owl.prototype.leave = function(name) { - $.each([ name ].concat(this._states.tags[name] || []), $.proxy(function(i, name) { - this._states.current[name]--; - }, this)); - }; - - /** - * Registers an event or state. - * @public - * @param {Object} object - The event or state to register. - */ - Owl.prototype.register = function(object) { - if (object.type === Owl.Type.Event) { - if (!$.event.special[object.name]) { - $.event.special[object.name] = {}; - } - - if (!$.event.special[object.name].owl) { - var _default = $.event.special[object.name]._default; - $.event.special[object.name]._default = function(e) { - if (_default && _default.apply && (!e.namespace || e.namespace.indexOf('owl') === -1)) { - return _default.apply(this, arguments); - } - return e.namespace && e.namespace.indexOf('owl') > -1; - }; - $.event.special[object.name].owl = true; - } - } else if (object.type === Owl.Type.State) { - if (!this._states.tags[object.name]) { - this._states.tags[object.name] = object.tags; - } else { - this._states.tags[object.name] = this._states.tags[object.name].concat(object.tags); - } - - this._states.tags[object.name] = $.grep(this._states.tags[object.name], $.proxy(function(tag, i) { - return $.inArray(tag, this._states.tags[object.name]) === i; - }, this)); - } - }; - - /** - * Suppresses events. - * @protected - * @param {Array.} events - The events to suppress. - */ - Owl.prototype.suppress = function(events) { - $.each(events, $.proxy(function(index, event) { - this._supress[event] = true; - }, this)); - }; - - /** - * Releases suppressed events. - * @protected - * @param {Array.} events - The events to release. - */ - Owl.prototype.release = function(events) { - $.each(events, $.proxy(function(index, event) { - delete this._supress[event]; - }, this)); - }; - - /** - * Gets unified pointer coordinates from event. - * @todo #261 - * @protected - * @param {Event} - The `mousedown` or `touchstart` event. - * @returns {Object} - Contains `x` and `y` coordinates of current pointer position. - */ - Owl.prototype.pointer = function(event) { - var result = { x: null, y: null }; - - event = event.originalEvent || event || window.event; - - event = event.touches && event.touches.length ? - event.touches[0] : event.changedTouches && event.changedTouches.length ? - event.changedTouches[0] : event; - - if (event.pageX) { - result.x = event.pageX; - result.y = event.pageY; - } else { - result.x = event.clientX; - result.y = event.clientY; - } - - return result; - }; - - /** - * Determines if the input is a Number or something that can be coerced to a Number - * @protected - * @param {Number|String|Object|Array|Boolean|RegExp|Function|Symbol} - The input to be tested - * @returns {Boolean} - An indication if the input is a Number or can be coerced to a Number - */ - Owl.prototype.isNumeric = function(number) { - return !isNaN(parseFloat(number)); - }; - - /** - * Gets the difference of two vectors. - * @todo #261 - * @protected - * @param {Object} - The first vector. - * @param {Object} - The second vector. - * @returns {Object} - The difference. - */ - Owl.prototype.difference = function(first, second) { - return { - x: first.x - second.x, - y: first.y - second.y - }; - }; - - /** - * The jQuery Plugin for the Owl Carousel - * @todo Navigation plugin `next` and `prev` - * @public - */ - $.fn.owlCarousel = function(option) { - var args = Array.prototype.slice.call(arguments, 1); - - return this.each(function() { - var $this = $(this), - data = $this.data('owl.carousel'); - - if (!data) { - data = new Owl(this, typeof option == 'object' && option); - $this.data('owl.carousel', data); - - $.each([ - 'next', 'prev', 'to', 'destroy', 'refresh', 'replace', 'add', 'remove' - ], function(i, event) { - data.register({ type: Owl.Type.Event, name: event }); - data.$element.on(event + '.owl.carousel.core', $.proxy(function(e) { - if (e.namespace && e.relatedTarget !== this) { - this.suppress([ event ]); - data[event].apply(this, [].slice.call(arguments, 1)); - this.release([ event ]); - } - }, data)); - }); - } - - if (typeof option == 'string' && option.charAt(0) !== '_') { - data[option].apply(data, args); - } - }); - }; - - /** - * The constructor for the jQuery Plugin - * @public - */ - $.fn.owlCarousel.Constructor = Owl; - -})(window.Zepto || window.jQuery, window, document); - -/** - * AutoRefresh Plugin - * @version 2.3.4 - * @author Artus Kolanowski - * @author David Deutsch - * @license The MIT License (MIT) - */ -;(function($, window, document, undefined) { - - /** - * Creates the auto refresh plugin. - * @class The Auto Refresh Plugin - * @param {Owl} carousel - The Owl Carousel - */ - var AutoRefresh = function(carousel) { - /** - * Reference to the core. - * @protected - * @type {Owl} - */ - this._core = carousel; - - /** - * Refresh interval. - * @protected - * @type {number} - */ - this._interval = null; - - /** - * Whether the element is currently visible or not. - * @protected - * @type {Boolean} - */ - this._visible = null; - - /** - * All event handlers. - * @protected - * @type {Object} - */ - this._handlers = { - 'initialized.owl.carousel': $.proxy(function(e) { - if (e.namespace && this._core.settings.autoRefresh) { - this.watch(); - } - }, this) - }; - - // set default options - this._core.options = $.extend({}, AutoRefresh.Defaults, this._core.options); - - // register event handlers - this._core.$element.on(this._handlers); - }; - - /** - * Default options. - * @public - */ - AutoRefresh.Defaults = { - autoRefresh: true, - autoRefreshInterval: 500 - }; - - /** - * Watches the element. - */ - AutoRefresh.prototype.watch = function() { - if (this._interval) { - return; - } - - this._visible = this._core.isVisible(); - this._interval = window.setInterval($.proxy(this.refresh, this), this._core.settings.autoRefreshInterval); - }; - - /** - * Refreshes the element. - */ - AutoRefresh.prototype.refresh = function() { - if (this._core.isVisible() === this._visible) { - return; - } - - this._visible = !this._visible; - - this._core.$element.toggleClass('owl-hidden', !this._visible); - - this._visible && (this._core.invalidate('width') && this._core.refresh()); - }; - - /** - * Destroys the plugin. - */ - AutoRefresh.prototype.destroy = function() { - var handler, property; - - window.clearInterval(this._interval); - - for (handler in this._handlers) { - this._core.$element.off(handler, this._handlers[handler]); - } - for (property in Object.getOwnPropertyNames(this)) { - typeof this[property] != 'function' && (this[property] = null); - } - }; - - $.fn.owlCarousel.Constructor.Plugins.AutoRefresh = AutoRefresh; - -})(window.Zepto || window.jQuery, window, document); - -/** - * Lazy Plugin - * @version 2.3.4 - * @author Bartosz Wojciechowski - * @author David Deutsch - * @license The MIT License (MIT) - */ -;(function($, window, document, undefined) { - - /** - * Creates the lazy plugin. - * @class The Lazy Plugin - * @param {Owl} carousel - The Owl Carousel - */ - var Lazy = function(carousel) { - - /** - * Reference to the core. - * @protected - * @type {Owl} - */ - this._core = carousel; - - /** - * Already loaded items. - * @protected - * @type {Array.} - */ - this._loaded = []; - - /** - * Event handlers. - * @protected - * @type {Object} - */ - this._handlers = { - 'initialized.owl.carousel change.owl.carousel resized.owl.carousel': $.proxy(function(e) { - if (!e.namespace) { - return; - } - - if (!this._core.settings || !this._core.settings.lazyLoad) { - return; - } - - if ((e.property && e.property.name == 'position') || e.type == 'initialized') { - var settings = this._core.settings, - n = (settings.center && Math.ceil(settings.items / 2) || settings.items), - i = ((settings.center && n * -1) || 0), - position = (e.property && e.property.value !== undefined ? e.property.value : this._core.current()) + i, - clones = this._core.clones().length, - load = $.proxy(function(i, v) { this.load(v) }, this); - //TODO: Need documentation for this new option - if (settings.lazyLoadEager > 0) { - n += settings.lazyLoadEager; - // If the carousel is looping also preload images that are to the "left" - if (settings.loop) { - position -= settings.lazyLoadEager; - n++; - } - } - - while (i++ < n) { - this.load(clones / 2 + this._core.relative(position)); - clones && $.each(this._core.clones(this._core.relative(position)), load); - position++; - } - } - }, this) - }; - - // set the default options - this._core.options = $.extend({}, Lazy.Defaults, this._core.options); - - // register event handler - this._core.$element.on(this._handlers); - }; - - /** - * Default options. - * @public - */ - Lazy.Defaults = { - lazyLoad: false, - lazyLoadEager: 0 - }; - - /** - * Loads all resources of an item at the specified position. - * @param {Number} position - The absolute position of the item. - * @protected - */ - Lazy.prototype.load = function(position) { - var $item = this._core.$stage.children().eq(position), - $elements = $item && $item.find('.owl-lazy'); - - if (!$elements || $.inArray($item.get(0), this._loaded) > -1) { - return; - } - - $elements.each($.proxy(function(index, element) { - var $element = $(element), image, - url = (window.devicePixelRatio > 1 && $element.attr('data-src-retina')) || $element.attr('data-src') || $element.attr('data-srcset'); - - this._core.trigger('load', { element: $element, url: url }, 'lazy'); - - if ($element.is('img')) { - $element.one('load.owl.lazy', $.proxy(function() { - $element.css('opacity', 1); - this._core.trigger('loaded', { element: $element, url: url }, 'lazy'); - }, this)).attr('src', url); - } else if ($element.is('source')) { - $element.one('load.owl.lazy', $.proxy(function() { - this._core.trigger('loaded', { element: $element, url: url }, 'lazy'); - }, this)).attr('srcset', url); - } else { - image = new Image(); - image.onload = $.proxy(function() { - $element.css({ - 'background-image': 'url("' + url + '")', - 'opacity': '1' - }); - this._core.trigger('loaded', { element: $element, url: url }, 'lazy'); - }, this); - image.src = url; - } - }, this)); - - this._loaded.push($item.get(0)); - }; - - /** - * Destroys the plugin. - * @public - */ - Lazy.prototype.destroy = function() { - var handler, property; - - for (handler in this.handlers) { - this._core.$element.off(handler, this.handlers[handler]); - } - for (property in Object.getOwnPropertyNames(this)) { - typeof this[property] != 'function' && (this[property] = null); - } - }; - - $.fn.owlCarousel.Constructor.Plugins.Lazy = Lazy; - -})(window.Zepto || window.jQuery, window, document); - -/** - * AutoHeight Plugin - * @version 2.3.4 - * @author Bartosz Wojciechowski - * @author David Deutsch - * @license The MIT License (MIT) - */ -;(function($, window, document, undefined) { - - /** - * Creates the auto height plugin. - * @class The Auto Height Plugin - * @param {Owl} carousel - The Owl Carousel - */ - var AutoHeight = function(carousel) { - /** - * Reference to the core. - * @protected - * @type {Owl} - */ - this._core = carousel; - - this._previousHeight = null; - - /** - * All event handlers. - * @protected - * @type {Object} - */ - this._handlers = { - 'initialized.owl.carousel refreshed.owl.carousel': $.proxy(function(e) { - if (e.namespace && this._core.settings.autoHeight) { - this.update(); - } - }, this), - 'changed.owl.carousel': $.proxy(function(e) { - if (e.namespace && this._core.settings.autoHeight && e.property.name === 'position'){ - this.update(); - } - }, this), - 'loaded.owl.lazy': $.proxy(function(e) { - if (e.namespace && this._core.settings.autoHeight - && e.element.closest('.' + this._core.settings.itemClass).index() === this._core.current()) { - this.update(); - } - }, this) - }; - - // set default options - this._core.options = $.extend({}, AutoHeight.Defaults, this._core.options); - - // register event handlers - this._core.$element.on(this._handlers); - this._intervalId = null; - var refThis = this; - - // These changes have been taken from a PR by gavrochelegnou proposed in #1575 - // and have been made compatible with the latest jQuery version - $(window).on('load', function() { - if (refThis._core.settings.autoHeight) { - refThis.update(); - } - }); - - // Autoresize the height of the carousel when window is resized - // When carousel has images, the height is dependent on the width - // and should also change on resize - $(window).resize(function() { - if (refThis._core.settings.autoHeight) { - if (refThis._intervalId != null) { - clearTimeout(refThis._intervalId); - } - - refThis._intervalId = setTimeout(function() { - refThis.update(); - }, 250); - } - }); - - }; - - /** - * Default options. - * @public - */ - AutoHeight.Defaults = { - autoHeight: false, - autoHeightClass: 'owl-height' - }; - - /** - * Updates the view. - */ - AutoHeight.prototype.update = function() { - var start = this._core._current, - end = start + this._core.settings.items, - lazyLoadEnabled = this._core.settings.lazyLoad, - visible = this._core.$stage.children().toArray().slice(start, end), - heights = [], - maxheight = 0; - - $.each(visible, function(index, item) { - heights.push($(item).height()); - }); - - maxheight = Math.max.apply(null, heights); - - if (maxheight <= 1 && lazyLoadEnabled && this._previousHeight) { - maxheight = this._previousHeight; - } - - this._previousHeight = maxheight; - - this._core.$stage.parent() - .height(maxheight) - .addClass(this._core.settings.autoHeightClass); - }; - - AutoHeight.prototype.destroy = function() { - var handler, property; - - for (handler in this._handlers) { - this._core.$element.off(handler, this._handlers[handler]); - } - for (property in Object.getOwnPropertyNames(this)) { - typeof this[property] !== 'function' && (this[property] = null); - } - }; - - $.fn.owlCarousel.Constructor.Plugins.AutoHeight = AutoHeight; - -})(window.Zepto || window.jQuery, window, document); - -/** - * Video Plugin - * @version 2.3.4 - * @author Bartosz Wojciechowski - * @author David Deutsch - * @license The MIT License (MIT) - */ -;(function($, window, document, undefined) { - - /** - * Creates the video plugin. - * @class The Video Plugin - * @param {Owl} carousel - The Owl Carousel - */ - var Video = function(carousel) { - /** - * Reference to the core. - * @protected - * @type {Owl} - */ - this._core = carousel; - - /** - * Cache all video URLs. - * @protected - * @type {Object} - */ - this._videos = {}; - - /** - * Current playing item. - * @protected - * @type {jQuery} - */ - this._playing = null; - - /** - * All event handlers. - * @todo The cloned content removale is too late - * @protected - * @type {Object} - */ - this._handlers = { - 'initialized.owl.carousel': $.proxy(function(e) { - if (e.namespace) { - this._core.register({ type: 'state', name: 'playing', tags: [ 'interacting' ] }); - } - }, this), - 'resize.owl.carousel': $.proxy(function(e) { - if (e.namespace && this._core.settings.video && this.isInFullScreen()) { - e.preventDefault(); - } - }, this), - 'refreshed.owl.carousel': $.proxy(function(e) { - if (e.namespace && this._core.is('resizing')) { - this._core.$stage.find('.cloned .owl-video-frame').remove(); - } - }, this), - 'changed.owl.carousel': $.proxy(function(e) { - if (e.namespace && e.property.name === 'position' && this._playing) { - this.stop(); - } - }, this), - 'prepared.owl.carousel': $.proxy(function(e) { - if (!e.namespace) { - return; - } - - var $element = $(e.content).find('.owl-video'); - - if ($element.length) { - $element.css('display', 'none'); - this.fetch($element, $(e.content)); - } - }, this) - }; - - // set default options - this._core.options = $.extend({}, Video.Defaults, this._core.options); - - // register event handlers - this._core.$element.on(this._handlers); - - this._core.$element.on('click.owl.video', '.owl-video-play-icon', $.proxy(function(e) { - this.play(e); - }, this)); - }; - - /** - * Default options. - * @public - */ - Video.Defaults = { - video: false, - videoHeight: false, - videoWidth: false - }; - - /** - * Gets the video ID and the type (YouTube/Vimeo/vzaar only). - * @protected - * @param {jQuery} target - The target containing the video data. - * @param {jQuery} item - The item containing the video. - */ - Video.prototype.fetch = function(target, item) { - var type = (function() { - if (target.attr('data-vimeo-id')) { - return 'vimeo'; - } else if (target.attr('data-vzaar-id')) { - return 'vzaar' - } else { - return 'youtube'; - } - })(), - id = target.attr('data-vimeo-id') || target.attr('data-youtube-id') || target.attr('data-vzaar-id'), - width = target.attr('data-width') || this._core.settings.videoWidth, - height = target.attr('data-height') || this._core.settings.videoHeight, - url = target.attr('href'); - - if (url) { - - /* - Parses the id's out of the following urls (and probably more): - https://www.youtube.com/watch?v=:id - https://youtu.be/:id - https://vimeo.com/:id - https://vimeo.com/channels/:channel/:id - https://vimeo.com/groups/:group/videos/:id - https://app.vzaar.com/videos/:id - - Visual example: https://regexper.com/#(http%3A%7Chttps%3A%7C)%5C%2F%5C%2F(player.%7Cwww.%7Capp.)%3F(vimeo%5C.com%7Cyoutu(be%5C.com%7C%5C.be%7Cbe%5C.googleapis%5C.com)%7Cvzaar%5C.com)%5C%2F(video%5C%2F%7Cvideos%5C%2F%7Cembed%5C%2F%7Cchannels%5C%2F.%2B%5C%2F%7Cgroups%5C%2F.%2B%5C%2F%7Cwatch%5C%3Fv%3D%7Cv%5C%2F)%3F(%5BA-Za-z0-9._%25-%5D*)(%5C%26%5CS%2B)%3F - */ - - id = url.match(/(http:|https:|)\/\/(player.|www.|app.)?(vimeo\.com|youtu(be\.com|\.be|be\.googleapis\.com|be\-nocookie\.com)|vzaar\.com)\/(video\/|videos\/|embed\/|channels\/.+\/|groups\/.+\/|watch\?v=|v\/)?([A-Za-z0-9._%-]*)(\&\S+)?/); - - if (id[3].indexOf('youtu') > -1) { - type = 'youtube'; - } else if (id[3].indexOf('vimeo') > -1) { - type = 'vimeo'; - } else if (id[3].indexOf('vzaar') > -1) { - type = 'vzaar'; - } else { - throw new Error('Video URL not supported.'); - } - id = id[6]; - } else { - throw new Error('Missing video URL.'); - } - - this._videos[url] = { - type: type, - id: id, - width: width, - height: height - }; - - item.attr('data-video', url); - - this.thumbnail(target, this._videos[url]); - }; - - /** - * Creates video thumbnail. - * @protected - * @param {jQuery} target - The target containing the video data. - * @param {Object} info - The video info object. - * @see `fetch` - */ - Video.prototype.thumbnail = function(target, video) { - var tnLink, - icon, - path, - dimensions = video.width && video.height ? 'width:' + video.width + 'px;height:' + video.height + 'px;' : '', - customTn = target.find('img'), - srcType = 'src', - lazyClass = '', - settings = this._core.settings, - create = function(path) { - icon = '
    '; - - if (settings.lazyLoad) { - tnLink = $('
    ',{ - "class": 'owl-video-tn ' + lazyClass, - "srcType": path - }); - } else { - tnLink = $( '
    ', { - "class": "owl-video-tn", - "style": 'opacity:1;background-image:url(' + path + ')' - }); - } - target.after(tnLink); - target.after(icon); - }; - - // wrap video content into owl-video-wrapper div - target.wrap( $( '
    ', { - "class": "owl-video-wrapper", - "style": dimensions - })); - - if (this._core.settings.lazyLoad) { - srcType = 'data-src'; - lazyClass = 'owl-lazy'; - } - - // custom thumbnail - if (customTn.length) { - create(customTn.attr(srcType)); - customTn.remove(); - return false; - } - - if (video.type === 'youtube') { - path = "//img.youtube.com/vi/" + video.id + "/hqdefault.jpg"; - create(path); - } else if (video.type === 'vimeo') { - $.ajax({ - type: 'GET', - url: '//vimeo.com/api/v2/video/' + video.id + '.json', - jsonp: 'callback', - dataType: 'jsonp', - success: function(data) { - path = data[0].thumbnail_large; - create(path); - } - }); - } else if (video.type === 'vzaar') { - $.ajax({ - type: 'GET', - url: '//vzaar.com/api/videos/' + video.id + '.json', - jsonp: 'callback', - dataType: 'jsonp', - success: function(data) { - path = data.framegrab_url; - create(path); - } - }); - } - }; - - /** - * Stops the current video. - * @public - */ - Video.prototype.stop = function() { - this._core.trigger('stop', null, 'video'); - this._playing.find('.owl-video-frame').remove(); - this._playing.removeClass('owl-video-playing'); - this._playing = null; - this._core.leave('playing'); - this._core.trigger('stopped', null, 'video'); - }; - - /** - * Starts the current video. - * @public - * @param {Event} event - The event arguments. - */ - Video.prototype.play = function(event) { - var target = $(event.target), - item = target.closest('.' + this._core.settings.itemClass), - video = this._videos[item.attr('data-video')], - width = video.width || '100%', - height = video.height || this._core.$stage.height(), - html, - iframe; - - if (this._playing) { - return; - } - - this._core.enter('playing'); - this._core.trigger('play', null, 'video'); - - item = this._core.items(this._core.relative(item.index())); - - this._core.reset(item.index()); - - html = $( '' ); - html.attr( 'height', height ); - html.attr( 'width', width ); - if (video.type === 'youtube') { - html.attr( 'src', '//www.youtube.com/embed/' + video.id + '?autoplay=1&rel=0&v=' + video.id ); - } else if (video.type === 'vimeo') { - html.attr( 'src', '//player.vimeo.com/video/' + video.id + '?autoplay=1' ); - } else if (video.type === 'vzaar') { - html.attr( 'src', '//view.vzaar.com/' + video.id + '/player?autoplay=true' ); - } - - iframe = $(html).wrap( '
    ' ).insertAfter(item.find('.owl-video')); - - this._playing = item.addClass('owl-video-playing'); - }; - - /** - * Checks whether an video is currently in full screen mode or not. - * @todo Bad style because looks like a readonly method but changes members. - * @protected - * @returns {Boolean} - */ - Video.prototype.isInFullScreen = function() { - var element = document.fullscreenElement || document.mozFullScreenElement || - document.webkitFullscreenElement; - - return element && $(element).parent().hasClass('owl-video-frame'); - }; - - /** - * Destroys the plugin. - */ - Video.prototype.destroy = function() { - var handler, property; - - this._core.$element.off('click.owl.video'); - - for (handler in this._handlers) { - this._core.$element.off(handler, this._handlers[handler]); - } - for (property in Object.getOwnPropertyNames(this)) { - typeof this[property] != 'function' && (this[property] = null); - } - }; - - $.fn.owlCarousel.Constructor.Plugins.Video = Video; - -})(window.Zepto || window.jQuery, window, document); - -/** - * Animate Plugin - * @version 2.3.4 - * @author Bartosz Wojciechowski - * @author David Deutsch - * @license The MIT License (MIT) - */ -;(function($, window, document, undefined) { - - /** - * Creates the animate plugin. - * @class The Navigation Plugin - * @param {Owl} scope - The Owl Carousel - */ - var Animate = function(scope) { - this.core = scope; - this.core.options = $.extend({}, Animate.Defaults, this.core.options); - this.swapping = true; - this.previous = undefined; - this.next = undefined; - - this.handlers = { - 'change.owl.carousel': $.proxy(function(e) { - if (e.namespace && e.property.name == 'position') { - this.previous = this.core.current(); - this.next = e.property.value; - } - }, this), - 'drag.owl.carousel dragged.owl.carousel translated.owl.carousel': $.proxy(function(e) { - if (e.namespace) { - this.swapping = e.type == 'translated'; - } - }, this), - 'translate.owl.carousel': $.proxy(function(e) { - if (e.namespace && this.swapping && (this.core.options.animateOut || this.core.options.animateIn)) { - this.swap(); - } - }, this) - }; - - this.core.$element.on(this.handlers); - }; - - /** - * Default options. - * @public - */ - Animate.Defaults = { - animateOut: false, - animateIn: false - }; - - /** - * Toggles the animation classes whenever an translations starts. - * @protected - * @returns {Boolean|undefined} - */ - Animate.prototype.swap = function() { - - if (this.core.settings.items !== 1) { - return; - } - - if (!$.support.animation || !$.support.transition) { - return; - } - - this.core.speed(0); - - var left, - clear = $.proxy(this.clear, this), - previous = this.core.$stage.children().eq(this.previous), - next = this.core.$stage.children().eq(this.next), - incoming = this.core.settings.animateIn, - outgoing = this.core.settings.animateOut; - - if (this.core.current() === this.previous) { - return; - } - - if (outgoing) { - left = this.core.coordinates(this.previous) - this.core.coordinates(this.next); - previous.one($.support.animation.end, clear) - .css( { 'left': left + 'px' } ) - .addClass('animated owl-animated-out') - .addClass(outgoing); - } - - if (incoming) { - next.one($.support.animation.end, clear) - .addClass('animated owl-animated-in') - .addClass(incoming); - } - }; - - Animate.prototype.clear = function(e) { - $(e.target).css( { 'left': '' } ) - .removeClass('animated owl-animated-out owl-animated-in') - .removeClass(this.core.settings.animateIn) - .removeClass(this.core.settings.animateOut); - this.core.onTransitionEnd(); - }; - - /** - * Destroys the plugin. - * @public - */ - Animate.prototype.destroy = function() { - var handler, property; - - for (handler in this.handlers) { - this.core.$element.off(handler, this.handlers[handler]); - } - for (property in Object.getOwnPropertyNames(this)) { - typeof this[property] != 'function' && (this[property] = null); - } - }; - - $.fn.owlCarousel.Constructor.Plugins.Animate = Animate; - -})(window.Zepto || window.jQuery, window, document); - -/** - * Autoplay Plugin - * @version 2.3.4 - * @author Bartosz Wojciechowski - * @author Artus Kolanowski - * @author David Deutsch - * @author Tom De Caluwé - * @license The MIT License (MIT) - */ -;(function($, window, document, undefined) { - - /** - * Creates the autoplay plugin. - * @class The Autoplay Plugin - * @param {Owl} scope - The Owl Carousel - */ - var Autoplay = function(carousel) { - /** - * Reference to the core. - * @protected - * @type {Owl} - */ - this._core = carousel; - - /** - * The autoplay timeout id. - * @type {Number} - */ - this._call = null; - - /** - * Depending on the state of the plugin, this variable contains either - * the start time of the timer or the current timer value if it's - * paused. Since we start in a paused state we initialize the timer - * value. - * @type {Number} - */ - this._time = 0; - - /** - * Stores the timeout currently used. - * @type {Number} - */ - this._timeout = 0; - - /** - * Indicates whenever the autoplay is paused. - * @type {Boolean} - */ - this._paused = true; - - /** - * All event handlers. - * @protected - * @type {Object} - */ - this._handlers = { - 'changed.owl.carousel': $.proxy(function(e) { - if (e.namespace && e.property.name === 'settings') { - if (this._core.settings.autoplay) { - this.play(); - } else { - this.stop(); - } - } else if (e.namespace && e.property.name === 'position' && this._paused) { - // Reset the timer. This code is triggered when the position - // of the carousel was changed through user interaction. - this._time = 0; - } - }, this), - 'initialized.owl.carousel': $.proxy(function(e) { - if (e.namespace && this._core.settings.autoplay) { - this.play(); - } - }, this), - 'play.owl.autoplay': $.proxy(function(e, t, s) { - if (e.namespace) { - this.play(t, s); - } - }, this), - 'stop.owl.autoplay': $.proxy(function(e) { - if (e.namespace) { - this.stop(); - } - }, this), - 'mouseover.owl.autoplay': $.proxy(function() { - if (this._core.settings.autoplayHoverPause && this._core.is('rotating')) { - this.pause(); - } - }, this), - 'mouseleave.owl.autoplay': $.proxy(function() { - if (this._core.settings.autoplayHoverPause && this._core.is('rotating')) { - this.play(); - } - }, this), - 'touchstart.owl.core': $.proxy(function() { - if (this._core.settings.autoplayHoverPause && this._core.is('rotating')) { - this.pause(); - } - }, this), - 'touchend.owl.core': $.proxy(function() { - if (this._core.settings.autoplayHoverPause) { - this.play(); - } - }, this) - }; - - // register event handlers - this._core.$element.on(this._handlers); - - // set default options - this._core.options = $.extend({}, Autoplay.Defaults, this._core.options); - }; - - /** - * Default options. - * @public - */ - Autoplay.Defaults = { - autoplay: false, - autoplayTimeout: 5000, - autoplayHoverPause: false, - autoplaySpeed: false - }; - - /** - * Transition to the next slide and set a timeout for the next transition. - * @private - * @param {Number} [speed] - The animation speed for the animations. - */ - Autoplay.prototype._next = function(speed) { - this._call = window.setTimeout( - $.proxy(this._next, this, speed), - this._timeout * (Math.round(this.read() / this._timeout) + 1) - this.read() - ); - - if (this._core.is('interacting') || document.hidden) { - return; - } - this._core.next(speed || this._core.settings.autoplaySpeed); - } - - /** - * Reads the current timer value when the timer is playing. - * @public - */ - Autoplay.prototype.read = function() { - return new Date().getTime() - this._time; - }; - - /** - * Starts the autoplay. - * @public - * @param {Number} [timeout] - The interval before the next animation starts. - * @param {Number} [speed] - The animation speed for the animations. - */ - Autoplay.prototype.play = function(timeout, speed) { - var elapsed; - - if (!this._core.is('rotating')) { - this._core.enter('rotating'); - } - - timeout = timeout || this._core.settings.autoplayTimeout; - - // Calculate the elapsed time since the last transition. If the carousel - // wasn't playing this calculation will yield zero. - elapsed = Math.min(this._time % (this._timeout || timeout), timeout); - - if (this._paused) { - // Start the clock. - this._time = this.read(); - this._paused = false; - } else { - // Clear the active timeout to allow replacement. - window.clearTimeout(this._call); - } - - // Adjust the origin of the timer to match the new timeout value. - this._time += this.read() % timeout - elapsed; - - this._timeout = timeout; - this._call = window.setTimeout($.proxy(this._next, this, speed), timeout - elapsed); - }; - - /** - * Stops the autoplay. - * @public - */ - Autoplay.prototype.stop = function() { - if (this._core.is('rotating')) { - // Reset the clock. - this._time = 0; - this._paused = true; - - window.clearTimeout(this._call); - this._core.leave('rotating'); - } - }; - - /** - * Pauses the autoplay. - * @public - */ - Autoplay.prototype.pause = function() { - if (this._core.is('rotating') && !this._paused) { - // Pause the clock. - this._time = this.read(); - this._paused = true; - - window.clearTimeout(this._call); - } - }; - - /** - * Destroys the plugin. - */ - Autoplay.prototype.destroy = function() { - var handler, property; - - this.stop(); - - for (handler in this._handlers) { - this._core.$element.off(handler, this._handlers[handler]); - } - for (property in Object.getOwnPropertyNames(this)) { - typeof this[property] != 'function' && (this[property] = null); - } - }; - - $.fn.owlCarousel.Constructor.Plugins.autoplay = Autoplay; - -})(window.Zepto || window.jQuery, window, document); - -/** - * Navigation Plugin - * @version 2.3.4 - * @author Artus Kolanowski - * @author David Deutsch - * @license The MIT License (MIT) - */ -;(function($, window, document, undefined) { - 'use strict'; - - /** - * Creates the navigation plugin. - * @class The Navigation Plugin - * @param {Owl} carousel - The Owl Carousel. - */ - var Navigation = function(carousel) { - /** - * Reference to the core. - * @protected - * @type {Owl} - */ - this._core = carousel; - - /** - * Indicates whether the plugin is initialized or not. - * @protected - * @type {Boolean} - */ - this._initialized = false; - - /** - * The current paging indexes. - * @protected - * @type {Array} - */ - this._pages = []; - - /** - * All DOM elements of the user interface. - * @protected - * @type {Object} - */ - this._controls = {}; - - /** - * Markup for an indicator. - * @protected - * @type {Array.} - */ - this._templates = []; - - /** - * The carousel element. - * @type {jQuery} - */ - this.$element = this._core.$element; - - /** - * Overridden methods of the carousel. - * @protected - * @type {Object} - */ - this._overrides = { - next: this._core.next, - prev: this._core.prev, - to: this._core.to - }; - - /** - * All event handlers. - * @protected - * @type {Object} - */ - this._handlers = { - 'prepared.owl.carousel': $.proxy(function(e) { - if (e.namespace && this._core.settings.dotsData) { - this._templates.push('
    ' + - $(e.content).find('[data-dot]').addBack('[data-dot]').attr('data-dot') + '
    '); - } - }, this), - 'added.owl.carousel': $.proxy(function(e) { - if (e.namespace && this._core.settings.dotsData) { - this._templates.splice(e.position, 0, this._templates.pop()); - } - }, this), - 'remove.owl.carousel': $.proxy(function(e) { - if (e.namespace && this._core.settings.dotsData) { - this._templates.splice(e.position, 1); - } - }, this), - 'changed.owl.carousel': $.proxy(function(e) { - if (e.namespace && e.property.name == 'position') { - this.draw(); - } - }, this), - 'initialized.owl.carousel': $.proxy(function(e) { - if (e.namespace && !this._initialized) { - this._core.trigger('initialize', null, 'navigation'); - this.initialize(); - this.update(); - this.draw(); - this._initialized = true; - this._core.trigger('initialized', null, 'navigation'); - } - }, this), - 'refreshed.owl.carousel': $.proxy(function(e) { - if (e.namespace && this._initialized) { - this._core.trigger('refresh', null, 'navigation'); - this.update(); - this.draw(); - this._core.trigger('refreshed', null, 'navigation'); - } - }, this) - }; - - // set default options - this._core.options = $.extend({}, Navigation.Defaults, this._core.options); - - // register event handlers - this.$element.on(this._handlers); - }; - - /** - * Default options. - * @public - * @todo Rename `slideBy` to `navBy` - */ - Navigation.Defaults = { - nav: false, - navText: [ - '‹', - '›' - ], - navSpeed: false, - navElement: 'button type="button" role="presentation"', - navContainer: false, - navContainerClass: 'owl-nav', - navClass: [ - 'owl-prev', - 'owl-next' - ], - slideBy: 1, - dotClass: 'owl-dot', - dotsClass: 'owl-dots', - dots: true, - dotsEach: false, - dotsData: false, - dotsSpeed: false, - dotsContainer: false - }; - - /** - * Initializes the layout of the plugin and extends the carousel. - * @protected - */ - Navigation.prototype.initialize = function() { - var override, - settings = this._core.settings; - - // create DOM structure for relative navigation - this._controls.$relative = (settings.navContainer ? $(settings.navContainer) - : $('
    ').addClass(settings.navContainerClass).appendTo(this.$element)).addClass('disabled'); - - this._controls.$previous = $('<' + settings.navElement + '>') - .addClass(settings.navClass[0]) - .html(settings.navText[0]) - .prependTo(this._controls.$relative) - .on('click', $.proxy(function(e) { - this.prev(settings.navSpeed); - }, this)); - this._controls.$next = $('<' + settings.navElement + '>') - .addClass(settings.navClass[1]) - .html(settings.navText[1]) - .appendTo(this._controls.$relative) - .on('click', $.proxy(function(e) { - this.next(settings.navSpeed); - }, this)); - - // create DOM structure for absolute navigation - if (!settings.dotsData) { - this._templates = [ $('