diff --git a/.github/workflows/auto-pr.yml b/.github/workflows/auto-pr.yml index 86edede7d6..d4cd30c876 100644 --- a/.github/workflows/auto-pr.yml +++ b/.github/workflows/auto-pr.yml @@ -1,10 +1,10 @@ -name: Merge branch dev with rel-4.3 +name: Merge branch dev with rel-4.4 on: push: branches: - - rel-4.3 + - rel-4.4 jobs: - merge-dev-with-rel-4-3: + merge-dev-with-rel-4-4: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -12,13 +12,13 @@ jobs: ref: dev - name: Reset promotion branch run: | - git fetch origin rel-4.3:rel-4.3 - git reset --hard rel-4.3 + git fetch origin rel-4.4:rel-4.4 + git reset --hard rel-4.4 - name: Create Pull Request uses: peter-evans/create-pull-request@v3 with: - branch: auto-merge/rel-4-3/${{github.run_number}} - title: Merge branch dev with rel-4.3 - body: This PR generated automatically to merge dev with rel-4.3. Please review the changed files before merging to prevent any errors that may occur. + branch: auto-merge/rel-4-4/${{github.run_number}} + title: Merge branch dev with rel-4.4 + body: This PR generated automatically to merge dev with rel-4.4. Please review the changed files before merging to prevent any errors that may occur. reviewers: ${{github.actor}} token: ${{ github.token }} diff --git a/.gitignore b/.gitignore index f3f48c5963..c0347428f2 100644 --- a/.gitignore +++ b/.gitignore @@ -315,3 +315,6 @@ tempkey.jwk # ABP Analyzing files *.abppkg.analyze.json + +# appsettings.secrets.json +appsettings.secrets.json \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/it.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/it.json index 6dada8fba7..6944813279 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/it.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/it.json @@ -2,13 +2,13 @@ "culture": "it", "texts": { "Account": "Account ABP - Accesso e registrazione | ABP.IO", - "Welcome": "benvenuto", + "Welcome": "Benvenuto", "UseOneOfTheFollowingLinksToContinue": "Usa uno dei seguenti link per continuare", "FrameworkHomePage": "Home page del framework", "FrameworkDocumentation": "Documentazione del framework", "OfficialBlog": "Blog ufficiale", - "CommercialHomePage": "Home page commerciale", - "CommercialSupportWebSite": "Sito web di supporto commerciale", + "CommercialHomePage": "Home page di Commercial", + "CommercialSupportWebSite": "Sito web supporto di Commercial", "CommunityWebSite": "Sito web della comunità ABP" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/sk.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/sk.json new file mode 100644 index 0000000000..2acb1c9e30 --- /dev/null +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/sk.json @@ -0,0 +1,14 @@ +{ + "culture": "sk", + "texts": { + "Account": "Účet ABP - Prihlásenie a registrácia | ABP.IO", + "Welcome": "Vitajte", + "UseOneOfTheFollowingLinksToContinue": "Ak chcete pokračovať, použite jeden z nasledujúcich odkazov", + "FrameworkHomePage": "Domovská stránka frameworku", + "FrameworkDocumentation": "Dokumentácia k frameworku", + "OfficialBlog": "Oficiálny blog", + "CommercialHomePage": "Komerčná domovská stránka", + "CommercialSupportWebSite": "Webová stránka komerčnej podpory", + "CommunityWebSite": "Webová stránka ABP komunity" + } +} \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en-GB.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en-GB.json index 970e04d24e..4ea99af7e1 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en-GB.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en-GB.json @@ -197,6 +197,19 @@ "RemoveCache": "Remove Cache", "Language": "Language", "Optional": "Optional", - "CreateArticleLanguageInfo": "The language in which the article is written" + "CreateArticleLanguageInfo": "The language in which the article is written", + "Menu:Quotation": "Quotation", + "Menu:Invoice": "Invoice", + "Menu:PaymentRequests": "Payment Requests", + "Permission:PaymentRequests": "Payment Requests", + "PaymentRequests": "Payment Requests", + "Creator": "Creator", + "ExtraProperties": "Extra Properties", + "Organization": "Organization", + "Waiting": "Waiting", + "Completed": "Completed", + "Failed": "Failed", + "PaymentRequestDeletionWarningMessage": "This payment request will be deleted. Do you confirm that?", + "Payment": "Payment" } } \ 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 0cfd2a477a..10ca9a0a67 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json @@ -277,6 +277,32 @@ "ThisExtensionIsNotAllowed": "This extension is not allowed.", "TheFileIsTooLarge": "The file is too large!", "ArticleDeletionConfirmationMessage": "Are you sure you want to hard delete this article?", - "ChooseCoverImage": "Choose a cover image..." + "ChooseCoverImage": "Choose a cover image...", + "Menu:Quotation": "Quotation", + "Menu:Invoice": "Invoice", + "Menu:PaymentRequests": "Payment Requests", + "Permission:PaymentRequests": "Payment Requests", + "PaymentRequests": "Payment Requests", + "Creator": "Creator", + "ExtraProperties": "Extra Properties", + "Organization": "Organization", + "Waiting": "Waiting", + "Completed": "Completed", + "Failed": "Failed", + "PaymentRequestDeletionWarningMessage": "This payment request will be deleted. Do you confirm that?", + "Payment": "Payment", + "AddPayment": "Add Payment", + "Enum:PurchaseType:1": "New License", + "Enum:PurchaseType:2": "License Extend", + "Enum:PurchaseType:3": "License Upgrade", + "Enum:PurchaseType:4": "Additional Developer", + "LicenceType": "License Type", + "PurchaseType": "Purchase Type", + "ReceiptNo": "Receipt No", + "PaymentTime": "Payment Time", + "ProductPrice": "Product Price", + "AdditionalDeveloper": "Additional developer", + "ThisPaymentHasBeenAlreadyUsed": "This payment has been already used", + "PaymentTimeCannotBeFutureTime": "Payment time cannot be future time" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/fi.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/fi.json index 488e07a1d0..76904c06d4 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/fi.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/fi.json @@ -108,12 +108,12 @@ "LicenseStartDate": "Lisenssin aloituspäivä", "LicenseEndDate": "Lisenssin päättymispäivä", "OrganizationNamePlaceholder": "Organisaation nimi...", - "TotalQuestionCountPlaceholder": "Kysymysten kokonaismäärä ...", - "RemainingQuestionCountPlaceholder": "Jäljellä oleva kysymysten määrä ...", + "TotalQuestionCountPlaceholder": "Kysymysten kokonaismäärä...", + "RemainingQuestionCountPlaceholder": "Jäljellä oleva kysymysten määrä...", "LicenseTypePlaceholder": "Lisenssi-tyyppi...", "CreationDatePlaceholder": "Luomispäivä...", - "LicenseStartDatePlaceholder": "Lisenssin aloituspäivä ...", - "LicenseEndDatePlaceholder": "Lisenssin päättymispäivä ...", + "LicenseStartDatePlaceholder": "Lisenssin aloituspäivä...", + "LicenseEndDatePlaceholder": "Lisenssin päättymispäivä...", "UsernameOrEmail": "Käyttäjätunnus tai sähköpostiosoite", "UsernameOrEmailPlaceholder": "Käyttäjätunnus tai sähköpostiosoite...", "Member": "Jäsen", @@ -277,6 +277,6 @@ "ThisExtensionIsNotAllowed": "Tätä laajennusta ei sallita.", "TheFileIsTooLarge": "Tiedosto on liian suuri!", "ArticleDeletionConfirmationMessage": "Haluatko varmasti poistaa tämän artikkelin vaikeasti?", - "ChooseCoverImage": "Valitse kansikuva ..." + "ChooseCoverImage": "Valitse kansikuva..." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/fr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/fr.json index 2a2d8ba568..189ea8a482 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/fr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/fr.json @@ -108,12 +108,12 @@ "LicenseStartDate": "Date de début de la licence", "LicenseEndDate": "Date de fin de licence", "OrganizationNamePlaceholder": "Nom de l'organisation...", - "TotalQuestionCountPlaceholder": "Nombre total de questions ...", - "RemainingQuestionCountPlaceholder": "Nombre de questions restantes ...", + "TotalQuestionCountPlaceholder": "Nombre total de questions...", + "RemainingQuestionCountPlaceholder": "Nombre de questions restantes...", "LicenseTypePlaceholder": "Type de licence...", "CreationDatePlaceholder": "Date de création...", - "LicenseStartDatePlaceholder": "Date de début de la licence ...", - "LicenseEndDatePlaceholder": "Date de fin de licence ...", + "LicenseStartDatePlaceholder": "Date de début de la licence...", + "LicenseEndDatePlaceholder": "Date de fin de licence...", "UsernameOrEmail": "Nom d'utilisateur ou email", "UsernameOrEmailPlaceholder": "Nom d'utilisateur ou email...", "Member": "Membre", @@ -277,6 +277,6 @@ "ThisExtensionIsNotAllowed": "Cette extension n'est pas autorisée.", "TheFileIsTooLarge": "Le fichier est trop volumineux!", "ArticleDeletionConfirmationMessage": "Voulez-vous vraiment supprimer définitivement cet article?", - "ChooseCoverImage": "Choisissez une image de couverture ..." + "ChooseCoverImage": "Choisissez une image de couverture..." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/hi.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/hi.json index 7b0f4ad205..e5beeb11f7 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/hi.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/hi.json @@ -108,12 +108,12 @@ "LicenseStartDate": "लाइसेंस शुरू होने की तारीख", "LicenseEndDate": "लाइसेंस की अंतिम तिथि", "OrganizationNamePlaceholder": "संगठन का नाम...", - "TotalQuestionCountPlaceholder": "कुल प्रश्न संख्या ...", - "RemainingQuestionCountPlaceholder": "शेष प्रश्न गणना ...", + "TotalQuestionCountPlaceholder": "कुल प्रश्न संख्या...", + "RemainingQuestionCountPlaceholder": "शेष प्रश्न गणना...", "LicenseTypePlaceholder": "लाइसेंस के प्रकार...", "CreationDatePlaceholder": "निर्माण तिथि...", - "LicenseStartDatePlaceholder": "लाइसेंस शुरू होने की तारीख ...", - "LicenseEndDatePlaceholder": "लाइसेंस की अंतिम तिथि ...", + "LicenseStartDatePlaceholder": "लाइसेंस शुरू होने की तारीख...", + "LicenseEndDatePlaceholder": "लाइसेंस की अंतिम तिथि...", "UsernameOrEmail": "उपयोगकर्ता का नाम या ईमेल", "UsernameOrEmailPlaceholder": "उपयोगकर्ता का नाम या ईमेल...", "Member": "सदस्य", @@ -277,6 +277,6 @@ "ThisExtensionIsNotAllowed": "इस एक्सटेंशन की अनुमति नहीं है।", "TheFileIsTooLarge": "फ़ाइल बहुत बड़ी है!", "ArticleDeletionConfirmationMessage": "क्या आप वाकई इस लेख को हटाना चाहते हैं?", - "ChooseCoverImage": "कवर छवि चुनें ..." + "ChooseCoverImage": "कवर छवि चुनें..." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/it.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/it.json index 2aaacd7b86..04f82726ba 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/it.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/it.json @@ -5,20 +5,20 @@ "Permission:Manage": "Gestisci organizzazioni", "Permission:DiscountRequests": "Richieste di sconto", "Permission:DiscountManage": "Gestisci le richieste di sconto", - "Permission:Disable": "disattivare", - "Permission:Enable": "Abilitare", + "Permission:Disable": "Disattiva", + "Permission:Enable": "Abilita", "Permission:EnableSendEmail": "Abilita Invia e-mail", "Permission:SendEmail": "Invia una email", "Permission:NpmPackages": "Pacchetti NPM", "Permission:NugetPackages": "Pacchetti Nuget", "Permission:Maintenance": "Manutenzione", - "Permission:Maintain": "Mantenere", - "Permission:ClearCaches": "Cancella cache", + "Permission:Maintain": "Mantieni", + "Permission:ClearCaches": "Pulisci cache", "Permission:Modules": "Moduli", "Permission:Packages": "Pacchi", - "Permission:Edit": "modificare", + "Permission:Edit": "Modifica", "Permission:Delete": "Elimina", - "Permission:Create": "Creare", + "Permission:Create": "Crea", "Permission:Accounting": "Contabilità", "Permission:Accounting:Quotation": "Quotazione", "Permission:Accounting:Invoice": "Fattura", @@ -30,31 +30,31 @@ "NugetPackageDeletionWarningMessage": "Questo pacchetto Nuget verrà eliminato. Lo confermi?", "ModuleDeletionWarningMessage": "Questo modulo verrà eliminato. Lo confermi?", "Name": "Nome", - "DisplayName": "Nome da visualizzare", + "DisplayName": "Nome visualizzato", "ShortDescription": "Breve descrizione", "NameFilter": "Nome", - "CreationTime": "Tempo di creazione", + "CreationTime": "Orario di creazione", "IsPro": "È pro", "IsFreeToActiveLicenseOwners": "Gratuito per i proprietari di licenze", "ShowOnModuleList": "Mostra nell'elenco dei moduli", "EfCoreConfigureMethodName": "Configurare il nome del metodo", "IsProFilter": "È pro", "ApplicationType": "Tipo di applicazione", - "Target": "Bersaglio", - "TargetFilter": "Bersaglio", + "Target": "Obiettivo", + "TargetFilter": "Obiettivo", "ModuleClass": "Classe del modulo", "NugetPackageTarget.DomainShared": "Dominio condiviso", "NugetPackageTarget.Domain": "Dominio", "NugetPackageTarget.Application": "Applicazione", - "NugetPackageTarget.ApplicationContracts": "Contratti applicativi", + "NugetPackageTarget.ApplicationContracts": "Contratti Applicazione", "NugetPackageTarget.HttpApi": "Http Api", - "NugetPackageTarget.HttpApiClient": "Client API HTTP", - "NugetPackageTarget.Web": "ragnatela", + "NugetPackageTarget.HttpApiClient": "Http Api Client", + "NugetPackageTarget.Web": "Web", "NugetPackageTarget.EntityFrameworkCore": "DeleteAllEntityFramework Core", "NugetPackageTarget.MongoDB": "MongoDB", - "Edit": "modificare", + "Edit": "Modifica", "Delete": "Elimina", - "Refresh": "ricaricare", + "Refresh": "Ricarica", "NpmPackages": "Pacchetti NPM", "NugetPackages": "Pacchetti Nuget", "NpmPackageCount": "Conteggio pacchetti NPM", @@ -64,10 +64,10 @@ "CreateANpmPackage": "Crea un pacchetto NPM", "CreateAModule": "Crea un modulo", "CreateANugetPackage": "Crea un pacchetto Nuget", - "AddNew": "Aggiungere nuova", - "PackageAlreadyExist{0}": "Il pacchetto \"{0}\" è già aggiunto.", - "ModuleAlreadyExist{0}": "Il modulo \"{0}\" è già aggiunto.", - "ClearCache": "Cancella cache", + "AddNew": "Aggiungi nuova", + "PackageAlreadyExist{0}": "Il pacchetto \"{0}\" è già stato aggiunto.", + "ModuleAlreadyExist{0}": "Il modulo \"{0}\" è già stato aggiunto.", + "ClearCache": "Pulisci cache", "SuccessfullyCleared": "Cancellato con successo", "Menu:NpmPackages": "Pacchetti NPM", "Menu:Modules": "Moduli", @@ -75,19 +75,19 @@ "Menu:NugetPackages": "Pacchetti Nuget", "CreateAnOrganization": "Crea un'organizzazione", "Organizations": "Organizzazioni", - "LongName": "Nome lungo", + "LongName": "Nome esteso", "LicenseType": "Tipo di licenza", "MissingLicenseTypeField": "Il campo del tipo di licenza è obbligatorio!", "LicenseStartTime": "Ora di inizio della licenza", "LicenseEndTime": "Ora di fine della licenza", "AllowedDeveloperCount": "Conteggio sviluppatori consentito", - "UserNameOrEmailAddress": "Username o Indirizzo Email", + "UserNameOrEmailAddress": "Username o indirizzo Email", "AddOwner": "Aggiungi proprietario", "UserName": "Nome utente", "Email": "E-mail", "Developers": "Sviluppatori", "AddDeveloper": "Aggiungi sviluppatore", - "Create": "Creare", + "Create": "Crea", "UserNotFound": "Utente non trovato", "{0}WillBeRemovedFromDevelopers": "{0} Verrà rimosso dagli sviluppatori, confermi?", "{0}WillBeRemovedFromOwners": "{0} Verrà rimosso dai proprietari, confermi?", @@ -95,7 +95,7 @@ "Computers": "Computer", "UniqueComputerId": "ID computer univoco", "LastSeenDate": "Data dell'ultimo accesso", - "{0}Computer{1}WillBeRemovedFromRecords": "Il computer di {0} ({1}) verrà rimosso dai record", + "{0}Computer{1}WillBeRemovedFromRecords": "Il computer di {0} ({1}) verrà rimosso da quelli registrati", "OrganizationDeletionWarningMessage": "L'organizzazione verrà eliminata", "DeletingLastOwnerWarningMessage": "Un'organizzazione deve avere almeno un proprietario! Pertanto non puoi rimuovere questo proprietario", "This{0}AlreadyExistInThisOrganization": "Questo {0} esiste già in questa organizzazione", @@ -108,12 +108,12 @@ "LicenseStartDate": "Data di inizio della licenza", "LicenseEndDate": "Data di fine della licenza", "OrganizationNamePlaceholder": "Nome dell'organizzazione...", - "TotalQuestionCountPlaceholder": "Conteggio totale delle domande ...", - "RemainingQuestionCountPlaceholder": "Numero di domande rimanenti ...", + "TotalQuestionCountPlaceholder": "Conteggio totale delle domande...", + "RemainingQuestionCountPlaceholder": "Numero di domande rimanenti...", "LicenseTypePlaceholder": "Tipo di licenza...", "CreationDatePlaceholder": "Data di creazione...", - "LicenseStartDatePlaceholder": "Data di inizio della licenza ...", - "LicenseEndDatePlaceholder": "Data di fine della licenza ...", + "LicenseStartDatePlaceholder": "Data di inizio della licenza...", + "LicenseEndDatePlaceholder": "Data di fine della licenza...", "UsernameOrEmail": "Nome utente o email", "UsernameOrEmailPlaceholder": "Nome utente o email...", "Member": "Membro", @@ -126,8 +126,8 @@ "DiscountQuantity": "Quantità di sconto", "DiscountPrice": "Prezzo scontato", "Quotation": "Quotazione", - "ExtraText": "Testo extra", - "ExtraAmount": "Importo extra", + "ExtraText": "Testo ulteriore", + "ExtraAmount": "Importo ulteriore", "DownloadQuotation": "Scarica preventivo", "Invoice": "Fattura", "TaxNumber": "Codice fiscale", @@ -138,22 +138,22 @@ "AddProduct": "Aggiungi prodotto", "AddProductWarning": "Devi aggiungere il prodotto!", "TotalPrice": "Prezzo totale", - "Generate": "creare", + "Generate": "Genera", "MissingQuantityField": "Il campo della quantità è obbligatorio!", "MissingPriceField": "Il campo Prezzo è obbligatorio!", "CodeUsageStatus": "Stato", "Country": "Nazione", "DeveloperCount": "Conteggio sviluppatori", - "RequestCode": "Codice richiesto", + "RequestCode": "Codice richiesta", "WebSite": "Sito web", "GithubUsername": "Nome utente GitHub", "PhoneNumber": "Numero di telefono", "ProjectDescription": "descrizione del progetto", - "Referrer": "Referrer", + "Referrer": "Referente", "DiscountRequests": "Richiesta di sconto", "Copylink": "Copia link", - "Disable": "disattivare", - "Enable": "Abilitare", + "Disable": "Disabilita", + "Enable": "Abilita", "EnableSendEmail": "Abilita Invia e-mail", "SendEmail": "Invia una email", "SuccessfullyDisabled": "Disabilitato con successo", @@ -162,7 +162,7 @@ "SuccessfullySent": "Inviato con successo", "SuccessfullyDeleted": "Eliminato con successo", "DiscountRequestDeletionWarningMessage": "La richiesta di sconto verrà eliminata", - "BusinessType": "tipo di affari", + "BusinessType": "Tipo di affari", "TotalQuestionCount": "Conteggio totale delle domande", "RemainingQuestionCount": "Numero di domande rimanenti", "TotalQuestionMustBeGreaterWarningMessage": "TotalQuestionCount deve essere maggiore di RemainingQuestionCount!", @@ -171,16 +171,16 @@ "Notes": "Appunti", "Menu:Community": "Comunità", "Menu:Articles": "Articoli", - "Wait": "Aspettare", + "Wait": "Attendere", "Approve": "Approvare", "Reject": "Rifiutare", "Details": "Dettagli", "Url": "Url", "Title": "Titolo", - "ContentSource": "Fonte di contenuto", + "ContentSource": "Fonte del contenuto", "Status": "Stato", "ReadArticle": "Leggi l'articolo", - "ArticleHasBeenWaiting": "L'articolo è stato in attesa", + "ArticleHasBeenWaiting": "L'articolo è in attesa", "ArticleHasBeenApproved": "L'articolo è stato approvato", "ArticleHasBeenRejected": "L'articolo è stato respinto", "Permission:Community": "Comunità", @@ -197,10 +197,10 @@ "RemoveCacheConfirmationMessage": "Sei sicuro di aver rimosso la cache per l'articolo \"{0}\"?", "SuccessfullyRemoved": "Cancellato con successo", "RemoveCache": "Rimuovi cache", - "Language": "linguaggio", + "Language": "Lingua", "Optional": "Opzionale", "CreateArticleLanguageInfo": "La lingua in cui è scritto il post", - "Enum:ContentSource:2": "Post video", + "Enum:ContentSource:2": "Pubblica video", "VideoPreview": "Anteprima video", "VideoPreviewErrorMessage": "Impossibile recuperare l'URL del video da Youtube. Ciò può essere causato dal fatto che il video è privato o l'URL fornito non è disponibile.", "DeleteCoverImage": "Elimina immagine di copertina", @@ -214,19 +214,19 @@ "Currency": "Moneta", "Gateway": "Gateway", "State": "Stato", - "FailReason": "Motivo fallito", + "FailReason": "Motivo fallimento", "ReIndexAllPosts": "Reindicizza tutti i post", "ReIndexAllPostsConfirmationMessage": "Sei sicuro di voler reindicizzare tutti i post?", "SuccessfullyReIndexAllPosts": "Tutti i post sono stati reindicizzati con successo.", - "Permission:FullSearch": "Ricerca nel testo completo", + "Permission:FullSearch": "Ricerca in tutto il testo", "Menu:CliAnalytics": "Cli Analytics", "TemplateName": "Nome modello", "TemplateVersion": "Versione modello", - "DatabaseProvider": "Provider di database", - "IsTiered": "È a livelli", + "DatabaseProvider": "Provider del database", + "IsTiered": "Multi livello", "ProjectName": "Nome del progetto", "Username": "Nome utente", - "Tool": "Attrezzo", + "Tool": "Strumento", "Command": "Comando", "UiFramework": "Framework dell'interfaccia utente", "Options": "Opzioni", @@ -234,10 +234,10 @@ "Permission:CliAnalyticses": "Cli Analyticses", "Permission:CliAnalytics": "Cli Analytics", "Search": "Ricerca", - "ClearFilter": "Filtro pulito", - "LicensePrivateKey": "Chiave privata della licenza", - "LicensePublicKey": "Licenza chiave pubblica", - "ApiKey": "Chiave API NuGet", + "ClearFilter": "Pulisci filtro", + "LicensePrivateKey": "Chiave privata licenza", + "LicensePublicKey": "Chiave pubblica licenza", + "ApiKey": "NuGet API key", "ShowInvoiceRequestsOfOrganization": "Richieste di fattura", "ShowQuestionsOfOrganization": "Domande", "Question": "Domanda", @@ -245,7 +245,7 @@ "Questions": "Domande", "InvoiceRequests": "Richieste di fattura", "Address": "Indirizzo", - "TaxNo": "No fiscale", + "TaxNo": "Partiva IVA", "Permission:InvoiceRequest": "Richiesta di fattura", "Permission:Question": "Domanda", "AddNoteSuccessMessage": "Nota aggiunta con successo", @@ -258,8 +258,8 @@ "Enum:EntityChangeType:0": "Creato", "Enum:EntityChangeType:1": "Aggiornato", "Enum:EntityChangeType:2": "Eliminato", - "TenantId": "ID inquilino", - "ChangeTime": "Cambia il tempo", + "TenantId": "ID tenant", + "ChangeTime": "Cambia orario", "EntityTypeFullName": "Nome completo del tipo di entità", "AuditLogsFor{0}Organization": "Log di controllo per l'organizzazione \"{0}\"", "Permission:EntityChange": "Modifica entità", @@ -269,14 +269,40 @@ "EmailSendDate": "Data di invio dell'e-mail", "PaymentRequestsFor{0}Organization": "Richieste di pagamento per l'organizzazione \"{0}\"", "PaymentDetails": "Dettagli di pagamento", - "PaymentProduct": "Prodotto di pagamento", - "ProductName": "nome del prodotto", + "PaymentProduct": "Prodotti pagamento", + "ProductName": "Nome del prodotto", "Code": "Codice", "GenerateInvoice": "Genera fattura", - "ExportOrganizationsToExcel": "esportare in Excel", + "ExportOrganizationsToExcel": "Esporta in Excel", "ThisExtensionIsNotAllowed": "Questa estensione non è consentita.", "TheFileIsTooLarge": "Il file è troppo grande!", "ArticleDeletionConfirmationMessage": "Sei sicuro di voler eliminare definitivamente questo articolo?", - "ChooseCoverImage": "Scegli un'immagine di copertina ..." + "ChooseCoverImage": "Scegli un'immagine di copertina...", + "Menu:Quotation": "Quotation", + "Menu:Invoice": "Fattura", + "Menu:PaymentRequests": "Richieste Pagamento", + "Permission:PaymentRequests": "Richieste Pagamento", + "PaymentRequests": "Richieste Pagamento", + "Creator": "Creatore", + "ExtraProperties": "Proprietà Extra", + "Organization": "Organizzazione", + "Waiting": "In attesa", + "Completed": "Completato", + "Failed": "Fallito", + "PaymentRequestDeletionWarningMessage": "Questa richiesta pagamento sarà cancellata. Confermi?", + "Payment": "Pagamento", + "AddPayment": "Aggiungi Pagamento", + "Enum:PurchaseType:1": "Nuova Licenza", + "Enum:PurchaseType:2": "Estendi Licenza", + "Enum:PurchaseType:3": "Aggiornamento Licenza", + "Enum:PurchaseType:4": "Sviluppatore Aggiuntivo", + "LicenceType": "Tipo Licenza", + "PurchaseType": "Tipo Acquisto", + "ReceiptNo": "Ricevuta n.", + "PaymentTime": "Tipo Pagamento", + "ProductPrice": "Prezzo Prodotto", + "AdditionalDeveloper": "Sviluppatore aggiuntivo", + "ThisPaymentHasBeenAlreadyUsed": "Questo pagamento è già stato utilizzato", + "PaymentTimeCannotBeFutureTime": "Orario di pagamento non può essere nel futuro" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/sk.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/sk.json new file mode 100644 index 0000000000..fcc64ed321 --- /dev/null +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/sk.json @@ -0,0 +1,282 @@ +{ + "culture": "sk", + "texts": { + "Permission:Organizations": "Organizácie", + "Permission:Manage": "Správa organizácií", + "Permission:DiscountRequests": "Žiadosti o zľavu", + "Permission:DiscountManage": "Správa žiadostí o zľavu", + "Permission:Disable": "Zakázať", + "Permission:Enable": "Povoliť", + "Permission:EnableSendEmail": "Povoliť odosielanie emailov", + "Permission:SendEmail": "Odoslať e-mail", + "Permission:NpmPackages": "Balíky NPM", + "Permission:NugetPackages": "Balíky Nuget", + "Permission:Maintenance": "Údržba", + "Permission:Maintain": "Spustiť údržbu", + "Permission:ClearCaches": "Vymazať vyrovnávaciu pamäť", + "Permission:Modules": "Moduly", + "Permission:Packages": "Balíky", + "Permission:Edit": "Upraviť", + "Permission:Delete": "Zmazať", + "Permission:Create": "Vytvoriť", + "Permission:Accounting": "Účtovníctvo", + "Permission:Accounting:Quotation": "Ponuka", + "Permission:Accounting:Invoice": "Faktúra", + "Menu:Organizations": "Organizácie", + "Menu:Accounting": "Účtovníctvo", + "Menu:Packages": "Balíky", + "Menu:DiscountRequests": "Žiadosti o zľavu", + "NpmPackageDeletionWarningMessage": "Tento balík NPM bude zmazaný. Chcete operáciu potvrdiť?", + "NugetPackageDeletionWarningMessage": "Tento balík Nuget bude zmazaný. Chcete operáciu potvrdiť?", + "ModuleDeletionWarningMessage": "Tento modul bude zmazaný. Chcete operáciu potvrdiť?", + "Name": "Názov", + "DisplayName": "Zobrazený názov", + "ShortDescription": "Krátky popis", + "NameFilter": "Názov", + "CreationTime": "Čas vytvorenia", + "IsPro": "Je pro", + "IsFreeToActiveLicenseOwners": "Zadarmo pre majiteľov licencií", + "ShowOnModuleList": "Zobraziť v zozname modulov", + "EfCoreConfigureMethodName": "Konfigurovať názov metódy", + "IsProFilter": "Je pro", + "ApplicationType": "Typ aplikácie", + "Target": "Cieľ", + "TargetFilter": "Cieľ", + "ModuleClass": "Trieda modulu", + "NugetPackageTarget.DomainShared": "Domain Shared", + "NugetPackageTarget.Domain": "Domain", + "NugetPackageTarget.Application": "Application", + "NugetPackageTarget.ApplicationContracts": "Application Contracts", + "NugetPackageTarget.HttpApi": "Http Api", + "NugetPackageTarget.HttpApiClient": "Http Api Client", + "NugetPackageTarget.Web": "Web", + "NugetPackageTarget.EntityFrameworkCore": "DeleteAllEntityFramework Core", + "NugetPackageTarget.MongoDB": "MongoDB", + "Edit": "Upraviť", + "Delete": "Zmazať", + "Refresh": "Obnoviť", + "NpmPackages": "Balíky NPM", + "NugetPackages": "Balíky Nuget", + "NpmPackageCount": "Počet NPM balíkov ", + "NugetPackageCount": "Počet Nuget balíkov", + "Module": "Moduly", + "ModuleInfo": "Informácie o module", + "CreateANpmPackage": "Vytvoriť NPM balík", + "CreateAModule": "Vytvoriť modul", + "CreateANugetPackage": "Vytvoriť Nuget balík", + "AddNew": "Pridať nový", + "PackageAlreadyExist{0}": "Balík \"{0}\" je už pridaný.", + "ModuleAlreadyExist{0}": "Modul \"{0}\" je už pridaný.", + "ClearCache": "Vyčistiť vyrovnávaciu pamäť", + "SuccessfullyCleared": "Úspešne vyčistené", + "Menu:NpmPackages": "NPM balíky", + "Menu:Modules": "Moduly", + "Menu:Maintenance": "Údržba", + "Menu:NugetPackages": "Nuget balíky", + "CreateAnOrganization": "Vytvoriť organizáciu", + "Organizations": "Organizácie", + "LongName": "Dlhý názov", + "LicenseType": "Typ licencie", + "MissingLicenseTypeField": "Pole Typ licencie je povinné!", + "LicenseStartTime": "Čas začiatku platnosti licencie", + "LicenseEndTime": "Čas ukončenia platnosti licencie", + "AllowedDeveloperCount": "Povolený počet vývojárov", + "UserNameOrEmailAddress": "Používateľské meno alebo e-mailová adresa", + "AddOwner": "Pridať vlastníka", + "UserName": "Používateľské meno", + "Email": "Email", + "Developers": "Vývojári", + "AddDeveloper": "Pridať vývojára", + "Create": "Vytvoriť", + "UserNotFound": "Používateľ nebol nájdený", + "{0}WillBeRemovedFromDevelopers": "{0} bude odstranený z vývojárov, chcete akciu potvrdiť?", + "{0}WillBeRemovedFromOwners": "{0} bude odstranený z vlastníkov, chcete akciu potvrdiť?", + "{0}WillBeRemovedFromMembers": "{0} bude odstranený z členov, chcete akciu potvrdiť?", + "Computers": "Počítače", + "UniqueComputerId": "Jedinečný identifikátor počítača", + "LastSeenDate": "Dátum posledného zobrazenia", + "{0}Computer{1}WillBeRemovedFromRecords": "Počítač používateľa {0} ({1}) bude zo záznamov odstránený", + "OrganizationDeletionWarningMessage": "Organizácia bude vymazaná", + "DeletingLastOwnerWarningMessage": "Organizácia musí mať aspoň jedného vlastníka! Z tohto dôvodu nie je možné vlastníka odstrániť", + "This{0}AlreadyExistInThisOrganization": "Tento {0} už v tejto organizácii existuje", + "AreYouSureYouWantToDeleteAllComputers": "Ste si istí, že chcete zmazať všetky počítače?", + "DeleteAll": "Zmazať všetko", + "DoYouWantToCreateNewUser": "Chcete vytvoriť nového používateľa?", + "MasterModules": "Hlavné moduly", + "OrganizationName": "Názov organizácie", + "CreationDate": "Dátum vytvorenia", + "LicenseStartDate": "Dátum začiatku platnosti licencie", + "LicenseEndDate": "Dátum ukončenia platnosti licencie", + "OrganizationNamePlaceholder": "Názov organizácie...", + "TotalQuestionCountPlaceholder": "Celkový počet otázok...", + "RemainingQuestionCountPlaceholder": "Počet zostávajúcich otázok...", + "LicenseTypePlaceholder": "Typ licencie...", + "CreationDatePlaceholder": "Dátum vytvorenia...", + "LicenseStartDatePlaceholder": "Dátum začiatku platnosti licencie...", + "LicenseEndDatePlaceholder": "Dátum ukončenia platnosti licencie...", + "UsernameOrEmail": "Používateľské meno alebo email", + "UsernameOrEmailPlaceholder": "Používateľské meno alebo email...", + "Member": "Člen", + "PurchaseOrderNo": "Nákupná objednávka č.", + "QuotationDate": "Dátum cenovej ponuky", + "CompanyName": "Názov spoločnosti", + "CompanyAddress": "Adresa spoločnosti", + "Price": "Cena", + "DiscountText": "Text zľavy", + "DiscountQuantity": "Zľavnené množstvo", + "DiscountPrice": "Zľavová cena", + "Quotation": "Ponuka", + "ExtraText": "Extra text", + "ExtraAmount": "Dodatočná suma", + "DownloadQuotation": "Stiahnuť ponuku", + "Invoice": "Faktúra", + "TaxNumber": "DIČ", + "InvoiceNumber": "Číslo faktúry", + "InvoiceDate": "Dátum faktúry", + "InvoiceNote": "Poznámka k faktúre", + "Quantity": "Množstvo", + "AddProduct": "Pridať produkt", + "AddProductWarning": "Musíte pridať produkt!", + "TotalPrice": "Celková cena", + "Generate": "Generovať", + "MissingQuantityField": "Pole Množstvo je povinné!", + "MissingPriceField": "Pole Cena je povinné!", + "CodeUsageStatus": "Stav", + "Country": "Krajina", + "DeveloperCount": "Počet vývojárov", + "RequestCode": "Kód žiadosti", + "WebSite": "Internetová stránka", + "GithubUsername": "Github používateľské meno", + "PhoneNumber": "Telefónne číslo", + "ProjectDescription": "Popis projektu", + "Referrer": "Sprostredkovateľ", + "DiscountRequests": "Žiadosť o zľavu", + "Copylink": "Kopírovať odkaz", + "Disable": "Zakázať", + "Enable": "Povoliť", + "EnableSendEmail": "Povoliť odoslanie e-mailu", + "SendEmail": "Odošli e-mail", + "SuccessfullyDisabled": "Úspešne zakázané", + "SuccessfullyEnabled": "Úspešne povolené", + "EmailSent": "Email bol odoslaný", + "SuccessfullySent": "Úspešne odoslané", + "SuccessfullyDeleted": "Úspešne zmazané", + "DiscountRequestDeletionWarningMessage": "Žiadosť o zľavu bude vymazaná", + "BusinessType": "Typ podnikania", + "TotalQuestionCount": "Celkový počet otázok", + "RemainingQuestionCount": "Zostávajúci počet otázok", + "TotalQuestionMustBeGreaterWarningMessage": "Celkový počet otázok musí byť väčší ako zostávajúci počet otázok !", + "QuestionCountsMustBeGreaterThanZero": "Celkový počet otázok a zostávajúci počet otázok sa musí rovnať alebo byť väčší ako nula!", + "UnlimitedQuestionCount": "Neobmedzený počet otázok", + "Notes": "Poznámky", + "Menu:Community": "Komunita", + "Menu:Articles": "Články", + "Wait": "Čakať", + "Approve": "Schváliť", + "Reject": "Zamietnuť", + "Details": "Podrobnosti", + "Url": "Url", + "Title": "Nadpis", + "ContentSource": "Zdroj obsahu", + "Status": "Stav", + "ReadArticle": "Prečítať článok", + "ArticleHasBeenWaiting": "Článok čakal", + "ArticleHasBeenApproved": "Článok bol schválený", + "ArticleHasBeenRejected": "Článok bol zamietnutý", + "Permission:Community": "Komunita", + "Permission:CommunityArticle": "Článok", + "Link": "Odkaz", + "Enum:ContentSource:0": "Github", + "Enum:ContentSource:1": "Externý", + "Enum:Status:0": "Čaká", + "Enum:Status:1": "Zamietný", + "Enum:Status:2": "Schválený", + "Summary": "Zhrnutie", + "AuthorName": "Meno autora", + "CoverImage": "Titulný obrázok", + "RemoveCacheConfirmationMessage": "Naozaj chcete odstrániť vyrovnávaciu pamäť pre článok \"{0}\"?", + "SuccessfullyRemoved": "Úspešne vyčistené", + "RemoveCache": "Odstrániť vyrovnávaciu pamäť", + "Language": "Jazyk", + "Optional": "Voliteľné", + "CreateArticleLanguageInfo": "Jazyk, v ktorom je príspevok napísaný", + "Enum:ContentSource:2": "Video príspevok", + "VideoPreview": "Náhľad videa", + "VideoPreviewErrorMessage": "Poskytnutú url videa sa z Youtube nepodarilo načítať. Video môže byť súkromné alebo adresa URL nie je dostupná.", + "DeleteCoverImage": "Odstránenie titulného obrázku", + "DeleteCoverImageConfirmationMessage": "Ste si istý, že chcete zmazať titulný obrázok pre \"{0}\"?", + "DeleteCoverImageSuccessMessage": "Titulný obrázok úspešne zmazaný", + "PaymentsOf": "Platby", + "ShowPaymentsOfOrganization": "Platby", + "Date": "Dátum", + "Products": "Produkty", + "TotalAmount": "Celková suma", + "Currency": "Mena", + "Gateway": "Brána", + "State": "Štát", + "FailReason": "Dôvod zlyhania", + "ReIndexAllPosts": "Reindexovanie všetkých príspevkov", + "ReIndexAllPostsConfirmationMessage": "Ste si istý, že chcete reindexovať všetky príspevky?", + "SuccessfullyReIndexAllPosts": "Všetky príspevky boli úspešne reindexované.", + "Permission:FullSearch": "Vyhľadávanie v celom texte", + "Menu:CliAnalytics": "Cli analytiky", + "TemplateName": "Názov šablóny", + "TemplateVersion": "Verzia šablóny", + "DatabaseProvider": "Poskytovateľ databázy", + "IsTiered": "Je viacúrovňový", + "ProjectName": "Názov projektu", + "Username": "Meno používateľa", + "Tool": "Nástroj", + "Command": "Príkaz", + "UiFramework": "UI frameworku", + "Options": "Možnosti", + "CliAnalytics": "Cli analytiky", + "Permission:CliAnalyticses": "Cli analytiky", + "Permission:CliAnalytics": "Cli analytiky", + "Search": "Hľadať", + "ClearFilter": "Vyčistiť filter", + "LicensePrivateKey": "Privátny kľúč licencie", + "LicensePublicKey": "Verejný kľúč licencie", + "ApiKey": "NuGet API kľúč ", + "ShowInvoiceRequestsOfOrganization": "Žiadosti o faktúru", + "ShowQuestionsOfOrganization": "Otázky", + "Question": "Otázka", + "Open": "Otvoriť", + "Questions": "Otázky", + "InvoiceRequests": "Žiadosti o faktúru", + "Address": "Adresa", + "TaxNo": "DIČ", + "Permission:InvoiceRequest": "Žiadosť o faktúru", + "Permission:Question": "Otázka", + "AddNoteSuccessMessage": "Poznámka úspešne pridaná", + "NameSurname": "Meno Priezvisko", + "Note": "Poznámka", + "Add": "Pridať", + "ProjectDownloads": "Stiahnutia projektu", + "ShowProjectDownloadsOfOrganization": "Stiahnutia projektu", + "ShowAuditLogsOfOrganization": "Audit logy", + "Enum:EntityChangeType:0": "Vytvorené", + "Enum:EntityChangeType:1": "Aktualizované", + "Enum:EntityChangeType:2": "Zmazané", + "TenantId": "ID tenanta", + "ChangeTime": "Čas zmeny", + "EntityTypeFullName": "Úplný názov typu entity", + "AuditLogsFor{0}Organization": "Audit logy pre organizáciu \"{0}\"", + "Permission:EntityChange": "Zmena entity", + "Permission:ProjectDownload": "Stiahnutie projektu", + "Permission:PaymentRequest": "Žiadosť o platbu", + "CreatorEmailAddress": "Emailová adresa autora", + "EmailSendDate": "Dátum odoslania emailu", + "PaymentRequestsFor{0}Organization": "Žiadosti o platbu pre organizáciu \"{0}\"", + "PaymentDetails": "Podrobnosti o platbe", + "PaymentProduct": "Platobný produkt", + "ProductName": "Názov produktu", + "Code": "Kód", + "GenerateInvoice": "Generovať faktúru", + "ExportOrganizationsToExcel": "Export do Excelu", + "ThisExtensionIsNotAllowed": "Táto prípona nie je povolená.", + "TheFileIsTooLarge": "Súbor je príliš veľký!", + "ArticleDeletionConfirmationMessage": "Ste si istí, že chcete tento článok úplne vymazať?", + "ChooseCoverImage": "Vybrať titulný obrázok..." + } +} \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/tr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/tr.json index c1253db259..f59e504f22 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/tr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/tr.json @@ -227,6 +227,32 @@ "TheFileIsTooLarge": "Dosya çok büyük.", "ArticleDeletionConfirmationMessage": "Bu makaleyi kalıcı olarak silmek istediğinizden emin misiniz?", "ChooseCoverImage": "Bir kapak resmi seçin...", - "CoverImage": "Kapak Resmi" + "CoverImage": "Kapak Resmi", + "Menu:Quotation": "Fiyatlandırma", + "Menu:Invoice": "Fatura", + "Menu:PaymentRequests": "Ödeme İstekleri", + "Permission:PaymentRequests": "Ödeme İstekleri", + "PaymentRequests": "Ödeme İstekleri", + "Creator": "Oluşturan", + "ExtraProperties": "Ekstra Özellikler", + "Organization": "Organizasyon", + "Waiting": "Bekliyor", + "Completed": "Tamamlandı", + "Failed": "Başarısız Oldu", + "PaymentRequestDeletionWarningMessage": "Bu ödeme isteği silinecek. Bunu onaylıyor musun?", + "Payment": "Ödeme", + "AddPayment": "Ödeme Ekle", + "Enum:PurchaseType:1": "Yeni Lisans", + "Enum:PurchaseType:2": "Lisans Uzatma", + "Enum:PurchaseType:3": "Lisans Yükseltmesi", + "Enum:PurchaseType:4": "Ek Geliştirici", + "LicenceType": "Lisans Türü", + "PurchaseType": "Satın Alma Türü", + "ReceiptNo": "Fiş No", + "PaymentTime": "Ödeme Zamanı", + "ProductPrice": "Ürün Fiyatı", + "AdditionalDeveloper": "Ek geliştirici", + "ThisPaymentHasBeenAlreadyUsed": "Bu ödeme zaten kullanılmış", + "PaymentTimeCannotBeFutureTime": "Ödeme zamanı gelecek zaman olamaz" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en-GB.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en-GB.json index 82ffbf0f4c..442384bd0a 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en-GB.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en-GB.json @@ -32,6 +32,7 @@ "SeeDocuments": "See Documents", "Samples": "Samples", "FreeDDDBook": "Free DDD E-book", - "New": "New" + "New": "New", + "Volo.AbpIo.Domain:020005": "License extend year can not be lower than {MinExtendLicenseYear} year and greater than {MaxExtendLicenseYear} years " } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json index 646c2f9648..b9c8d23563 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json @@ -34,6 +34,7 @@ "Framework": "Framework", "Support": "Support", "FreeDDDBook": "Free DDD E-book", - "New": "New" + "New": "New", + "Volo.AbpIo.Domain:020005": "License extend year can not be lower than {MinExtendLicenseYear} year and greater than {MaxExtendLicenseYear} years " } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/hi.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/hi.json index cd1ec4491d..5096d3cbe7 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/hi.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/hi.json @@ -11,8 +11,8 @@ "Volo.AbpIo.Domain:010011": "व्यक्तिगत लाइसेंस में 1 से अधिक डेवलपर नहीं हो सकते हैं!", "Volo.AbpIo.Domain:010012": "लाइसेंस की अवधि समाप्त होने के एक महीने बाद तक लाइसेंस नहीं बढ़ाया जा सकता है!", "Volo.AbpIo.Domain:020001": "इस NPM पैकेज को नहीं हटाया जा सका क्योंकि \"{NugetPackages}\" नुगेट पैकेज इस पैकेज पर निर्भर हैं।", - "Volo.AbpIo.Domain:020002": "यह NPM पैकेज नहीं हटा सका क्योंकि \"{मॉड्यूल}\" मॉड्यूल इस पैकेज का उपयोग कर रहे हैं।", - "Volo.AbpIo.Domain:020003": "इस NPM पैकेज को नहीं हटाया जा सका क्योंकि \"{मॉड्यूल}\" मॉड्यूल इस पैकेज का उपयोग कर रहे हैं और \"{NugetPackages}\" नुगेट पैकेज इस पैकेज पर निर्भर हैं।", + "Volo.AbpIo.Domain:020002": "यह NPM पैकेज नहीं हटा सका क्योंकि \"{Modules}\" मॉड्यूल इस पैकेज का उपयोग कर रहे हैं।", + "Volo.AbpIo.Domain:020003": "इस NPM पैकेज को नहीं हटाया जा सका क्योंकि \"{Modules}\" मॉड्यूल इस पैकेज का उपयोग कर रहे हैं और \"{NugetPackages}\" नुगेट पैकेज इस पैकेज पर निर्भर हैं।", "Volo.AbpIo.Domain:020004": "इस Nuget पैकेज को हटाया नहीं जा सका क्योंकि \"{Modules}\" मॉड्यूल इस पैकेज का उपयोग कर रहे हैं।", "WantToLearn?": "सीखना चाहते है?", "ReadyToGetStarted?": "आरंभ करने के लिए तैयार हैं?", diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/it.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/it.json index 8a0e9d9e0b..abc3ce28ec 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/it.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/it.json @@ -19,7 +19,7 @@ "JoinOurCommunity": "Unisciti alla nostra community", "GetStartedUpper": "INIZIARE", "ForkMeOnGitHub": "Fork me su GitHub", - "Features": "Caratteristiche", + "Features": "Funzionalità", "GetStarted": "Iniziare", "Documents": "Documenti", "Community": "Comunità", @@ -31,7 +31,10 @@ "Permission:UserInfo": "Informazioni utente", "SeeDocuments": "Vedi Documenti", "Samples": "Campioni", - "Framework": "Struttura", - "Support": "Supporto" + "Framework": "Framework", + "Support": "Supporto", + "FreeDDDBook": "DDD E-Book Gratuito", + "New": "Nuovo", + "Volo.AbpIo.Domain:020005": "Estensione Licenza non può essere inferiore a {MinExtendLicenseYear} anno e maggiore di {MaxExtendLicenseYear} anni " } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/sk.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/sk.json new file mode 100644 index 0000000000..619956340d --- /dev/null +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/sk.json @@ -0,0 +1,37 @@ +{ + "culture": "sk", + "texts": { + "Volo.AbpIo.Domain:010004": "Dosiahnutý maximálny počet členov!", + "Volo.AbpIo.Domain:010005": "Dosiahnutý maximálny počet majiteľov!", + "Volo.AbpIo.Domain:010006": "Tento používateľ je už vlastníkom v tejto organizácii!", + "Volo.AbpIo.Domain:010007": "Tento používateľ je už vývojárom v tejto organizácii!", + "Volo.AbpIo.Domain:010008": "Povolený počet vývojárov nemôže byť menší ako aktuálny počet vývojárov!", + "Volo.AbpIo.Domain:010009": "Povolený počet vývojárov nemôže byť menší ako 0!", + "Volo.AbpIo.Domain:010010": "Maximálny počet mac adries je prekročený!", + "Volo.AbpIo.Domain:010011": "Osobná licencia nemôže mať viac ako 1 vývojára!", + "Volo.AbpIo.Domain:010012": "Licenciu nie je možné predĺžiť mesiac po jej vypršaní!", + "Volo.AbpIo.Domain:020001": "NPM balík sa nepodarilo zmazať, pretože Nuget balíky \"{NugetPackages}\" sú závislé na tomto balíku.", + "Volo.AbpIo.Domain:020002": "NPM balík sa nepodarilo zmazať, pretože tento balík používajú moduly \"{Modules}\".", + "Volo.AbpIo.Domain:020003": "NPM balík sa nepodarilo zmazať, pretože tento balík používajú moduly \"{Modules}\" a Nuget balíky \"{NugetPackages}\" sú závislé na tomto balíku.", + "Volo.AbpIo.Domain:020004": "Nuget balík sa nepodarilo zmazať, pretože tento balík používajú moduly \"{Modules}\".", + "WantToLearn?": "Chcete sa učiť?", + "ReadyToGetStarted?": "Ste pripravený začať?", + "JoinOurCommunity": "Pripojte sa k našej komunite", + "GetStartedUpper": "ZAČAŤ", + "ForkMeOnGitHub": "Fork me on GitHub", + "Features": "Funkcie", + "GetStarted": "Začať", + "Documents": "Dokumenty", + "Community": "Komunita", + "ContributionGuide": "Sprievodca prispievaním", + "Blog": "Blog", + "Commercial": "Commercial", + "MyAccount": "Moje konto", + "Permission:License": "Licencia", + "Permission:UserInfo": "Informácie o používateľovi", + "SeeDocuments": "Pozrieť dokumenty", + "Samples": "Ukážky", + "Framework": "Framework", + "Support": "Podpora" + } +} \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/tr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/tr.json index 2f932f299b..04c114a6c2 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/tr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/tr.json @@ -28,6 +28,7 @@ "Commercial": "Ticari", "SeeDocuments": "Dokümanlara Göz Atın", "FreeDDDBook": "Ücretsiz DDD Kitabı", - "New": "Yeni" + "New": "Yeni", + "Volo.AbpIo.Domain:020005": "Lisans uzatma yılı {MinExtendLicenseYear} yıldan az {MaxExtendLicenseYear} yıldan fazla olamaz." } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en-GB.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en-GB.json index 0694811b78..af20ee9ba7 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en-GB.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en-GB.json @@ -100,6 +100,7 @@ "VolosoftSharingInformationMessage": "I would like Volosoft to share my information with select partners so I can receive relevant information about their products and services.", "WeWillSendYouADownloadLink": "We've sent the file to {0}.", "InvalidFormInputs": "Please, type the valid information specified on the form.", - "DDDBookEmailBody": "Thank you.
To download your book, click here." + "DDDBookEmailBody": "Thank you.
To download your book, click here.", + "FreeDDDEBook": "Free DDD E-Book" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json index d7f9f3c651..04da7d097d 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json @@ -45,7 +45,7 @@ "IndexPageHeroSection": "A complete web development platformbuilt-on framework", "AbpCommercialShortDescription": "ABP Commercial provides pre-built application modules, rapid application development tooling, professional UI themes, premium support and more.", "LiveDemo": "Live Demo", - "GetLicence" :"Get a Licence", + "GetLicence": "Get a Licence", "Application": "Application", "StartupTemplates": "Startup Templates", "Startup": "Startup", @@ -174,8 +174,8 @@ "HowManyProductsExplanation": "There is no limit to create an ABP project. You can create as many project as you want, develop and upload them to different servers.", "HowManyDevelopers": "How many developers can work on the ABP Commercial?", "HowManyDevelopersExplanation": "ABP Commercial licenses are per developer. Different license types have different developer limits. However, you can add more developers to any license type whenever you need. See the prices page for license types, developer limits and additional developer costs.", - "ChangingLicenseType": "Can I change my license type in the future?", - "ChangingLicenseTypeExplanation": "You can always add new developers in your same license type. See also \"How many developers can work on the ABP Commercial?\". You can also upgrade to a higher license by paying the calculated price difference. When you upgrade to a higher license plan, you get the benefits of the new plan, but the license upgrade does not change the license expiry date.", + "ChangingLicenseType": "Can I upgrade my license type later?", + "ChangingLicenseTypeExplanation": "You can upgrade to a higher license by paying the difference within your active license period. When you upgrade to a higher license plan, you get the benefits of the new plan, but the license upgrade does not change the license expiry date. Besides, you can also add new developer seats to your existing license, see \"How many developers can work on the ABP Commercial?\"", "LicenseExtendUpgradeDiff": "What is the difference between license extend and upgrade?", "LicenseExtendUpgradeDiffExplanation": "Extending: By extending/renewing your license, you will continue to get premium support and get major updates for the modules and themes. Besides, you will be able to continue creating new projects. And you will still be able to use ABP Suite which speeds up your development.
Upgrading: By upgrading your license, you will promote to a higher license plan which will allow you to get additional benefits. See the license comparison table to check the differences between the license plans.On the other hand, when you upgrade, your license expiry date will not change!To extend your license end date, you need to extend your license.", "LicenseRenewalCost": "What is the license renewal cost after 1 year?", @@ -196,14 +196,14 @@ "WhatHappensWhenLicenseEndsExplanation4": "You can not install new modules and themes added to the ABP Commercial platform after your license ends.", "WhatHappensWhenLicenseEndsExplanation5": "You can not use the ABP Suite.", "WhatHappensWhenLicenseEndsExplanation6": "You can not get the premium support anymore.", - "WhatHappensWhenLicenseEndsExplanation7": "You can renew your subscription if you want to continue to get these benefits. There is a 20% discount when you renew your subscription.", + "WhatHappensWhenLicenseEndsExplanation7": "You can renew your subscription if you want to continue to get these benefits. If you renew your subscription within 1 month after your license expires, a 20% discount is applied.", "WhenShouldIRenewMyLicense": "When should I renew my license?", - "WhenShouldIRenewMyLicenseExplanation1": "If you renew your license within 1 month after your license expires, %20 discount will be applied to total license price.", + "WhenShouldIRenewMyLicenseExplanation1": "If you renew your license within 1 month after your license expires, a %20 discount is be applied.", "WhenShouldIRenewMyLicenseExplanation2": "If you renew your license after 1 month from your license expire date, the renew price will be same as license purchase price and there will be no discount for your renewal.", "TrialPlan": "Do you have a trial plan?", "TrialPlanExplanation": "For now, ABP Commercial doesn't have a trial plan. For the Team licenses we provide 30 days money back guarantee. You can just request a refund in the first 30 days. For the Business and Enterprise licenses, we provide 60% refund in 30 days. This is because Business and Enterprise licenses include the full source code of all the modules and the themes.", "DoYouAcceptBankWireTransfer": "Do you accept bank wire transfer?", - "DoYouAcceptBankWireTransferExplanation": "Yes, we accept bank wire transfer.
After sending the license amount via bank wire transfer, send us your receipt and the requested license type via e-mail.
Our international bank account information:", + "DoYouAcceptBankWireTransferExplanation": "Yes, we accept bank wire transfer.
After sending the license fee via bank transfer, email us at info@abp.io your receipt and the type of license requested. Our international bank account information:", "HowToUpgrade": "How to upgrade existing applications when a new version is available?", "HowToUpgradeExplanation1": "When you create a new application using ABP Commercial, all the modules and the theme are used as NuGet and NPM packages. So, you can easily upgrade the packages when a new version is available.", "HowToUpgradeExplanation2": "In addition to the standard NuGet/NPM upgrades, ABP CLI provides an update command that automatically finds and upgrades all ABP related packages in your solution.", @@ -357,6 +357,7 @@ "VolosoftSharingInformationMessage": "I would like Volosoft to share my information with select partners so I can receive relevant information about their products and services.", "WeWillSendYouADownloadLink": "A link to download the e-book has been sent to {0}.
Check your inbox / junk / spam boxes!", "InvalidFormInputs": "Please, type the valid information specified on the form.", - "DDDBookEmailBody": "Thank you.
To download your book, click here." + "DDDBookEmailBody": "Thank you.
To download your book, click here.", + "FreeDDDEBook": "Free DDD E-Book" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/fi.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/fi.json index b2da832b2a..8de4891e99 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/fi.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/fi.json @@ -42,7 +42,7 @@ "NotOrganizationMember": "Et ole minkään organisaation jäsen.", "UnsubscribeLicenseExpirationEmailSuccessTitle": "Tilauksen peruutus onnistui", "UnsubscribeLicenseExpirationEmailSuccessMessage": "Et enää saa muistutuksia lisenssin vanhentumispäivästä.", - "IndexPageHeroSection": " Täydellinen verkkokehitysympäristö sisäänrakennettu kehys ", + "IndexPageHeroSection": " Täydellinen verkkokehitysympäristö sisäänrakennettu kehys ", "AbpCommercialShortDescription": "ABP Commercial tarjoaa valmiita sovellusmoduuleja, nopean sovelluskehitystyökalun, ammattimaiset käyttöliittymäteemat, ensiluokkaisen tuen ja paljon muuta.", "LiveDemo": "Live-esittely", "GetLicence": "Hanki lisenssi", @@ -63,7 +63,7 @@ "AdditionalServices": "Lisäpalvelut", "WhatIsABPFramework": "MIKÄ ON ABP-KEHYS?", "OpenSourceBaseFramework": "Avoimen lähdekoodin peruskehys", - "ABPFrameworkExplanation": "

ABP Commercial perustuu ABP Frameworkiin, avoimen lähdekoodin ja yhteisövetoiseen verkkosovelluskehykseen ASP.NET Core -sovellukselle.

ABP Framework tarjoaa erinomaisen infrastruktuurin ylläpitettävien, laajennettavien tiedostojen kirjoittamiseen. ja testattava koodi parhailla käytänteillä.

Rakennettu ja integroitu jo tunnettujen suosittujen työkalujen kanssa. Matala oppimiskäyrä, helppo sopeutuminen, mukava kehitys.

", + "ABPFrameworkExplanation": "

ABP Commercial perustuu ABP Frameworkiin, avoimen lähdekoodin ja yhteisövetoiseen verkkosovelluskehykseen ASP.NET Core -sovellukselle.

ABP Framework tarjoaa erinomaisen infrastruktuurin ylläpitettävien, laajennettavien tiedostojen kirjoittamiseen. ja testattava koodi parhailla käytänteillä.

Rakennettu ja integroitu jo tunnettujen suosittujen työkalujen kanssa. Matala oppimiskäyrä, helppo sopeutuminen, mukava kehitys.

", "Modular": "Modulaarinen", "MicroserviceCompatible": "Mikropalvelu yhteensopiva", "DomainDrivenDesignInfrastructure": "Toimialueohjattu suunnittelun infrastruktuuri", @@ -78,7 +78,7 @@ "ManyMore": "Monta lisää", "ExploreTheABPFramework": "Tutustu ABP-kehykseen", "WhyUseTheABPCommercial": "Miksi käyttää ABP-mainosta?", - "WhyUseTheABPCommercialExplanation": "

Yritysluokan verkkosovellusten rakentaminen voi olla monimutkaista ja aikaa vievää.

ABP Commercial tarjoaa täydellisen perusinfrastruktuurin, joka tarvitaan kaikille nykyaikaisille yritystason ASP.NET-ytimille perustuvat ratkaisut. ABP: n sisäänrakennetut ominaisuudet ja moduulit tukevat koko suunnittelusykliä suunnittelusta käyttöönottoon asti.

", + "WhyUseTheABPCommercialExplanation": "

Yritysluokan verkkosovellusten rakentaminen voi olla monimutkaista ja aikaa vievää.

ABP Commercial tarjoaa täydellisen perusinfrastruktuurin, joka tarvitaan kaikille nykyaikaisille yritystason ASP.NET-ytimille perustuvat ratkaisut. ABP: n sisäänrakennetut ominaisuudet ja moduulit tukevat koko suunnittelusykliä suunnittelusta käyttöönottoon asti.

", "StartupTemplatesShortDescription": "Käynnistysmallien avulla voit aloittaa projektisi muutamassa sekunnissa.", "UIFrameworksOptions": "Käyttöliittymäkehysvaihtoehdot;", "DatabaseProviderOptions": "Tietokannan tarjoajan vaihtoehdot;", @@ -145,25 +145,25 @@ "DeveloperGuide": "Kehittäjän opas", "ModuleTemplate": "Moduulimalli", "ModuleTemplateExplanation1": "Haluatko luoda moduulin ja käyttää sitä uudelleen eri sovelluksissa? Tämä käynnistysmalli valmistaa kaiken aloittamaan uudelleenkäytettävän sovellusmoduulin tai mikropalvelun luomisen.", - "ModuleTemplateExplanation2": "

Voit tukea yhtä tai useampaa käyttöliittymäkehystä, yhtä tai useampaa tietokantapalvelua yhdelle moduulille. Käynnistysmalli on määritetty suorittamaan ja testaamaan moduulia pienimmässä sovelluksessa yksikön ja integraatiotestiinfrastruktuurin lisäksi.

Katso teknistä tietoa mallista kehittäjän oppaasta.

", + "ModuleTemplateExplanation2": "

Voit tukea yhtä tai useampaa käyttöliittymäkehystä, yhtä tai useampaa tietokantapalvelua yhdelle moduulille. Käynnistysmalli on määritetty suorittamaan ja testaamaan moduulia pienimmässä sovelluksessa yksikön ja integraatiotestiinfrastruktuurin lisäksi.

Katso teknistä tietoa mallista kehittäjän oppaasta.

", "WithAllStyleOptions": "kaikilla tyylivaihtoehdoilla", "Demo": "Demo", "SeeAllModules": "Katso kaikki moduulit", "ABPCLIExplanation": "ABP CLI (Command Line Interface) on komentorivityökalu joidenkin yleisten toimintojen suorittamiseen ABP-pohjaisiin ratkaisuihin.", "ABPSuiteEasilyCURD": "ABP Suite on työkalu, jonka avulla voit helposti luoda CRUD-sivuja", - "WeAreHereToHelp": " Apua olemme täällä", + "WeAreHereToHelp": "Apua olemme täällä", "BrowseOrAskQuestion": "Voit selata ohjeaiheitamme tai etsiä usein kysyttyjä kysymyksiä tai voit esittää meille kysymyksiä yhteydenottolomakkeella .", "SearchQuestionPlaceholder": "Hae usein kysyttyjä kysymyksiä", "WhatIsTheABPCommercial": "Mikä on ABP-kauppa?", "WhatAreDifferencesThanAbpFramework": "Mitä eroja on avoimen lähdekoodin ABP Frameworkilla ja ABP Commercialilla?", "ABPCommercialExplanation": "ABP Commercial on joukko ensiluokkaisia moduuleja, työkaluja, teemoja ja palveluja, jotka on rakennettu avoimen lähdekoodin ABP-kehyksen päälle. ABP Commercial kehittää ja tukee samaa tiimiä ABP-kehyksen takana.", - "WhatAreDifferencesThanABPFrameworkExplanation": "

ABP-kehys on modulaarinen, teemoitettava, mikropalvelujen kanssa yhteensopiva sovelluskehys ASP.NET Core -sovellukselle. Se tarjoaa täydellisen arkkitehtuurin ja vahvan infrastruktuurin, joka saa sinut keskittymään omaan yrityskoodiin sen sijaan, että toistat itsesi jokaisessa uudessa projektissa. Se perustuu ohjelmistokehityksen parhaisiin käytäntöihin ja jo tiedettyihin suosittuihin työkaluihin.

ABP-kehykset ovat täysin ilmaisia, avoimen lähdekoodin ja yhteisölähtöisiä. Se tarjoaa myös ilmaisen teeman ja joitain valmiita moduuleja (esim. Henkilöllisyyden hallinta ja vuokralaisten hallinta).

", + "WhatAreDifferencesThanABPFrameworkExplanation": "

ABP-kehys on modulaarinen, teemoitettava, mikropalvelujen kanssa yhteensopiva sovelluskehys ASP.NET Core -sovellukselle. Se tarjoaa täydellisen arkkitehtuurin ja vahvan infrastruktuurin, joka saa sinut keskittymään omaan yrityskoodiin sen sijaan, että toistat itsesi jokaisessa uudessa projektissa. Se perustuu ohjelmistokehityksen parhaisiin käytäntöihin ja jo tiedettyihin suosittuihin työkaluihin.

ABP-kehykset ovat täysin ilmaisia, avoimen lähdekoodin ja yhteisölähtöisiä. Se tarjoaa myös ilmaisen teeman ja joitain valmiita moduuleja (esim. Henkilöllisyyden hallinta ja vuokralaisten hallinta).

", "VisitTheFrameworkVSCommercialDocument": "Vieraile seuraavalla linkillä saadaksesi lisätietoja {1} ", "ABPCommercialFollowingBenefits": "ABP Commercial lisää seuraavat edut ABP-kehyksen päälle;", "Professional": "Ammattilainen", "UIThemes": "Käyttöliittymän teemat", "EnterpriseModules": "Yrityskäyttöiset, monipuoliset, valmiit sovellusmoduulit (esim. Identity Server -hallinta, SaaS-hallinta, kielen hallinta)", - "ToolingToSupport": "Työkalut, jotka tukevat kehitystesi tuottavuutta (esim. ABP Suite )", + "ToolingToSupport": "Työkalut, jotka tukevat kehitystesi tuottavuutta (esim. ABP Suite )", "PremiumSupportLink": "Ensiluokkainen tuki ", "WhatDoIDownloadABPCommercial": "Mitä lataan ostaessani ABP Commercial -palvelua?", "CreateUnlimitedSolutions": "Kun olet ostanut ABP Commercial -lisenssin, voit luoda rajoittamattomia ratkaisuja, kuten Aloittaminen -asiakirjassa kuvataan.", @@ -177,7 +177,7 @@ "ChangingLicenseType": "Voinko muuttaa lisenssityyppiäni tulevaisuudessa?", "ChangingLicenseTypeExplanation": "Voit aina lisätä uusia kehittäjiä samaan lisenssityyppiin. Katso myös \"Kuinka monta kehittäjää voi työskennellä ABP Commercialilla?\". Voit myös päivittää korkeampaan lisenssiin maksamalla lasketun hintaeron. Kun päivität korkeampaan lisenssisuunnitelmaan, saat uuden suunnitelman edut, mutta lisenssin päivitys ei muuta lisenssin voimassaolon päättymispäivää.", "LicenseExtendUpgradeDiff": "Mitä eroa on lisenssin laajennuksella ja päivityksellä?", - "LicenseExtendUpgradeDiffExplanation": " Laajentaminen: Laajentamalla / uusimalla käyttöoikeutta saat jatkossakin ensiluokkaista tukea ja saat tärkeitä päivityksiä moduuleille ja teemoille. Lisäksi voit jatkaa uusien projektien luomista. Ja voit silti käyttää ABP Suitea, joka vauhdittaa kehitystäsi.
Päivittäminen: Päivittämällä lisenssisi edistyt korkeampaan lisenssisuunnitelmaan, jonka avulla voit saada lisäetuja . Katso lisenssisuunnitelmien väliset erot lisenssien vertailutaulukosta . Toisaalta, kun päivität, lisenssin vanhentumispäivä ei muutu! Lisenssin päättymispäivän jatkamiseksi sinun on jatkettava käyttöoikeutta.", + "LicenseExtendUpgradeDiffExplanation": " Extension: en prolongeant/renouvelant votre licence, vous continuerez à bénéficier d'une assistance premium et à des mises à jour majeures pour les modules et les thèmes. De plus, vous pourrez continuer à créer de nouveaux projets. Et vous pourrez toujours utiliser ABP Suite, ce qui accélère votre développement.
Mise à niveau: En mettant à jour votre licence, vous passerez à un plan de licence supérieur qui vous permettra d’obtenir des avantages supplémentaires . Consultez le tableau de comparaison des licences pour vérifier les différences entre les plans de licence. En revanche, lors de la mise à niveau, la date d'expiration de votre licence ne changera pas! Pour prolonger la date de fin de votre licence, vous devez prolonger votre licence.", "LicenseRenewalCost": "Mitkä ovat lisenssin uusimiskustannukset vuoden kuluttua?", "LicenseRenewalCostExplanation": "Kaikkien ABP Commercial perpetual -lisenssien uusimis- (laajennus) hinta on {0} lisenssiluettelohinnasta. Tavallisen tiimilisenssin uusimishinta on $ {1}, tavallinen yrityslisenssi on $ {2} ja tavallinen yrityslisenssi on $ {3}. Jos olet jo asiakas, kirjaudu tilillesi ja tarkista käytettävissä oleva uusimishinta.", "HowDoIRenewMyLicense": "Kuinka uusin lisenssin?", @@ -186,13 +186,13 @@ "IsSourceCodeIncludedExplanation1": "Riippuu ostamastasi lisenssityypistä:", "IsSourceCodeIncludedExplanation2": " Tiimi : Ratkaisusi käyttää moduuleja ja teemoja NuGet- ja NPM-paketteina. Se ei sisällä heidän lähdekoodiaan. Tällä tavoin voit helposti päivittää nämä moduulit ja teemat aina, kun uusi versio on saatavana. Et kuitenkaan voi saada moduulien ja teemojen lähdekoodia.", "IsSourceCodeIncludedExplanation3": " Yritys / yritys : Tiimilisenssin lisäksi voit ladata minkä tahansa tarvitsemasi moduulin tai teeman lähdekoodin. Voit jopa poistaa tietyn moduulin NuGet / NPM-pakettiviitteet ja lisätä sen lähdekoodin suoraan ratkaisuusi sen muuttamiseksi kokonaan.", - "IsSourceCodeIncludedExplanation4": "

Moduulin lähdekoodin sisällyttäminen ratkaisuun antaa sinulle maksimaalisen vapauden mukauttaa moduulia. Tällöin moduulia ei voida päivittää automaattisesti, kun uusi versio julkaistaan.

Mikään lisensseistä ei sisällä ABP Suiten lähdekoodia, joka on ulkoinen työkalu, joka tuottaa koodia sinulle ja auttaa kehitykseen.

Katso muita lisenssityyppien eroja hinnoittelusivulta .

", + "IsSourceCodeIncludedExplanation4": "

Moduulin lähdekoodin sisällyttäminen ratkaisuun antaa sinulle maksimaalisen vapauden mukauttaa moduulia. Tällöin moduulia ei voida päivittää automaattisesti, kun uusi versio julkaistaan.

Mikään lisensseistä ei sisällä ABP Suiten lähdekoodia, joka on ulkoinen työkalu, joka tuottaa koodia sinulle ja auttaa kehitykseen.

Katso muita lisenssityyppien eroja hinnoittelusivulta .

", "ChangingDevelopers": "Voinko muuttaa organisaationi rekisteröityneitä kehittäjiä tulevaisuudessa?", "ChangingDevelopersExplanation": "Uusien kehittäjien lisäämisen lisenssiin lisäksi voit myös muuttaa olemassa olevia kehittäjiä (voit poistaa kehittäjän ja lisätä uuden samalle paikalle) ilman lisäkustannuksia.", "WhatHappensWhenLicenseEnds": "Mitä tapahtuu, kun lisenssikauteni päättyy?", "WhatHappensWhenLicenseEndsExplanation1": "ABP Commercial -lisenssityyppi on jatkuva lisenssi . Kun lisenssi vanhenee, voit jatkaa projektisi kehittämistä. Ja sinun ei tarvitse uudistaa lisenssiäsi. Lisenssisi mukana toimitetaan yhden vuoden päivitys- ja tukisuunnitelma. Jos haluat edelleen saada uusia ominaisuuksia, suorituskyvyn parannuksia, virhekorjauksia, tukea ja jatkaa ABP Suite -sovelluksen käyttöä, muista uusia suunnitelma joka vuosi. Kun lisenssi vanhenee, et voi saada enemmän seuraavista eduista;", "WhatHappensWhenLicenseEndsExplanation2": "Et voi luoda uusia ratkaisuja ABP Commercial -sovelluksella, mutta voit jatkaa olemassa olevien sovellusten kehittämistä ikuisesti.", - "WhatHappensWhenLicenseEndsExplanation3": "Voit saada päivityksiä moduuleista ja teemoista MAJOR-versiostasi. Esimerkiksi; jos käytät moduulin v3.2.0-versiota, voit silti saada päivityksiä kyseisen moduulin versiolle v3.x.x (v3.3.0, v3.5.2 ... jne.). Mutta et voi saada päivityksiä seuraavaan pääversioon (kuten v4.x, v5.x)", + "WhatHappensWhenLicenseEndsExplanation3": "Voit saada päivityksiä moduuleista ja teemoista MAJOR-versiostasi. Esimerkiksi; jos käytät moduulin v3.2.0-versiota, voit silti saada päivityksiä kyseisen moduulin versiolle v3.x.x (v3.3.0, v3.5.2... jne.). Mutta et voi saada päivityksiä seuraavaan pääversioon (kuten v4.x, v5.x)", "WhatHappensWhenLicenseEndsExplanation4": "Et voi asentaa uusia moduuleja ja teemoja, jotka on lisätty ABP Commercial -alustalle käyttöoikeuden päättymisen jälkeen.", "WhatHappensWhenLicenseEndsExplanation5": "Et voi käyttää ABP Suitea.", "WhatHappensWhenLicenseEndsExplanation6": "Et voi enää saada premium-tukea .", @@ -203,7 +203,7 @@ "TrialPlan": "Onko sinulla kokeilusuunnitelma?", "TrialPlanExplanation": "Toistaiseksi ABP Commercialilla ei ole kokeilusuunnitelmaa. Joukkueen lisensseille tarjoamme 30 päivän rahanpalautustakuun. Voit vain pyytää hyvitystä 30 ensimmäisen päivän aikana. Yritys- ja yrityslisensseille hyvitämme 60% 30 päivässä. Tämä johtuu siitä, että yritys- ja yrityslisenssit sisältävät kaikkien moduulien ja teemojen täydellisen lähdekoodin.", "DoYouAcceptBankWireTransfer": "Hyväksytkö pankkisiirron?", - "DoYouAcceptBankWireTransferExplanation": "Kyllä, hyväksymme pankkisiirron.
Kun olet lähettänyt lisenssimäärän pankkisiirrolla, lähetä meille kuitti ja pyydetty lisenssityyppi sähköpostitse.
Kansainväliset pankkitilitiedot:", + "DoYouAcceptBankWireTransferExplanation": "Kyllä, hyväksymme pankkisiirron.
Kun olet lähettänyt lisenssimäärän pankkisiirrolla, lähetä meille kuitti ja pyydetty lisenssityyppi sähköpostitse.
Kansainväliset pankkitilitiedot:", "HowToUpgrade": "Kuinka päivittää olemassa olevia sovelluksia, kun uusi versio on saatavilla?", "HowToUpgradeExplanation1": "Kun luot uuden sovelluksen ABP Commercial -sovelluksella, kaikkia moduuleja ja teemaa käytetään NuGet- ja NPM-paketteina. Joten voit päivittää paketit helposti, kun uusi versio on saatavilla.", "HowToUpgradeExplanation2": "Normaalien NuGet / NPM-päivitysten lisäksi ABP CLI tarjoaa päivityskomennon, joka etsii ja päivittää kaikki ratkaisusi ABP-paketit.", @@ -227,10 +227,10 @@ "RefundPolicyExplanation": "Voit pyytää hyvitystä 30 päivän kuluessa lisenssiostoksestasi. Yritys- ja yrityslisenssityypeillä on lähdekoodin latausvaihtoehto, joten hyvityksiä ei ole saatavana yrityksille ja yrityksille (eikä lisensseille, jotka sisältävät oikeuden saada lähdekoodia). Lisäksi uusimista ja toisen lisenssin ostoja ei hyvitetä.", "HowCanIRefundVat": "Kuinka voin palauttaa arvonlisäveron?", "HowCanIRefundVatExplanation1": "Jos suoritit maksun käyttämällä 2Checkoutia, voit palauttaa arvonlisäveron 2Checkout-tilisi kautta:", - "HowCanIRefundVatExplanation2": "Kirjaudu sisään 2Tarkista -tiliisi.", + "HowCanIRefundVatExplanation2": "Kirjaudu sisään 2Checkout -tiliisi.", "HowCanIRefundVatExplanation3": "Etsi sopiva tilaus ja paina \"Hyvitä myöhässä oleva alv\" (kirjoita ALV-tunnuksesi)", "HowCanIGetMyInvoice": "Kuinka saan laskuni?", - "HowCanIGetMyInvoiceExplanation": "Lisenssin ostamiseen on 2 maksuyhdyskäytävää: PayU ja 2Checkout. Jos ostat lisenssin 2Checkout gatewayn kautta, se lähettää PDF-laskun sähköpostiosoitteeseesi, katso 2Tarkista laskutus. Jos ostat PayU-yhdyskäytävän kautta tai pankkisiirrolla, valmistelemme ja lähetämme laskusi. Voit pyytää laskua organisaation hallintasivulta ", + "HowCanIGetMyInvoiceExplanation": "Lisenssin ostamiseen on 2 maksuyhdyskäytävää: PayU ja 2Checkout. Jos ostat lisenssin 2Checkout gatewayn kautta, se lähettää PDF-laskun sähköpostiosoitteeseesi, katso 2Checkout laskutus. Jos ostat PayU-yhdyskäytävän kautta tai pankkisiirrolla, valmistelemme ja lähetämme laskusi. Voit pyytää laskua organisaation hallintasivulta ", "Forum": "Foorumi", "SupportExplanation": "ABP Commercial -lisenssit tarjoavat ensiluokkaisen foorumituen ABP-kehyksen asiantuntijoista koostuvalle tiimille.", "PrivateTicket": "Yksityinen lippu", @@ -240,7 +240,7 @@ "FastEasy": "Nopea ja helppo", "AbpSuiteExplanation3": "ABP Suiten avulla voit luoda helposti CRUD-sivuja. Sinun tarvitsee vain määritellä entiteetti ja sen ominaisuudet, antaa loput ABP Suiten puolestasi! ABP Suite luo kaikki tarvittavat koodit CRUD-sivullesi muutamassa sekunnissa. Se tukee Angular-, MVC- ja Blazor-käyttöliittymiä.", "RichOptions": "Monipuoliset vaihtoehdot", - "AbpSuiteExplanation4": "ABP Suite tukee useita käyttöliittymävaihtoehtoja, kuten Razor Pages ja Kulma .Se tukee myös useita tietokantoja, kuten MongoDB ja kaikkia EntityFramework Core <: n tukemia tietokantoja. / strong> (MS SQL Server, Oracle, MySql, PostgreSQL ja lisää ).", + "AbpSuiteExplanation4": "ABP Suite supports multiple UI options like Razor Pages and Angular.It also supports multiple databases like MongoDB and all databases supported by EntityFramework Core (MS SQL Server, Oracle, MySql, PostgreSQL and more).", "AbpSuiteExplanation5": "Hyvä asia on, että sinun ei tarvitse huolehtia näistä vaihtoehdoista. ABP Suite ymmärtää projektisi tyypin ja luo koodin projektillesi ja sijoittaa luodun koodin oikeaan paikkaan projektissasi.", "SourceCode": "Lähdekoodi", "AbpSuiteExplanation6": "ABP Suite luo lähdekoodin sinulle! Se ei luo taikuustiedostoja verkkosivun luomiseksi. ABP Suite luo lähdekoodin Entity, Repository, Application Service, Code First Migration, JavaScript / TypeScript ja CSHTML / HTML sekä tarvittavat liitännät. ABP Suite tuottaa koodin myös ohjelmistokehityksen parhaiden käytäntöjen mukaisesti, joten sinun ei tarvitse huolehtia luodun koodin laadusta.", diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/fr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/fr.json index c5f9f1db6c..e8f4165971 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/fr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/fr.json @@ -42,7 +42,7 @@ "NotOrganizationMember": "Vous n'êtes membre d'aucune organisation.", "UnsubscribeLicenseExpirationEmailSuccessTitle": "Désabonnement réussi", "UnsubscribeLicenseExpirationEmailSuccessMessage": "Vous ne recevrez plus d'e-mails de rappel de la date d'expiration de la licence.", - "IndexPageHeroSection": "Une plate-forme de développement Web complète intégrée cadre ", + "IndexPageHeroSection": "Une plate-forme de développement Web complète intégrée cadre ", "AbpCommercialShortDescription": "ABP Commercial fournit des modules d'application prédéfinis, des outils de développement rapide d'applications, des thèmes d'interface utilisateur professionnels, un support premium et plus encore.", "LiveDemo": "Démo en direct", "GetLicence": "Obtenez une licence", @@ -63,7 +63,7 @@ "AdditionalServices": "Des services supplémentaires", "WhatIsABPFramework": "QU'EST-CE QUE LE FRAMEWORK ABP?", "OpenSourceBaseFramework": "Framework de base Open Source", - "ABPFrameworkExplanation": "

ABP Commercial est basé sur ABP Framework, un framework d'applications Web open source et communautaire pour ASP.NET Core.

ABP Framework fournit une excellente infrastructure pour écrire maintenable et extensible et du code testable avec les meilleures pratiques.

Construit et intégré à des outils populaires que vous connaissez déjà. Faible courbe d'apprentissage, adaptation facile, développement confortable.

", + "ABPFrameworkExplanation": "

ABP Commercial est basé sur ABP Framework, un framework d'applications Web open source et communautaire pour ASP.NET Core.

ABP Framework fournit une excellente infrastructure pour écrire maintenable et extensible et du code testable avec les meilleures pratiques.

Construit et intégré à des outils populaires que vous connaissez déjà. Faible courbe d'apprentissage, adaptation facile, développement confortable.

", "Modular": "Modulaire", "MicroserviceCompatible": "Compatible avec les microservices", "DomainDrivenDesignInfrastructure": "Infrastructure de conception pilotée par domaine", @@ -78,7 +78,7 @@ "ManyMore": "Beaucoup plus", "ExploreTheABPFramework": "Explorez le Framework ABP", "WhyUseTheABPCommercial": "Pourquoi utiliser ABP Commercial?", - "WhyUseTheABPCommercialExplanation": "

La création d'applications Web de niveau entreprise peut être complexe et prendre du temps.

ABP Commercial offre l'infrastructure de base parfaite nécessaire pour tous les ASP.NET Core modernes de niveau entreprise solutions basées. De la conception au déploiement, l'ensemble du cycle de développement est optimisé par les fonctionnalités et modules intégrés d'ABP.

", + "WhyUseTheABPCommercialExplanation": "

La création d'applications Web de niveau entreprise peut être complexe et prendre du temps.

ABP Commercial offre l'infrastructure de base parfaite nécessaire pour tous les ASP.NET Core modernes de niveau entreprise solutions basées. De la conception au déploiement, l'ensemble du cycle de développement est optimisé par les fonctionnalités et modules intégrés d'ABP.

", "StartupTemplatesShortDescription": "Les modèles de démarrage vous permettent de démarrer votre projet en quelques secondes.", "UIFrameworksOptions": "Options de frameworks d'interface utilisateur;", "DatabaseProviderOptions": "Options du fournisseur de base de données;", @@ -145,19 +145,19 @@ "DeveloperGuide": "Guide du développeur", "ModuleTemplate": "Modèle de module", "ModuleTemplateExplanation1": "Vous souhaitez créer un module et le réutiliser dans différentes applications? Ce modèle de démarrage prépare tout pour commencer à créer un module d'application réutilisable ou un microservice .", - "ModuleTemplateExplanation2": "

Vous pouvez prendre en charge un ou plusieurs frameworks d'interface utilisateur, un ou plusieurs fournisseurs de bases de données pour un seul module. Le modèle de démarrage est configuré pour exécuter et tester votre module dans une application minimale en plus de l'infrastructure de test d'unité et d'intégration.

Consultez le guide du développeur pour obtenir des informations techniques sur ce modèle.

", + "ModuleTemplateExplanation2": "

Vous pouvez prendre en charge un ou plusieurs frameworks d'interface utilisateur, un ou plusieurs fournisseurs de bases de données pour un seul module. Le modèle de démarrage est configuré pour exécuter et tester votre module dans une application minimale en plus de l'infrastructure de test d'unité et d'intégration.

Consultez le guide du développeur pour obtenir des informations techniques sur ce modèle.

", "WithAllStyleOptions": "avec toutes les options de style", "Demo": "Démo", "SeeAllModules": "Voir tous les modules", "ABPCLIExplanation": "ABP CLI (Command Line Interface) est un outil de ligne de commande pour effectuer certaines opérations courantes pour les solutions basées sur ABP.", "ABPSuiteEasilyCURD": "ABP Suite est un outil qui vous permet de créer facilement des pages CRUD", - "WeAreHereToHelp": "Nous sommes ici pour Aide ", + "WeAreHereToHelp": "Nous sommes ici pour Aide ", "BrowseOrAskQuestion": "Vous pouvez parcourir nos rubriques d'aide ou rechercher dans les questions fréquemment posées, ou vous pouvez nous poser une question en utilisant le formulaire de contact .", "SearchQuestionPlaceholder": "Rechercher dans les questions fréquemment posées", "WhatIsTheABPCommercial": "Qu'est-ce que la publicité ABP?", "WhatAreDifferencesThanAbpFramework": "Quelles sont les différences entre le Framework ABP open source et le ABP Commercial?", "ABPCommercialExplanation": "ABP Commercial est un ensemble de modules, d'outils, de thèmes et de services premium basés sur le framework ABP open source. ABP Commercial est développé et soutenu par la même équipe derrière le cadre ABP.", - "WhatAreDifferencesThanABPFrameworkExplanation": "

Framework ABP est un framework de développement d'applications modulaire, thématique et compatible avec les micro-services pour ASP.NET Core. Il fournit une architecture complète et une infrastructure solide pour vous permettre de vous concentrer sur votre propre code métier plutôt que de vous répéter à chaque nouveau projet. Il est basé sur les meilleures pratiques de développement logiciel et les outils populaires que vous connaissez déjà.

Le framework ABP est entièrement gratuit, open source et géré par la communauté. Il fournit également un thème gratuit et des modules prédéfinis (par exemple, la gestion des identités et la gestion des locataires).

", + "WhatAreDifferencesThanABPFrameworkExplanation": "

Framework ABP est un framework de développement d'applications modulaire, thématique et compatible avec les micro-services pour ASP.NET Core. Il fournit une architecture complète et une infrastructure solide pour vous permettre de vous concentrer sur votre propre code métier plutôt que de vous répéter à chaque nouveau projet. Il est basé sur les meilleures pratiques de développement logiciel et les outils populaires que vous connaissez déjà.

Le framework ABP est entièrement gratuit, open source et géré par la communauté. Il fournit également un thème gratuit et des modules prédéfinis (par exemple, la gestion des identités et la gestion des locataires).

", "VisitTheFrameworkVSCommercialDocument": "Pour plus d'informations, consultez le lien suivant: {1} ", "ABPCommercialFollowingBenefits": "ABP Commercial ajoute les avantages suivants en plus du cadre ABP;", "Professional": "Professionnel", @@ -177,7 +177,7 @@ "ChangingLicenseType": "Puis-je changer mon type de licence à l'avenir?", "ChangingLicenseTypeExplanation": "Vous pouvez toujours ajouter de nouveaux développeurs dans votre même type de licence. Voir aussi \"Combien de développeurs peuvent travailler sur l'ABP Commercial?\". Vous pouvez également passer à une licence supérieure en payant la différence de prix calculée. Lorsque vous passez à un plan de licence supérieur, vous bénéficiez des avantages du nouveau plan, mais la mise à niveau de la licence ne modifie pas la date d'expiration de la licence.", "LicenseExtendUpgradeDiff": "Quelle est la différence entre l'extension de licence et la mise à niveau?", - "LicenseExtendUpgradeDiffExplanation": " Extension: en prolongeant / renouvelant votre licence, vous continuerez à bénéficier d'une assistance premium et à des mises à jour majeures pour les modules et les thèmes. De plus, vous pourrez continuer à créer de nouveaux projets. Et vous pourrez toujours utiliser ABP Suite, ce qui accélère votre développement.
Mise à niveau: En mettant à jour votre licence, vous passerez à un plan de licence supérieur qui vous permettra d’obtenir des avantages supplémentaires . Consultez le tableau de comparaison des licences pour vérifier les différences entre les plans de licence. En revanche, lors de la mise à niveau, la date d'expiration de votre licence ne changera pas! Pour prolonger la date de fin de votre licence, vous devez prolonger votre licence.", + "LicenseExtendUpgradeDiffExplanation": " Extension: en prolongeant/renouvelant votre licence, vous continuerez à bénéficier d'une assistance premium et à des mises à jour majeures pour les modules et les thèmes. De plus, vous pourrez continuer à créer de nouveaux projets. Et vous pourrez toujours utiliser ABP Suite, ce qui accélère votre développement.
Mise à niveau: En mettant à jour votre licence, vous passerez à un plan de licence supérieur qui vous permettra d’obtenir des avantages supplémentaires . Consultez le tableau de comparaison des licences pour vérifier les différences entre les plans de licence. En revanche, lors de la mise à niveau, la date d'expiration de votre licence ne changera pas! Pour prolonger la date de fin de votre licence, vous devez prolonger votre licence.", "LicenseRenewalCost": "Quel est le coût du renouvellement de la licence après 1 an?", "LicenseRenewalCostExplanation": "Le taux de renouvellement (extension) de toutes les licences perpétuelles ABP Commercial correspond à {0} du prix catalogue de la licence. Le prix de renouvellement de la licence d'équipe standard est de {1} $, la licence professionnelle standard de {2} $ et la licence d'entreprise standard de {3} $. Si vous êtes déjà client, connectez-vous à votre compte pour consulter les tarifs de renouvellement disponibles.", "HowDoIRenewMyLicense": "Comment renouveler ma licence?", @@ -186,13 +186,13 @@ "IsSourceCodeIncludedExplanation1": "Dépend du type de licence que vous avez acheté:", "IsSourceCodeIncludedExplanation2": " Équipe : votre solution utilise les modules et les thèmes en tant que packages NuGet et NPM. Il n'inclut pas leur code source. De cette façon, vous pouvez facilement mettre à niveau ces modules et thèmes chaque fois qu'une nouvelle version est disponible. Cependant, vous ne pouvez pas obtenir le code source des modules et des thèmes.", "IsSourceCodeIncludedExplanation3": " Entreprise / Entreprise : en plus de la licence Team, vous pouvez télécharger le code source de tout module ou thème dont vous avez besoin. Vous pouvez même supprimer les références de package NuGet / NPM pour un module particulier et ajouter son code source directement à votre solution pour le modifier complètement.", - "IsSourceCodeIncludedExplanation4": "

L'inclusion du code source d'un module dans votre solution vous donne le maximum de liberté pour personnaliser ce module. Cependant, il ne sera pas possible de mettre à jour automatiquement le module lorsqu'une nouvelle version est publiée.

Aucune des licences n'inclut le code source d'ABP Suite, qui est un outil externe qui génère du code pour vous et vous aide à votre développement.

Consultez la page de tarification pour connaître les autres différences entre les types de licence.

", + "IsSourceCodeIncludedExplanation4": "

L'inclusion du code source d'un module dans votre solution vous donne le maximum de liberté pour personnaliser ce module. Cependant, il ne sera pas possible de mettre à jour automatiquement le module lorsqu'une nouvelle version est publiée.

Aucune des licences n'inclut le code source d'ABP Suite, qui est un outil externe qui génère du code pour vous et vous aide à votre développement.

Consultez la page de tarification pour connaître les autres différences entre les types de licence.

", "ChangingDevelopers": "Puis-je changer les développeurs enregistrés de mon organisation à l'avenir?", "ChangingDevelopersExplanation": "En plus d'ajouter de nouveaux développeurs à votre licence, vous pouvez également modifier les développeurs existants (vous pouvez supprimer un développeur et en ajouter un nouveau sur le même siège) sans aucun coût supplémentaire.", "WhatHappensWhenLicenseEnds": "Que se passe-t-il à la fin de ma période de licence?", "WhatHappensWhenLicenseEndsExplanation1": "Le type de licence commerciale ABP est licence perpétuelle . Une fois votre licence expirée, vous pouvez continuer à développer votre projet. Et vous n'êtes pas obligé de renouveler votre licence. Votre licence est livrée avec un plan de mise à jour et de support d'un an prêt à l'emploi. Pour continuer à recevoir de nouvelles fonctionnalités, des améliorations de performances, des corrections de bogues, une assistance et continuer à utiliser ABP Suite, assurez-vous de renouveler votre plan chaque année. Lorsque votre licence expirera, vous ne pourrez plus bénéficier des avantages suivants;", "WhatHappensWhenLicenseEndsExplanation2": "Vous ne pouvez pas créer de nouvelles solutions en utilisant ABP Commercial, mais vous pouvez continuer à développer vos applications existantes pour toujours.", - "WhatHappensWhenLicenseEndsExplanation3": "Vous pourrez obtenir des mises à jour pour les modules et les thèmes de votre version MAJEURE. Par example; si vous utilisez la v3.2.0 d'un module, vous pouvez toujours obtenir des mises à jour pour la v3.x.x (v3.3.0, v3.5.2 ... etc.) de ce module. Mais vous ne pouvez pas obtenir de mises à jour pour la prochaine version majeure (comme v4.x, v5.x)", + "WhatHappensWhenLicenseEndsExplanation3": "Vous pourrez obtenir des mises à jour pour les modules et les thèmes de votre version MAJEURE. Par example; si vous utilisez la v3.2.0 d'un module, vous pouvez toujours obtenir des mises à jour pour la v3.x.x (v3.3.0, v3.5.2... etc.) de ce module. Mais vous ne pouvez pas obtenir de mises à jour pour la prochaine version majeure (comme v4.x, v5.x)", "WhatHappensWhenLicenseEndsExplanation4": "Vous ne pouvez pas installer de nouveaux modules et thèmes ajoutés à la plate-forme ABP Commercial après la fin de votre licence.", "WhatHappensWhenLicenseEndsExplanation5": "Vous ne pouvez pas utiliser ABP Suite.", "WhatHappensWhenLicenseEndsExplanation6": "Vous ne pouvez plus bénéficier de l ' assistance premium .", @@ -203,7 +203,7 @@ "TrialPlan": "Avez-vous un plan d'essai?", "TrialPlanExplanation": "Pour l'instant, ABP Commercial n'a pas de plan d'essai. Pour les licences Team, nous offrons une garantie de remboursement de 30 jours. Vous pouvez simplement demander un remboursement dans les 30 premiers jours. Pour les licences Business et Enterprise, nous fournissons un remboursement de 60% en 30 jours. En effet, les licences Business et Enterprise incluent le code source complet de tous les modules et thèmes.", "DoYouAcceptBankWireTransfer": "Acceptez-vous les virements bancaires?", - "DoYouAcceptBankWireTransferExplanation": "Oui, nous acceptons les virements bancaires.
Après avoir envoyé le montant de la licence par virement bancaire, envoyez-nous votre reçu et le type de licence demandé par e-mail.
Nos coordonnées bancaires internationales:", + "DoYouAcceptBankWireTransferExplanation": "Oui, nous acceptons les virements bancaires.
Après avoir envoyé le montant de la licence par virement bancaire, envoyez-nous votre reçu et le type de licence demandé par e-mail.
Nos coordonnées bancaires internationales:", "HowToUpgrade": "Comment mettre à niveau des applications existantes lorsqu'une nouvelle version est disponible?", "HowToUpgradeExplanation1": "Lorsque vous créez une nouvelle application à l'aide d'ABP Commercial, tous les modules et le thème sont utilisés en tant que packages NuGet et NPM. Ainsi, vous pouvez facilement mettre à jour les packages lorsqu'une nouvelle version est disponible.", "HowToUpgradeExplanation2": "Outre les mises à niveau standard de NuGet / NPM, ABP CLI fournit une commande de mise à jour qui recherche et met automatiquement à niveau tous les packages associés à ABP dans votre solution.", @@ -230,7 +230,7 @@ "HowCanIRefundVatExplanation2": "Connectez-vous à votre compte 2Checkout ", "HowCanIRefundVatExplanation3": "Trouvez la commande appropriée et appuyez sur \"Rembourser la TVA tardive\" (entrez votre numéro de TVA)", "HowCanIGetMyInvoice": "Comment puis-je obtenir ma facture?", - "HowCanIGetMyInvoiceExplanation": "Il existe 2 passerelles de paiement pour l'achat d'une licence: PayU et 2Checkout. Si vous achetez votre licence via la passerelle 2Checkout, elle envoie la facture PDF à votre adresse e-mail, voir Facturation 2Checkout. Si vous achetez via la passerelle PayU ou par virement bancaire, nous préparerons et vous enverrons votre facture. Vous pouvez demander votre facture sur la page de gestion de l'organisation. ", + "HowCanIGetMyInvoiceExplanation": "Il existe 2 passerelles de paiement pour l'achat d'une licence: PayU et 2Checkout. Si vous achetez votre licence via la passerelle 2Checkout, elle envoie la facture PDF à votre adresse e-mail, voir Facturation 2Checkout. Si vous achetez via la passerelle PayU ou par virement bancaire, nous préparerons et vous enverrons votre facture. Vous pouvez demander votre facture sur la page de gestion de l'organisation.", "Forum": "Forum", "SupportExplanation": "ABP Commercial Licences fournit un support de forum premium par une équipe composée d'experts ABP Framework.", "PrivateTicket": "Billet privé", @@ -240,7 +240,7 @@ "FastEasy": "Rapide et facile", "AbpSuiteExplanation3": "ABP Suite vous permet de créer facilement des pages CRUD. Il vous suffit de définir votre entité et ses propriétés, laissez le reste à ABP Suite pour vous! ABP Suite génère tout le code nécessaire pour votre page CRUD en quelques secondes. Il prend en charge les interfaces utilisateur Angular, MVC et Blazor.", "RichOptions": "Options riches", - "AbpSuiteExplanation4": "ABP Suite prend en charge plusieurs options d'interface utilisateur telles que Razor Pages et Angular . Il prend également en charge plusieurs bases de données telles que MongoDB et toutes les bases de données prises en charge par EntityFramework Core < / strong> (MS SQL Server, Oracle, MySql, PostgreSQL et plus ).", + "AbpSuiteExplanation4": "ABP Suite prend en charge plusieurs options d'interface utilisateur telles que Razor Pages et Angular . Il prend également en charge plusieurs bases de données telles que MongoDB et toutes les bases de données prises en charge par EntityFramework Core < / strong> (MS SQL Server, Oracle, MySql, PostgreSQL et plus ).", "AbpSuiteExplanation5": "La bonne chose est que vous n'avez pas à vous soucier de ces options. ABP Suite comprend votre type de projet et génère le code de votre projet et place le code généré au bon endroit dans votre projet.", "SourceCode": "Code source", "AbpSuiteExplanation6": "ABP Suite génère le code source pour vous! Il ne génère pas de fichiers magiques pour générer la page Web. ABP Suite génère le code source pour Entity, Repository, Application Service, Code First Migration, JavaScript / TypeScript et CSHTML / HTML ainsi que les interfaces nécessaires. ABP Suite génère également le code selon les meilleures pratiques de développement logiciel, vous n'avez donc pas à vous soucier de la qualité du code généré.", diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hi.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hi.json index 54216f7719..4af2f31246 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hi.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/hi.json @@ -42,7 +42,7 @@ "NotOrganizationMember": "आप किसी संगठन के सदस्य नहीं हैं।", "UnsubscribeLicenseExpirationEmailSuccessTitle": "सफलतापूर्वक सदस्यता समाप्त कर दी गई", "UnsubscribeLicenseExpirationEmailSuccessMessage": "अब आपको लाइसेंस समाप्ति तिथि अनुस्मारक ईमेल प्राप्त नहीं होंगे।", - "IndexPageHeroSection": " एक पूर्ण वेब विकास मंच अंतर्निहित ढांचा ", + "IndexPageHeroSection": "एक पूर्ण वेब डेवलपमेंट प्लेटफॉर्मबिल्ट-ऑन Framework", "AbpCommercialShortDescription": "एबीपी कमर्शियल प्री-बिल्ट एप्लिकेशन मॉड्यूल, रैपिड एप्लिकेशन डेवलपमेंट टूलिंग, पेशेवर यूआई थीम, प्रीमियम सपोर्ट आदि प्रदान करता है।", "LiveDemo": "लाइव डेमो", "GetLicence": "एक लाइसेंस प्राप्त करें", @@ -63,7 +63,7 @@ "AdditionalServices": "अतिरिक्त सेवाएं", "WhatIsABPFramework": "ABP फ्रेमवर्क क्या है?", "OpenSourceBaseFramework": "ओपन सोर्स बेस फ्रेमवर्क", - "ABPFrameworkExplanation": "

ABP कमर्शियल ASP.NET Core पर एक खुला स्रोत और समुदाय संचालित वेब एप्लीकेशन फ्रेमवर्क है। और सर्वोत्तम प्रथाओं के साथ परीक्षण योग्य कोड।

आप पहले से ही जानते हैं कि लोकप्रिय उपकरणों पर निर्मित और एकीकृत। कम सीखने की अवस्था, आसान अनुकूलन, आरामदायक विकास। ", + "ABPFrameworkExplanation": "

ABP कमर्शियल ASP.NET Core पर एक खुला स्रोत और समुदाय संचालित वेब एप्लीकेशन फ्रेमवर्क है। और सर्वोत्तम प्रथाओं के साथ परीक्षण योग्य कोड।

आप पहले से ही जानते हैं कि लोकप्रिय उपकरणों पर निर्मित और एकीकृत। कम सीखने की अवस्था, आसान अनुकूलन, आरामदायक विकास।

", "Modular": "मॉड्यूलर", "MicroserviceCompatible": "माइक्रोसिस्ट सेवा संगत", "DomainDrivenDesignInfrastructure": "डोमेन संचालित डिज़ाइन इन्फ्रास्ट्रक्चर", @@ -78,7 +78,7 @@ "ManyMore": "बहुत सारी", "ExploreTheABPFramework": "ABP फ्रेमवर्क का अन्वेषण करें", "WhyUseTheABPCommercial": "एबीपी कमर्शियल का उपयोग क्यों करें?", - "WhyUseTheABPCommercialExplanation": "

बिल्डिंग एंटरप्राइज-ग्रेड वेब एप्लिकेशन जटिल और समय लेने वाली हो सकती हैं।

ABP कमर्शियल सभी आधुनिक एंटरप्राइज-ग्रेड ASP.NET Core के लिए आवश्यक सही आधारभूत संरचना प्रदान करता है। आधारित समाधान। डिज़ाइन से परिनियोजन तक, संपूर्ण विकास चक्र ABP की अंतर्निहित विशेषताओं और मॉड्यूल द्वारा सशक्त है। बिल्डिंग एंटरप्राइज-ग्रेड वेब एप्लिकेशन जटिल और समय लेने वाली हो सकती हैं।

ABP कमर्शियल सभी आधुनिक एंटरप्राइज-ग्रेड ASP.NET Core के लिए आवश्यक सही आधारभूत संरचना प्रदान करता है। आधारित समाधान। डिज़ाइन से परिनियोजन तक, संपूर्ण विकास चक्र ABP की अंतर्निहित विशेषताओं और मॉड्यूल द्वारा सशक्त है।

", "StartupTemplatesShortDescription": "स्टार्टअप टेम्प्लेट आपको कुछ ही सेकंड में अपने प्रोजेक्ट के लिए जंप-स्टार्ट करते हैं।", "UIFrameworksOptions": "यूआई चौखटे विकल्प;", "DatabaseProviderOptions": "डेटाबेस प्रदाता विकल्प;", @@ -108,7 +108,7 @@ "ProudToWorkWith": "गर्व के साथ काम करने के लिए", "OurConsumers": "दुनिया भर में 50 से अधिक देशों के सैकड़ों उद्यम और डेवलपर्स एबीपी कमर्शियल पर निर्भर हैं।", "JoinOurConsumers": "उनके साथ जुड़ें और तेजी से अद्भुत उत्पादों का निर्माण करें।", - "AdditionalServicesExplanation": "क्या आपको अतिरिक्त या कस्टम सेवाओं की आवश्यकता है? हम और हमारे साथी प्रदान कर सकते हैं; ", + "AdditionalServicesExplanation": "क्या आपको अतिरिक्त या कस्टम सेवाओं की आवश्यकता है? हम और हमारे साथी प्रदान कर सकते हैं; ", "CustomProjectDevelopment": "कस्टम परियोजना विकास", "CustomProjectDevelopmentExplanation": "अपने कस्टम प्रोजेक्ट्स के लिए समर्पित डेवलपर्स।", "PortingExistingProjects": "मौजूदा परियोजनाओं को पोर्ट करना", @@ -135,7 +135,7 @@ "GoHome": "घर जाओ", "CreateLiveDemo": "लाइव डेमो बनाएँ", "RegisterToTheNewsletter": "ABP.IO के बारे में जानकारी प्राप्त करने के लिए न्यूज़लेटर के लिए रजिस्टर करें, जिसमें नई रिलीज़ आदि शामिल हैं।", - "EnterYourEmailOrLogin": "अपने मौजूदा खाते का उपयोग करके अपना डेमो बनाने के लिए अपना ई-मेल पता दर्ज करें या अपना प्रदर्शन लॉगिन करें।", + "EnterYourEmailOrLogin": "अपना डेमो बनाने के लिए अपना ई-मेल पता दर्ज करें या अपने मौजूदा खाते का उपयोग करके लॉगिन करें।", "ApplicationTemplate": "एप्लिकेशन टेम्पलेट", "ApplicationTemplateExplanation": "एप्लिकेशन स्टार्टअप टेम्पलेट का उपयोग एक नया वेब एप्लिकेशन बनाने के लिए किया जाता है।", "EfCoreProvider": "इकाई ढाँचा (SQL सर्वर, MySQL, PostgreSQL, Oracle और अन्य का समर्थन करता है)", @@ -144,58 +144,58 @@ "SeeTheGuideOrGoToTheLiveDemo": "इस टेम्पलेट के बारे में तकनीकी जानकारी के लिए डेवलपर गाइड देखें या लाइव डेमो पर जाएं।", "DeveloperGuide": "डेवलपर गाइड", "ModuleTemplate": "मॉड्यूल टेम्पलेट", - "ModuleTemplateExplanation1": "आप एक मॉड्यूल बनाना और विभिन्न अनुप्रयोगों में पुन: उपयोग करना चाहते हैं? यह स्टार्टअप टेम्पलेट एक पुन: प्रयोज्य अनुप्रयोग मॉड्यूल या microservice बनाने के लिए शुरू करने के लिए सब कुछ तैयार करता है।", - "ModuleTemplateExplanation2": "

आप एकल मॉड्यूल के लिए एकल या एकाधिक UI फ्रेमवर्क, एकल या एकाधिक डेटाबेस प्रदाता का समर्थन कर सकते हैं। स्टार्टअप टेम्प्लेट को चलाने के लिए कॉन्फ़िगर किया गया है और अपने मॉड्यूल का परीक्षण करें यूनिट और एकीकरण परीक्षण के बुनियादी ढांचे के अलावा न्यूनतम आवेदन में।

इस टेम्पलेट के बारे में तकनीकी जानकारी के लिए डेवलपर गाइड देखें। ", + "ModuleTemplateExplanation1": "आप एक मॉड्यूल बनाना और विभिन्न अनुप्रयोगों में पुन: उपयोग करना चाहते हैं? यह स्टार्टअप टेम्पलेट एक पुन: प्रयोज्य अनुप्रयोग मॉड्यूल या microservice बनाने के लिए शुरू करने के लिए सब कुछ तैयार करता है।", + "ModuleTemplateExplanation2": "

आप एकल मॉड्यूल के लिए एकल या एकाधिक UI फ्रेमवर्क, एकल या एकाधिक डेटाबेस प्रदाता का समर्थन कर सकते हैं। स्टार्टअप टेम्प्लेट को चलाने के लिए कॉन्फ़िगर किया गया है और अपने मॉड्यूल का परीक्षण करें यूनिट और एकीकरण परीक्षण के बुनियादी ढांचे के अलावा न्यूनतम आवेदन में।

इस टेम्पलेट के बारे में तकनीकी जानकारी के लिए डेवलपर गाइड देखें।

", "WithAllStyleOptions": "सभी शैली विकल्पों के साथ", "Demo": "डेमो", "SeeAllModules": "सभी मॉड्यूल देखें", "ABPCLIExplanation": "ABP CLI (कमांड लाइन इंटरफेस) ABP आधारित समाधानों के लिए कुछ सामान्य ऑपरेशन करने के लिए एक कमांड लाइन टूल है।", "ABPSuiteEasilyCURD": "एबीपी सूट एक उपकरण है जो आपको आसानी से CRUD पेज बनाने की अनुमति देता है", - "WeAreHereToHelp": "हम यहाँ हैं मदद ", - "BrowseOrAskQuestion": "आप हमारे सहायता विषयों को ब्राउज़ कर सकते हैं या अक्सर पूछे जाने वाले प्रश्नों में खोज कर सकते हैं, या आप संपर्क फ़ॉर्म का उपयोग करके हमसे एक प्रश्न पूछ सकते हैं।", + "WeAreHereToHelp": "हम यहाँ हैं मदद ", + "BrowseOrAskQuestion": "आप हमारे सहायता विषयों को ब्राउज़ कर सकते हैं या अक्सर पूछे जाने वाले प्रश्नों में खोज कर सकते हैं, या आप संपर्क फ़ॉर्म का उपयोग करके हमसे एक प्रश्न पूछ सकते हैं।", "SearchQuestionPlaceholder": "अक्सर पूछे जाने वाले प्रश्नों में खोजें", "WhatIsTheABPCommercial": "ABP कमर्शियल क्या है?", "WhatAreDifferencesThanAbpFramework": "ओपन सोर्स ABP फ्रेमवर्क और ABP कमर्शियल के बीच क्या अंतर हैं?", - "ABPCommercialExplanation": "एबीपी कमर्शियल, खुले स्रोत के शीर्ष पर बनाए गए प्रीमियम मॉड्यूल, टूल, थीम और सेवाओं का एक सेट है जो ABP फ्रेमवर्क है। एबीपी कमर्शियल को एबीपी फ्रेमवर्क के पीछे उसी टीम द्वारा विकसित और समर्थित किया जा रहा है।", - "WhatAreDifferencesThanABPFrameworkExplanation": "

ABP फ्रेमवर्क ASP.NET Core के लिए एक मॉड्यूलर, थीमेबल, माइक्रो-सर्विस संगत एप्लिकेशन डेवलपमेंट फ्रेमवर्क है। यह आपको हर नई परियोजना के लिए खुद को दोहराने के बजाय अपने खुद के व्यवसाय कोड पर ध्यान केंद्रित करने के लिए एक पूर्ण वास्तुकला और एक मजबूत बुनियादी ढांचा प्रदान करता है। यह सॉफ्टवेयर विकास सर्वोत्तम प्रथाओं और लोकप्रिय उपकरणों पर आधारित है जिन्हें आप पहले से जानते हैं।

एबीपी फ्रेमवर्क पूरी तरह से मुक्त, खुला स्रोत और समुदाय-संचालित है। यह एक मुफ्त थीम और कुछ पूर्व-निर्मित मॉड्यूल (जैसे पहचान प्रबंधन और किरायेदार प्रबंधन) प्रदान करता है। ", - "VisitTheFrameworkVSCommercialDocument": "अधिक जानकारी के लिए, निम्न लिंक पर जाएं {1} ", + "ABPCommercialExplanation": "एबीपी कमर्शियल ओपन सोर्स ABP फ्रेमवर्क के शीर्ष पर निर्मित प्रीमियम मॉड्यूल, टूल, थीम और सेवाओं का एक सेट है। एबीपी कमर्शियल को एबीपी ढांचे के पीछे उसी टीम द्वारा विकसित और समर्थित किया जा रहा है।", + "WhatAreDifferencesThanABPFrameworkExplanation": "

ABP फ्रेमवर्क ASP.NET कोर के लिए एक मॉड्यूलर, थीम योग्य, माइक्रो-सर्विस संगत एप्लिकेशन डेवलपमेंट फ्रेमवर्क है। यह आपको हर नई परियोजना के लिए खुद को दोहराने के बजाय अपने स्वयं के व्यवसाय कोड पर ध्यान केंद्रित करने के लिए एक पूर्ण वास्तुकला और एक मजबूत बुनियादी ढांचा प्रदान करता है। यह सॉफ्टवेयर विकास सर्वोत्तम प्रथाओं और लोकप्रिय टूल पर आधारित है जिन्हें आप पहले से जानते हैं।

एबीपी ढांचा पूरी तरह से मुक्त, खुला स्रोत और समुदाय संचालित है। यह एक मुफ्त थीम और कुछ पूर्व-निर्मित मॉड्यूल (जैसे पहचान प्रबंधन और किरायेदार प्रबंधन) भी प्रदान करता है।

", + "VisitTheFrameworkVSCommercialDocument": "अधिक जानकारी के लिए, निम्न लिंक पर जाएँ {1} ", "ABPCommercialFollowingBenefits": "एबीपी वाणिज्यिक एबीपी ढांचे के शीर्ष पर निम्नलिखित लाभ जोड़ता है;", "Professional": "पेशेवर", "UIThemes": "यूआई थीम", - "EnterpriseModules": "एंटरप्राइज रेडी, फीचर से भरपूर, पहले से निर्मित एप्लिकेशन मॉड्यूल (जैसे पहचान सर्वर प्रबंधन, सास प्रबंधन, भाषा प्रबंधन)", - "ToolingToSupport": "अपनी विकास उत्पादकता का समर्थन करने का टूल", - "PremiumSupportLink": "प्रीमियम समर्थन ", + "EnterpriseModules": "एंटरप्राइज़ तैयार, सुविधा संपन्न, पूर्व-निर्मित एप्लिकेशन मॉड्यूल (उदा. पहचान सर्वर प्रबंधन, SaaS प्रबंधन, भाषा प्रबंधन)", + "ToolingToSupport": "आपकी विकास उत्पादकता का समर्थन करने के लिए टूलिंग (उदा.ABP Suite)", + "PremiumSupportLink": "प्रीमियम समर्थन", "WhatDoIDownloadABPCommercial": "जब मैं एबीपी कमर्शियल खरीदता हूं तो क्या डाउनलोड करता हूं?", - "CreateUnlimitedSolutions": "एक बार जब आप एक एबीपी कमर्शियल लाइसेंस खरीदते हैं, तो आप असीमित समाधान बना पाएंगे, जैसे कि Get Started दस्तावेज़ में वर्णित है।", + "CreateUnlimitedSolutions": "एक बार जब आप एबीपी वाणिज्यिक लाइसेंस खरीद लेते हैं, तो आप असीमित समाधान बनाने में सक्षम होंगे जैसा कि आरंभ करना दस्तावेज़ में वर्णित है।", "ABPCommercialSolutionExplanation": "जब आप एक नया एप्लिकेशन बनाते हैं, तो आपको अपनी प्राथमिकताओं के आधार पर एक विज़ुअल स्टूडियो समाधान (एक स्टार्टअप टेम्प्लेट) मिलता है। डाउनलोड किए गए समाधान में आपके लिए पहले से इंस्टॉल और कॉन्फ़िगर किए गए वाणिज्यिक मॉड्यूल और थीम हैं। यदि आप चाहें तो आप पहले से स्थापित मॉड्यूल को हटा सकते हैं या किसी अन्य मॉड्यूल को जोड़ सकते हैं। सभी मॉड्यूल और थीम डिफ़ॉल्ट रूप से एक NuGet / NPM पैकेज का उपयोग किया जाता है।", - "StartDevelopWithTutorials": "डाउनलोड किया गया समाधान अच्छी तरह से वास्तुकला और दस्तावेज है। आप अपने स्वयं के व्यवसाय कोड को इस आधार पर विकसित करना शुरू कर सकते हैं कि वह ट्यूटोरियल्स का अनुसरण कर सकता है", - "TryTheCommercialDemo": "ABP कमर्शियल स्टार्टअप टेम्प्लेट का उपयोग करके बनाए गए एक नमूना अनुप्रयोग को देखने के लिए आप डेमो का प्रयास कर सकते हैं।", + "StartDevelopWithTutorials": "डाउनलोड किया गया समाधान अच्छी तरह से संरचित और प्रलेखित है। आप ट्यूटोरियल . का अनुसरण करके इसके आधार पर अपना खुद का व्यवसाय कोड विकसित करना शुरू कर सकते हैं", + "TryTheCommercialDemo": "ABP कमर्शियल स्टार्टअप टेम्प्लेट का उपयोग करके बनाए गए एक नमूना एप्लिकेशन को देखने के लिए आप डेमो आज़मा सकते हैं।", "HowManyProducts": "ABP वाणिज्यिक का उपयोग करके मैं कितने विभिन्न उत्पाद / समाधान बना सकता हूं?", "HowManyProductsExplanation": "ABP प्रोजेक्ट बनाने की कोई सीमा नहीं है। आप जितने चाहें उतने प्रोजेक्ट बना सकते हैं, उन्हें विकसित कर सकते हैं और उन्हें विभिन्न सर्वरों पर अपलोड कर सकते हैं।", "HowManyDevelopers": "एबीपी कमर्शियल पर कितने डेवलपर काम कर सकते हैं?", - "HowManyDevelopersExplanation": "एबीपी वाणिज्यिक लाइसेंस प्रति डेवलपर हैं। विभिन्न लाइसेंस प्रकारों में अलग-अलग डेवलपर सीमाएँ होती हैं। हालाँकि, जब भी आपको आवश्यकता हो, आप किसी भी प्रकार के लाइसेंस में अधिक डेवलपर्स जोड़ सकते हैं। लाइसेंस प्रकार, डेवलपर सीमा और अतिरिक्त डेवलपर लागतों के लिए कीमतें पृष्ठ देखें।", + "HowManyDevelopersExplanation": "एबीपी वाणिज्यिक लाइसेंस प्रति डेवलपर हैं। विभिन्न लाइसेंस प्रकारों की अलग-अलग डेवलपर सीमाएँ होती हैं। हालांकि, जब भी आपको आवश्यकता हो, आप किसी भी प्रकार के लाइसेंस में अधिक डेवलपर जोड़ सकते हैं। लाइसेंस प्रकारों, डेवलपर सीमाओं और अतिरिक्त डेवलपर लागतों के लिए कीमतें पृष्ठ देखें।", "ChangingLicenseType": "क्या मैं भविष्य में अपना लाइसेंस प्रकार बदल सकता हूं?", "ChangingLicenseTypeExplanation": "आप अपने समान लाइसेंस प्रकार में हमेशा नए डेवलपर्स को जोड़ सकते हैं। यह भी देखें \"एबीपी कमर्शियल पर कितने डेवलपर काम कर सकते हैं?\"। आप गणना मूल्य अंतर का भुगतान करके एक उच्च लाइसेंस के लिए भी अपग्रेड कर सकते हैं। जब आप उच्च लाइसेंस योजना में अपग्रेड करते हैं, तो आपको नई योजना का लाभ मिलता है, लेकिन लाइसेंस अपग्रेड लाइसेंस की समाप्ति की तारीख को नहीं बदलता है।", "LicenseExtendUpgradeDiff": "लाइसेंस विस्तार और उन्नयन के बीच क्या अंतर है?", - "LicenseExtendUpgradeDiffExplanation": " विस्तार: अपने लाइसेंस का विस्तार / नवीनीकरण करके, आप प्रीमियम समर्थन प्राप्त करना और मॉड्यूल और थीम के लिए प्रमुख अपडेट प्राप्त करना जारी रखेंगे। इसके अलावा, आप नई परियोजनाओं को जारी रखने में सक्षम होंगे। और आप अभी भी एबीपी सूट का उपयोग कर पाएंगे जो आपके विकास को गति देता है।
उन्नयन: अपने लाइसेंस को अपग्रेड करके, आप एक उच्च लाइसेंस योजना को बढ़ावा देंगे जिससे आप अतिरिक्त लाभ प्राप्त कर सकेंगे। । लाइसेंस योजनाओं के बीच अंतर की जांच करने के लिए लाइसेंस तुलना तालिका देखें। दूसरी ओर, जब आप अपग्रेड करते हैं, तो आपकी लाइसेंस की समाप्ति तिथि नहीं बदलेगी! > अपनी लाइसेंस की अंतिम तिथि बढ़ाने के लिए, आपको अपने लाइसेंस का विस्तार करना होगा।", + "LicenseExtendUpgradeDiffExplanation": "Extending: By extending/renewing your license, you will continue to get premium support and get major updates for the modules and themes. Besides, you will be able to continue creating new projects. And you will still be able to use ABP Suite which speeds up your development.
Upgrading: By upgrading your license, you will promote to a higher license plan which will allow you to get additional benefits. See the license comparison table to check the differences between the license plans.On the other hand, when you upgrade, your license expiry date will not change!To extend your license end date, you need to extend your license.", "LicenseRenewalCost": "1 वर्ष के बाद लाइसेंस नवीनीकरण लागत क्या है?", - "LicenseRenewalCostExplanation": "सभी एबीपी वाणिज्यिक स्थायी लाइसेंसों का नवीकरण (विस्तार) दर लाइसेंस सूची मूल्य का {0} है। मानक टीम लाइसेंस का नवीनीकरण मूल्य $ {1} है, मानक व्यवसाय लाइसेंस $ {2} है और मानक एंटरप्राइज़ लाइसेंस $ {3} है। यदि आप पहले से ही ग्राहक हैं, तो उपलब्ध नवीनीकरण मूल्य निर्धारण की समीक्षा करने के लिए अपने खाते में लॉग इन करें ।", + "LicenseRenewalCostExplanation": "सभी एबीपी वाणिज्यिक स्थायी लाइसेंसों की नवीनीकरण (विस्तार) दर लाइसेंस सूची मूल्य का {0} है। मानक टीम लाइसेंस का नवीनीकरण मूल्य ${1} है, मानक व्यवसाय लाइसेंस ${2} है और मानक एंटरप्राइज़ लाइसेंस ${3} है। यदि आप पहले से ही ग्राहक हैं, तो उपलब्ध नवीनीकरण मूल्य-निर्धारण की समीक्षा के लिए अपने खाते में लॉग इन करें।", "HowDoIRenewMyLicense": "मैं अपने लाइसेंस का नवीनीकरण कैसे करूँ?", - "HowDoIRenewMyLicenseExplanation": "आप अपने लाइसेंस को संगठन प्रबंधन पृष्ठ पर नेविगेट करके नवीनीकृत कर सकते हैं। हमारी रियायती अर्ली रिन्यूअल दरों का लाभ उठाने के लिए, सुनिश्चित करें कि आप अपने लाइसेंस की समय सीमा समाप्त होने से पहले नवीनीकरण करें। हालाँकि, जब आपका प्रारंभिक नवीनीकरण अवसर बंद हो जाता है, तो यह जानने की चिंता न करें। आपकी सदस्यता समाप्त होने से पहले आपको 2 अनुस्मारक ई-मेल प्राप्त होंगे। हम उन्हें 30 दिन, समाप्ति से 7 दिन पहले भेज देंगे।", + "HowDoIRenewMyLicenseExplanation": "आप संगठन प्रबंधन पृष्ठ पर नेविगेट करके अपने लाइसेंस का नवीनीकरण कर सकते हैं। हमारी रियायती प्रारंभिक नवीनीकरण दरों का लाभ उठाने के लिए, अपने लाइसेंस की समय सीमा समाप्त होने से पहले नवीनीकरण करना सुनिश्चित करें। हालाँकि, यह जानने की चिंता न करें कि आपका प्रारंभिक नवीनीकरण अवसर कब बंद होगा। आपकी सदस्यता समाप्त होने से पहले आपको 2 अनुस्मारक ई-मेल प्राप्त होंगे। हम उन्हें समाप्ति से ७ दिन पहले, ३० दिनों में भेजेंगे।", "IsSourceCodeIncluded": "क्या मेरे लाइसेंस में वाणिज्यिक मॉड्यूल और थीम का स्रोत कोड शामिल है?", "IsSourceCodeIncludedExplanation1": "आपके द्वारा खरीदे गए लाइसेंस प्रकार पर निर्भर करता है:", "IsSourceCodeIncludedExplanation2": " टीम : आपका समाधान मॉड्यूल और थीम को NuGet और NPM पैकेज के रूप में उपयोग करता है। इसमें उनका स्रोत कोड शामिल नहीं है। इस तरह, जब भी कोई नया संस्करण उपलब्ध हो, आप इन मॉड्यूल और थीम को आसानी से अपग्रेड कर सकते हैं। हालाँकि, आप मॉड्यूल और थीम के स्रोत कोड प्राप्त नहीं कर सकते।", "IsSourceCodeIncludedExplanation3": " व्यवसाय / उद्यम : टीम लाइसेंस के अतिरिक्त, आप किसी भी मॉड्यूल या थीम की आवश्यकता के स्रोत कोड को डाउनलोड करने में सक्षम हैं। तुम भी एक विशेष मॉड्यूल के लिए NuGet / NPM पैकेज संदर्भ निकाल सकते हैं और इसे पूरी तरह से बदलने के लिए अपने समाधान के लिए अपने स्रोत कोड को सीधे जोड़ सकते हैं।", - "IsSourceCodeIncludedExplanation4": "

एक मॉड्यूल के स्रोत कोड को आपके समाधान में शामिल करना आपको उस मॉड्यूल को अनुकूलित करने की अधिकतम स्वतंत्रता देता है। हालाँकि, तब नया संस्करण जारी होने पर मॉड्यूल को स्वचालित रूप से अपग्रेड करना संभव नहीं होगा।

किसी भी लाइसेंस में ABP सुइट स्रोत कोड शामिल नहीं है, जो एक बाहरी उपकरण है जो आपके लिए कोड बनाता है और सहायता करता है आपके विकास के लिए।

लाइसेंस प्रकारों के अन्य अंतरों के लिए मूल्य निर्धारण पृष्ठ देखें। ", + "IsSourceCodeIncludedExplanation4": "

अपने समाधान में मॉड्यूल के स्रोत कोड को शामिल करने से आपको उस मॉड्यूल को अनुकूलित करने की अधिकतम स्वतंत्रता मिलती है। हालांकि, नया संस्करण जारी होने पर मॉड्यूल को स्वचालित रूप से अपग्रेड करना संभव नहीं होगा।

किसी भी लाइसेंस में एबीपी सूट स्रोत कोड शामिल नहीं है, जो एक बाहरी उपकरण है जो आपके लिए कोड उत्पन्न करता है और सहायता करता है। आपके विकास के लिए।

लाइसेंस प्रकारों के बीच अन्य अंतरों के लिए मूल्य निर्धारण पृष्ठ देखें।

", "ChangingDevelopers": "क्या मैं भविष्य में अपने संगठन के पंजीकृत डेवलपर्स को बदल सकता हूं?", "ChangingDevelopersExplanation": "अपने लाइसेंस में नए डेवलपर्स को जोड़ने के अलावा, आप मौजूदा डेवलपर्स को भी बदल सकते हैं (आप एक डेवलपर को हटा सकते हैं और एक ही सीट पर एक नया जोड़ सकते हैं) बिना किसी अतिरिक्त लागत के।", "WhatHappensWhenLicenseEnds": "मेरे लाइसेंस की अवधि समाप्त होने पर क्या होता है?", - "WhatHappensWhenLicenseEndsExplanation1": "ABP कमर्शियल लाइसेंस का प्रकार स्थायी लाइसेंस है। आपके लाइसेंस की समय सीमा समाप्त होने के बाद, आप अपना प्रोजेक्ट विकसित करना जारी रख सकते हैं। और आप अपने लाइसेंस को नवीनीकृत करने के लिए बाध्य नहीं हैं। आपका लाइसेंस एक वर्ष के अपडेट और समर्थन योजना के साथ आता है। नई सुविधाओं को प्राप्त करने के लिए, प्रदर्शन में वृद्धि, बग फिक्स, समर्थन और एबीपी सूट का उपयोग करना जारी रखें, प्रत्येक वर्ष अपनी योजना को नवीनीकृत करना सुनिश्चित करें। जब आपका लाइसेंस समाप्त हो जाता है, तो आप निम्नलिखित लाभ प्राप्त नहीं कर पाएंगे;", + "WhatHappensWhenLicenseEndsExplanation1": "ABP वाणिज्यिक लाइसेंस प्रकार स्थायी लाइसेंस है। आपके लाइसेंस की समय सीमा समाप्त होने के बाद, आप अपने प्रोजेक्ट को विकसित करना जारी रख सकते हैं। और आप अपने लाइसेंस को नवीनीकृत करने के लिए बाध्य नहीं हैं। आपका लाइसेंस बॉक्स से बाहर एक साल के अपडेट और समर्थन योजना के साथ आता है। नई सुविधाओं, प्रदर्शन में सुधार, बग फिक्स, समर्थन प्राप्त करना जारी रखने और एबीपी सूट का उपयोग जारी रखने के लिए, हर साल अपनी योजना को नवीनीकृत करना सुनिश्चित करें। जब आपका लाइसेंस समाप्त हो जाता है, तो आप निम्नलिखित में से अधिक लाभ प्राप्त नहीं कर पाएंगे;", "WhatHappensWhenLicenseEndsExplanation2": "आप एबीपी कमर्शियल का उपयोग करके नए समाधान नहीं बना सकते हैं, लेकिन आप अपने मौजूदा अनुप्रयोगों को हमेशा के लिए विकसित कर सकते हैं।", - "WhatHappensWhenLicenseEndsExplanation3": "आप अपने मुख्य संस्करण के भीतर मॉड्यूल और विषयों के लिए अद्यतन प्राप्त करने में सक्षम होंगे। उदाहरण के लिए; यदि आप किसी मॉड्यूल के v3.2.0 का उपयोग कर रहे हैं, तो आप उस मॉड्यूल के v3.x.x (v3.3.0, v3.5.2 ... आदि) के लिए अपडेट प्राप्त कर सकते हैं। लेकिन आपको अगले प्रमुख संस्करण के लिए अपडेट नहीं मिल सकता है (जैसे v4.x, v5.x)", + "WhatHappensWhenLicenseEndsExplanation3": "आप अपने मुख्य संस्करण के भीतर मॉड्यूल और विषयों के लिए अद्यतन प्राप्त करने में सक्षम होंगे। उदाहरण के लिए; यदि आप किसी मॉड्यूल के v3.2.0 का उपयोग कर रहे हैं, तो आप उस मॉड्यूल के v3.x.x (v3.3.0, v3.5.2... आदि) के लिए अपडेट प्राप्त कर सकते हैं। लेकिन आपको अगले प्रमुख संस्करण के लिए अपडेट नहीं मिल सकता है (जैसे v4.x, v5.x)", "WhatHappensWhenLicenseEndsExplanation4": "आप अपने लाइसेंस समाप्त होने के बाद एबीपी वाणिज्यिक मंच में जोड़े गए नए मॉड्यूल और थीम स्थापित नहीं कर सकते।", "WhatHappensWhenLicenseEndsExplanation5": "आप एबीपी सूट का उपयोग नहीं कर सकते।", - "WhatHappensWhenLicenseEndsExplanation6": "आपको अब और प्रीमियम सहायता नहीं मिल सकती है।", + "WhatHappensWhenLicenseEndsExplanation6": "अब आपको प्रीमियम सहायता नहीं मिल सकती है।", "WhatHappensWhenLicenseEndsExplanation7": "यदि आप इन लाभों को प्राप्त करना जारी रखना चाहते हैं तो आप अपनी सदस्यता को नवीनीकृत कर सकते हैं। जब आप अपनी सदस्यता को नवीनीकृत करते हैं तो 20% छूट मिलती है।", "WhenShouldIRenewMyLicense": "मुझे अपना लाइसेंस कब नवीनीकृत करना चाहिए?", "WhenShouldIRenewMyLicenseExplanation1": "यदि आप अपना लाइसेंस समाप्त होने के बाद अपने लाइसेंस का नवीनीकरण 1 महीने के भीतर करते हैं, तो कुल लाइसेंस मूल्य पर% 20 छूट लागू होगी।", @@ -203,24 +203,24 @@ "TrialPlan": "क्या आपके पास एक परीक्षण योजना है?", "TrialPlanExplanation": "अभी के लिए, एबीपी कमर्शियल में ट्रायल प्लान नहीं है। टीम लाइसेंस के लिए हम 30 दिन की मनी बैक गारंटी प्रदान करते हैं। आप पहले 30 दिनों में धनवापसी का अनुरोध कर सकते हैं। बिजनेस और एंटरप्राइज लाइसेंस के लिए, हम 30 दिनों में 60% रिफंड प्रदान करते हैं। ऐसा इसलिए है क्योंकि व्यवसाय और एंटरप्राइज़ लाइसेंस में सभी मॉड्यूल और थीम का पूर्ण स्रोत कोड शामिल है।", "DoYouAcceptBankWireTransfer": "क्या आप बैंक वायर ट्रांसफर स्वीकार करते हैं?", - "DoYouAcceptBankWireTransferExplanation": "हां, हम बैंक वायर ट्रांसफर स्वीकार करते हैं।
बैंक वायर ट्रांसफर के माध्यम से लाइसेंस राशि भेजने के बाद, हमें अपनी रसीद और अनुरोधित लाइसेंस प्रकार ई-मेल के माध्यम से भेजें।
हमारे अंतरराष्ट्रीय बैंक खाते की जानकारी:", + "DoYouAcceptBankWireTransferExplanation": "हां, हम बैंक वायर ट्रांसफर स्वीकार करते हैं।
बैंक वायर ट्रांसफर के माध्यम से लाइसेंस राशि भेजने के बाद, हमें अपनी रसीद और अनुरोधित लाइसेंस प्रकार ई-मेल के माध्यम से भेजें।
हमारे अंतरराष्ट्रीय बैंक खाते की जानकारी:", "HowToUpgrade": "नया संस्करण उपलब्ध होने पर मौजूदा एप्लिकेशन को कैसे अपग्रेड किया जाए?", "HowToUpgradeExplanation1": "जब आप ABP कमर्शियल का उपयोग करके एक नया एप्लिकेशन बनाते हैं, तो सभी मॉड्यूल और थीम को NuGet और NPM पैकेज के रूप में उपयोग किया जाता है। इसलिए, नया संस्करण उपलब्ध होने पर आप आसानी से संकुल को अपग्रेड कर सकते हैं।", - "HowToUpgradeExplanation2": "मानक NuGet / NPM अपग्रेड के अलावा, ABP CLI एक अपडेट कमांड प्रदान करता है जो आपके समाधान में सभी एबीपी से संबंधित पैकेजों को स्वचालित रूप से ढूंढता है और उन्हें अपग्रेड करता है।", + "HowToUpgradeExplanation2": "मानक NuGet/NPM अपग्रेड के अलावा, ABP CLI एक अपडेट कमांड प्रदान करता है जो आपके समाधान में सभी ABP संबंधित पैकेजों को स्वचालित रूप से ढूंढता और अपग्रेड करता है।", "DatabaseSupport": "कौन से डेटाबेस सिस्टम समर्थित हैं?", - "DatabaseSupportExplanation": "ABP फ्रेमवर्क स्वयं ही डेटाबेस अज्ञेयवादी है और अपनी प्रकृति के अनुसार किसी भी डेटाबेस प्रदाता के साथ काम कर सकता है। वर्तमान में कार्यान्वित प्रदाताओं की सूची के लिए डेटा एक्सेस दस्तावेज़ देखें।", + "DatabaseSupportExplanation": "एबीपी फ्रेमवर्क अपने आप में डेटाबेस अज्ञेयवादी है और अपनी प्रकृति से किसी भी डेटाबेस प्रदाता के साथ काम कर सकता है। वर्तमान में कार्यान्वित प्रदाताओं की सूची के लिए डेटा एक्सेस दस्तावेज़ देखें।", "UISupport": "कौन से UI फ्रेमवर्क समर्थित हैं?", "Supported": "समर्थित", - "UISupportExplanation": "ABP फ्रेमवर्क स्वयं UI फ्रेमवर्क अज्ञेयवादी है और किसी भी UI फ्रेमवर्क के साथ काम कर सकता है। हालाँकि, सभी UI फ्रेमवर्क के लिए स्टार्टअप टेम्प्लेट, मॉड्यूल UI और थीम लागू नहीं किए गए थे। UI विकल्पों की अप-टू-डेट सूची के लिए आरंभ किया गया दस्तावेज़ देखें।", + "UISupportExplanation": "एबीपी फ्रेमवर्क अपने आप में यूआई फ्रेमवर्क अज्ञेयवादी है और किसी भी यूआई फ्रेमवर्क के साथ काम कर सकता है। हालांकि, स्टार्टअप टेम्प्लेट, मॉड्यूल UI और थीम सभी UI फ्रेमवर्क के लिए लागू नहीं किए गए थे। UI विकल्पों की अप-टू-डेट सूची के लिए आरंभ करने वाला दस्तावेज़ देखें।", "MicroserviceSupport": "क्या यह सूक्ष्म सेवा वास्तुकला का समर्थन करता है?", - "MicroserviceSupportExplanation1": "एबीपी ढांचे का एक प्रमुख लक्ष्य सूक्ष्म सेवा समाधान बनाने के लिए एक सुविधाजनक बुनियादी ढांचा प्रदान करना है। यह समझने के लिए कि यह माइक्रो-सर्विस सिस्टम बनाने में कैसे मदद करता है, यह समझने के लिए माइक्रो-सर्विस आर्किटेक्चर दस्तावेज़ देखें।", - "MicroserviceSupportExplanation2": "सभी एबीपी कमर्शियल मॉड्यूल्स को मॉड्यूल डेवलपमेंट बेस्ट प्रैक्टिस दस्तावेज़ का पालन करके माइक्रो-सर्विस परिनियोजन परिदृश्य (अपने एपीआई और डेटाबेस के साथ) का समर्थन करने के लिए डिज़ाइन किया गया है।", - "MicroserviceSupportExplanation3": "हम आपको अपना स्वयं का समाधान बनाने में मदद करने के लिए एक माइक्रो-सेवा वास्तुकला कार्यान्वयन को प्रदर्शित करने वाला एक नमूना माइक्रो-सर्विस डेमो समाधान प्रदान करते हैं।", + "MicroserviceSupportExplanation1": "एबीपी ढांचे के प्रमुख लक्ष्यों में से एक सूक्ष्म सेवा समाधान बनाने के लिए एक सुविधाजनक बुनियादी ढांचा प्रदान करना है। यह समझने के लिए कि यह माइक्रो-सर्विस सिस्टम बनाने में कैसे मदद करता है, माइक्रो-सर्विस आर्किटेक्चर दस्तावेज़ देखें।", + "MicroserviceSupportExplanation2": "सभी एबीपी वाणिज्यिक मॉड्यूल मॉड्यूल विकास सर्वोत्तम प्रथाओं दस्तावेज़ का पालन करके माइक्रो-सेवा परिनियोजन परिदृश्यों (अपने स्वयं के एपीआई और डेटाबेस के साथ) का समर्थन करने के लिए डिज़ाइन किए गए हैं।", + "MicroserviceSupportExplanation3": "हम एक नमूना माइक्रो-सर्विस डेमो सॉल्यूशन प्रदान करते हैं जो आपको अपना समाधान बनाने में मदद करने के लिए एक माइक्रो-सर्विस आर्किटेक्चर कार्यान्वयन दर्शाता है।", "MicroserviceSupportExplanation4": "तो, संक्षिप्त उत्तर \" हां है, यह सूक्ष्म सेवा वास्तुकला का समर्थन करता है \"।", "MicroserviceSupportExplanation5": "हालाँकि, एक माइक्रो-सर्विस सिस्टम एक समाधान है और हर समाधान में अलग-अलग आवश्यकताएं, नेटवर्क टोपोलॉजी, संचार परिदृश्य, प्रमाणीकरण संभावनाएं, डेटाबेस पृथक्करण / साझा करने के निर्णय, रनटाइम कॉन्फ़िगरेशन, 3 पार्टी सिस्टम एकीकरण और कई और अधिक होंगे।", "MicroserviceSupportExplanation6": "ABP फ्रेमवर्क और ABP कमर्शियल आपको अपने स्वयं के समाधान का निर्माण करने में मदद करने के लिए सूक्ष्म सेवा परिदृश्य, सूक्ष्म सेवा संगत मॉड्यूल, नमूने और प्रलेखन के लिए बुनियादी ढाँचा प्रदान करता है। लेकिन अपने सपनों के समाधान को सीधे आपके लिए पहले से डाउनलोड करने की उम्मीद न करें। आपको इसे समझने और अपनी आवश्यकताओं के आधार पर कुछ हिस्सों को एक साथ लाने की आवश्यकता होगी।", "WhereCanIDownloadSourceCode": "मैं स्रोत-कोड कहां से डाउनलोड कर सकता हूं?", - "WhereCanIDownloadSourceCodeExplanation": "आप एबीपी सूट या एबीपी सीएलआई के माध्यम से सभी एबीपी मॉड्यूल, कोणीय पैकेज और थीम के स्रोत कोड डाउनलोड कर सकते हैं। स्रोत-कोड डाउनलोड कैसे करें? देखें", + "WhereCanIDownloadSourceCodeExplanation": "आप एबीपी सूट या एबीपी सीएलआई के माध्यम से सभी एबीपी मॉड्यूल, एंगुलर पैकेज और थीम का सोर्स कोड डाउनलोड कर सकते हैं। देखें स्रोत-कोड कैसे डाउनलोड करें?", "ComputerLimitation": "एबीपी विकसित करते समय एक डेवलपर कितने कंप्यूटरों पर लॉगिन कर सकता है?", "ComputerLimitationExplanation": "हम विशेष रूप से प्रति व्यक्ति / लाइसेंस प्राप्त डेवलपर के लिए {0} कंप्यूटर की अनुमति देते हैं। जब भी किसी तीसरे मशीन पर एबीपी कमर्शियल उत्पादों को विकसित करने के लिए डेवलपर की आवश्यकता होती है, तो स्थिति को समझाते हुए लाइसेंस@abp.io को एक ई-मेल भेजा जाना चाहिए और फिर हम अपने सिस्टम में उचित आवंटन करेंगे।", "RefundPolicy": "क्या आपके पास धनवापसी नीति है?", @@ -230,17 +230,17 @@ "HowCanIRefundVatExplanation2": "अपने 2 चेकआउट खाते में लॉग इन करें", "HowCanIRefundVatExplanation3": "उपयुक्त आदेश खोजें और \"रिफंड बेल्ड वैट\" दबाएं (अपनी वैट आईडी दर्ज करें)", "HowCanIGetMyInvoice": "मैं अपना चालान कैसे प्राप्त कर सकता हूं?", - "HowCanIGetMyInvoiceExplanation": "लाइसेंस खरीदने के लिए 2 भुगतान गेटवे हैं: PayU और 2 चेकआउट। यदि आप 2Checkout गेटवे के माध्यम से अपना लाइसेंस खरीदते हैं, तो यह आपके ईमेल पते पर पीडीएफ चालान भेजता है, देखें 2 चेकआउट चालान। यदि आप पेयू गेटवे या बैंक वायर ट्रांसफर के माध्यम से खरीदते हैं, तो हम आपका चालान तैयार करेंगे और भेजेंगे। आप अपने चालान का अनुरोध संगठन प्रबंधन पृष्ठ से कर सकते हैं", + "HowCanIGetMyInvoiceExplanation": "लाइसेंस खरीदने के लिए 2 भुगतान गेटवे हैं: PayU और 2 चेकआउट। यदि आप 2Checkout गेटवे के माध्यम से अपना लाइसेंस खरीदते हैं, तो यह आपके ईमेल पते पर पीडीएफ चालान भेजता है, देखें 2 चेकआउट चालान। यदि आप पेयू गेटवे या बैंक वायर ट्रांसफर के माध्यम से खरीदते हैं, तो हम आपका चालान तैयार करेंगे और भेजेंगे। आप अपने चालान का अनुरोध संगठन प्रबंधन पृष्ठ से कर सकते हैं", "Forum": "मंच", "SupportExplanation": "ABP कमर्शियल लाइसेंस एक टीम द्वारा प्रीमियम फ़ोरम सपोर्ट प्रदान करता है जिसमें ABP फ्रेमवर्क विशेषज्ञ होते हैं।", "PrivateTicket": "निजी टिकट", "PrivateTicketExplanation": "एंटरप्राइज लाइसेंस में ई-मेल और टिकट प्रणाली के साथ एक निजी सहायता भी शामिल है।", "AbpSuiteExplanation1": "ABP सुइट आपको कुछ ही मिनटों में वेब पेज बनाने की अनुमति देता है। यह .NET कोर ग्लोबल टूल है जिसे कमांड लाइन से इंस्टॉल किया जा सकता है।", - "AbpSuiteExplanation2": "यह एक नया ABP समाधान बना सकता है, डेटाबेस से फ्रंट-एंड तक CRUD पेज बना सकता है। तकनीकी ओवरव्यू के लिए दस्तावेज़ देखें", + "AbpSuiteExplanation2": "यह एक नया ABP समाधान बना सकता है, डेटाबेस से फ्रंट-एंड तक CRUD पेज बना सकता है। तकनीकी ओवरव्यू के लिए दस्तावेज़ देखें", "FastEasy": "तेज और आसान", "AbpSuiteExplanation3": "ABP सुइट आपको आसानी से CRUD पेज बनाने की अनुमति देता है। आपको बस अपनी इकाई और उसके गुणों को परिभाषित करने की आवश्यकता है, बाकी को आप के लिए एबीपी सूट करने दें! एबीपी सूट कुछ ही सेकंड में आपके सीआरयूडी पृष्ठ के लिए सभी आवश्यक कोड उत्पन्न करता है। यह Angular, MVC और Blazor यूजर इंटरफेस को सपोर्ट करता है।", "RichOptions": "अमीर विकल्प", - "AbpSuiteExplanation4": "ABP सुइट रेजर पेज और Angular । यह MongoDB जैसे कई डेटाबेस का समर्थन करता है और सभी डेटाबेस EntityFramework Core <द्वारा समर्थित है / strong> (MS SQL सर्वर, Oracle, MySql, PostgreSQL और )।", + "AbpSuiteExplanation4": "ABP Suite supports multiple UI options like Razor Pages and Angular.It also supports multiple databases like MongoDB and all databases supported by EntityFramework Core (MS SQL Server, Oracle, MySql, PostgreSQL and more).", "AbpSuiteExplanation5": "अच्छी बात यह है कि, आपको उन विकल्पों के बारे में चिंता करने की ज़रूरत नहीं है। एबीपी सूट आपके प्रोजेक्ट के प्रकार को समझता है और आपकी परियोजना के लिए कोड बनाता है और उत्पन्न कोड को आपके प्रोजेक्ट में सही जगह पर रखता है।", "SourceCode": "सोर्स कोड", "AbpSuiteExplanation6": "ABP स्वीट आपके लिए सोर्स कोड जेनरेट करता है! यह वेब पेज जनरेट करने के लिए मैजिक फाइल्स जेनरेट नहीं करता है। एबीपी सूट इकाई, रिपोजिटरी, एप्लीकेशन सर्विस, कोड फर्स्ट माइग्रेशन, जावास्क्रिप्ट / टाइपस्क्रिप्ट और सीएसएचटीएमएल / एचटीएमएल और आवश्यक इंटरफेस के लिए भी स्रोत कोड उत्पन्न करता है। ABP Suite सॉफ्टवेयर डेवलपमेंट के बेस्ट प्रैक्टिसेस के अनुसार कोड भी तैयार करता है, इसलिए आपको जनरेट किए गए कोड की गुणवत्ता के बारे में चिंता करने की आवश्यकता नहीं है।", diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/it.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/it.json index 766702a92b..722c10275f 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/it.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/it.json @@ -42,7 +42,7 @@ "NotOrganizationMember": "Non sei membro di nessuna organizzazione.", "UnsubscribeLicenseExpirationEmailSuccessTitle": "Iscrizione annullata con successo", "UnsubscribeLicenseExpirationEmailSuccessMessage": "Non riceverai più email di promemoria della data di scadenza della licenza.", - "IndexPageHeroSection": " Una completa piattaforma di sviluppo web integrata framework ", + "IndexPageHeroSection": " Una completa piattaforma di sviluppo web integrata framework ", "AbpCommercialShortDescription": "ABP Commercial fornisce moduli applicativi predefiniti, strumenti per lo sviluppo rapido di applicazioni, temi dell'interfaccia utente professionale, supporto premium e altro ancora.", "LiveDemo": "Dimostrazione dal vivo", "GetLicence": "Ottieni una licenza", @@ -51,56 +51,56 @@ "Startup": "Avviare", "Templates": "Modelli", "Developer": "Sviluppatore", - "Tools": "Utensili", + "Tools": "Strumenti", "Premium": "Premium", - "PremiumSupport": "Supporto premium", - "PremiumForumSupport": "Supporto forum premium", - "UI": "UI", + "PremiumSupport": "Supporto Premium", + "PremiumForumSupport": "Supporto Forum Premium", + "UI": "Interfaccia Utente", "Themes": "Temi", "JoinOurNewsletter": "Iscriviti alla nostra newsletter", "Send": "Spedire", "Learn": "Imparare", "AdditionalServices": "Servizi aggiuntivi", - "WhatIsABPFramework": "QUAL È IL QUADRO ABP?", + "WhatIsABPFramework": "COSA E` IL FRAMEWORK ABP?", "OpenSourceBaseFramework": "Framework di base open source", - "ABPFrameworkExplanation": "

ABP Commercial si basa su ABP Framework, un framework per applicazioni web open source e guidato dalla comunità per ASP.NET Core.

ABP Framework fornisce un'eccellente infrastruttura per la scrittura gestibile ed estensibile e codice testabile con best practice.

Costruito e integrato in strumenti popolari che già conosci. Bassa curva di apprendimento, facile adattamento, sviluppo confortevole.

", + "ABPFrameworkExplanation": "

ABP Commercial si basa su ABP Framework, un framework per applicazioni web open source e guidato dalla comunità per ASP.NET Core.

ABP Framework fornisce un'eccellente infrastruttura per la scrittura gestibile ed estensibile e codice testabile con best practice.

Costruito e integrato in strumenti popolari che già conosci. Bassa curva di apprendimento, facile adattamento, sviluppo confortevole.

", "Modular": "Modulare", "MicroserviceCompatible": "Compatibile con microservizi", - "DomainDrivenDesignInfrastructure": "Infrastruttura di progettazione basata sul dominio", + "DomainDrivenDesignInfrastructure": "Infrastruttura Domain Driven Design", "MultiTenancy": "Multi-Tenancy", "DistributedMessaging": "Messaggistica distribuita", "DynamicProxying": "Proxying dinamico", - "BackgroundJobs": "Lavori in background", - "AuditLogging": "Registrazione di audit", + "BackgroundJobs": "Operazioni in background", + "AuditLogging": "Registri di controllo", "BLOBStoring": "Archiviazione BLOB", "BundlingMinification": "Raggruppamento e minimizzazione", "AdvancedLocalization": "Localizzazione avanzata", "ManyMore": "Molti altri", "ExploreTheABPFramework": "Esplora il framework ABP", - "WhyUseTheABPCommercial": "Perché utilizzare lo spot pubblicitario ABP?", - "WhyUseTheABPCommercialExplanation": "

La creazione di applicazioni Web di livello aziendale può essere complessa e richiede tempo.

ABP Commercial offre l'infrastruttura di base perfetta necessaria per tutti i moderni ASP.NET Core di livello aziendale soluzioni basate. Dalla progettazione all'implementazione, l'intero ciclo di sviluppo è potenziato dalle funzionalità e dai moduli integrati di ABP.

", + "WhyUseTheABPCommercial": "Perché utilizzare ABP Commercial?", + "WhyUseTheABPCommercialExplanation": "

La creazione di applicazioni Web di livello aziendale può essere complessa e richiede tempo.

ABP Commercial offre l'infrastruttura di base perfetta necessaria per tutti i moderni ASP.NET Core di livello aziendale soluzioni basate. Dalla progettazione all'implementazione, l'intero ciclo di sviluppo è potenziato dalle funzionalità e dai moduli integrati di ABP.

", "StartupTemplatesShortDescription": "I modelli di avvio ti consentono di avviare rapidamente il tuo progetto in pochi secondi.", "UIFrameworksOptions": "Opzioni dei framework dell'interfaccia utente;", "DatabaseProviderOptions": "Opzioni del provider di database;", "PreBuiltApplicationModules": "Moduli applicativi predefiniti", "PreBuiltApplicationModulesShortDescription": "I requisiti delle applicazioni più comuni sono già stati sviluppati per te come moduli riutilizzabili.", - "Account": "account", + "Account": "Account", "Blogging": "Blogging", "Identity": "Identità", "IdentityServer": "Identity Server", "Saas": "Saas", "LanguageManagement": "Gestione della lingua", "TextTemplateManagement": "Gestione dei modelli di testo", - "See All Modules": "SeeAllModules", + "See All Modules": "Vedi tutti i moduli", "ABPSuite": "ABP Suite", "AbpSuiteShortDescription": "ABP Suite è uno strumento complementare all'ABP Commercial.", "AbpSuiteExplanation": "Ti consente di creare pagine web in pochi minuti. È uno strumento .NET Core Global che può essere installato dalla riga di comando. Può creare una nuova soluzione ABP, generare pagine CRUD dal database al front-end.", "Details": "Dettagli", "LeptonTheme": "Tema Lepton", "ProfessionalModernUIThemes": "Temi dell'interfaccia utente moderni e professionali", - "LeptonThemeExplanation": "Lepton fornisce una gamma di temi di amministrazione Bootstrap che fungono da solida base per qualsiasi progetto che richieda dashboard di amministrazione.", + "LeptonThemeExplanation": "Lepton fornisce una gamma di temi di amministrazione Bootstrap che fungono da solida base per qualsiasi progetto che richieda cruscotto di amministrazione.", "DefaultTheme": "Tema predefinito", - "MaterialTheme": "Tema materiale", + "MaterialTheme": "Tema Material", "Default2Theme": "Tema predefinito 2", "DarkTheme": "Tema scuro", "DarkBlueTheme": "Tema blu scuro", @@ -111,13 +111,13 @@ "AdditionalServicesExplanation": "Hai bisogno di servizi aggiuntivi o personalizzati? Noi e i nostri partner possiamo fornire; ", "CustomProjectDevelopment": "Sviluppo di progetti personalizzati", "CustomProjectDevelopmentExplanation": "Sviluppatori dedicati per i tuoi progetti personalizzati.", - "PortingExistingProjects": "Porting di progetti esistenti", + "PortingExistingProjects": "Miigrazione di progetti esistenti", "PortingExistingProjectsExplanation": "Migrazione dei tuoi progetti legacy alla piattaforma ABP.", "LiveSupport": "Supporto dal vivo", "LiveSupportExplanation": "Opzione di supporto remoto dal vivo quando ne hai bisogno.", "Training": "Formazione", "TrainingExplanation": "Formazione dedicata per i tuoi sviluppatori.", - "OnBoarding": "Onboarding", + "OnBoarding": "Affiancamento", "OnBoardingExplanation": "Aiuta a configurare i tuoi ambienti di sviluppo, CI e CD.", "PrioritizedTechnicalSupport": "Assistenza tecnica prioritaria", "PremiumSupportExplanation": "Oltre al grande supporto della comunità del framework ABP, il nostro team di supporto risponde alle domande tecniche e ai problemi degli utenti commerciali con alta priorità.", @@ -132,7 +132,7 @@ "SendMessage": "Invia messaggio", "Success": "Successo", "WeWillReplyYou": "Abbiamo ricevuto il tuo messaggio e ti contatteremo a breve.", - "GoHome": "Andare a casa", + "GoHome": "Torna all'inizio", "CreateLiveDemo": "Crea demo live", "RegisterToTheNewsletter": "Registrati alla newsletter per ricevere informazioni su ABP.IO, comprese le nuove versioni ecc.", "EnterYourEmailOrLogin": "Inserisci il tuo indirizzo e-mail per creare la tua demo o Accedi utilizzando il tuo account esistente.", @@ -143,28 +143,28 @@ "ApplicationTemplateArchitecture": "Questo modello di applicazione supporta anche un'architettura a più livelli in cui il livello dell'interfaccia utente, il livello API e il servizio di autenticazione sono fisicamente separati.", "SeeTheGuideOrGoToTheLiveDemo": "Consulta la guida per sviluppatori per informazioni tecniche su questo modello o vai alla demo live.", "DeveloperGuide": "Guida per sviluppatori", - "ModuleTemplate": "Modello di modulo", + "ModuleTemplate": "Modello per modulo", "ModuleTemplateExplanation1": "Vuoi creare un modulo e riutilizzarlo in diverse applicazioni? Questo modello di avvio prepara tutto per iniziare a creare un modulo dell'applicazione riutilizzabile o un microservizio .", - "ModuleTemplateExplanation2": "

Puoi supportare uno o più framework UI, uno o più provider di database per un singolo modulo. Il modello di avvio è configurato per eseguire e testare il modulo in un'applicazione minima oltre all'unità e all'infrastruttura di test di integrazione.

Consulta la guida per sviluppatori per informazioni tecniche su questo modello.

", + "ModuleTemplateExplanation2": "

Puoi supportare uno o più framework UI, uno o più provider di database per un singolo modulo. Il modello di avvio è configurato per eseguire e testare il modulo in un'applicazione minima oltre all'unità e all'infrastruttura di test di integrazione.

Consulta la guida per sviluppatori per informazioni tecniche su questo modello.

", "WithAllStyleOptions": "con tutte le opzioni di stile", "Demo": "Demo", "SeeAllModules": "Vedi tutti i moduli", "ABPCLIExplanation": "ABP CLI (Command Line Interface) è uno strumento a riga di comando per eseguire alcune operazioni comuni per soluzioni basate su ABP.", "ABPSuiteEasilyCURD": "ABP Suite è uno strumento che ti permette di creare facilmente pagine CRUD", - "WeAreHereToHelp": "Siamo qui per Aiuto ", + "WeAreHereToHelp": "Siamo qui per Aiutarti", "BrowseOrAskQuestion": "Puoi sfogliare i nostri argomenti della guida o cercare nelle domande frequenti oppure puoi farci una domanda utilizzando il modulo di contatto .", "SearchQuestionPlaceholder": "Cerca nelle domande frequenti", - "WhatIsTheABPCommercial": "Cos'è l'ABP Commercial?", + "WhatIsTheABPCommercial": "Cos'è ABP Commercial?", "WhatAreDifferencesThanAbpFramework": "Quali sono le differenze tra ABP Framework open source e ABP Commercial?", "ABPCommercialExplanation": "ABP Commercial è un insieme di moduli, strumenti, temi e servizi premium basati sul framework ABP open source. ABP Commercial è sviluppato e supportato dallo stesso team dietro il framework ABP.", - "WhatAreDifferencesThanABPFrameworkExplanation": "

ABP framework è un framework di sviluppo di applicazioni compatibile con micro-servizi per ASP.NET Core modulare. Fornisce un'architettura completa e una solida infrastruttura per farti concentrare sul tuo codice aziendale piuttosto che ripetere te stesso per ogni nuovo progetto. Si basa sulle migliori pratiche di sviluppo software e su strumenti popolari che già conosci.

Il framework ABP è completamente gratuito, open source e guidato dalla comunità. Fornisce inoltre un tema gratuito e alcuni moduli predefiniti (ad es. Gestione delle identità e gestione dei tenant).

", + "WhatAreDifferencesThanABPFrameworkExplanation": "

ABP framework è un framework di sviluppo di applicazioni compatibile con micro-servizi per ASP.NET Core modulare. Fornisce un'architettura completa e una solida infrastruttura per farti concentrare sul tuo codice aziendale piuttosto che ripetere te stesso per ogni nuovo progetto. Si basa sulle migliori pratiche di sviluppo software e su strumenti popolari che già conosci.

Il framework ABP è completamente gratuito, open source e guidato dalla comunità. Fornisce inoltre un tema gratuito e alcuni moduli predefiniti (ad es. Gestione delle identità e gestione dei tenant).

", "VisitTheFrameworkVSCommercialDocument": "Visita il seguente link, per ulteriori informazioni {1} ", "ABPCommercialFollowingBenefits": "ABP Commercial aggiunge i seguenti vantaggi al framework ABP;", "Professional": "Professionale", "UIThemes": "Temi dell'interfaccia utente", - "EnterpriseModules": " moduli applicativi preconfigurati, ricchi di funzionalità (ad es. Gestione di Identity Server, gestione SaaS, gestione della lingua)", - "ToolingToSupport": "Strumenti per supportare la produttività dello sviluppo (ad es. ABP Suite )", - "PremiumSupportLink": "Supporto Premium premium", + "EnterpriseModules": "Pronto per le imprese, moduli applicativi preconfigurati, ricchi di funzionalità (ad es. Gestione di Identity Server, gestione SaaS, gestione della lingua)", + "ToolingToSupport": "Strumenti per supportare la produttività dello sviluppo (ad es. ABP Suite )", + "PremiumSupportLink": "Supporto Premium", "WhatDoIDownloadABPCommercial": "Cosa devo scaricare quando acquisto ABP Commercial?", "CreateUnlimitedSolutions": "Dopo aver acquistato una licenza commerciale ABP, sarai in grado di creare soluzioni illimitate come descritto nel documento Guida introduttiva .", "ABPCommercialSolutionExplanation": "Quando crei una nuova applicazione, ottieni una soluzione Visual Studio (un modello di avvio) in base alle tue preferenze. La soluzione scaricata ha moduli e temi commerciali già installati e configurati per te. Puoi rimuovere un modulo preinstallato o aggiungere un altro modulo, se lo desideri. Tutti i moduli e temi vengono usati come pacchetti NuGet / NPM per impostazione predefinita.", @@ -177,7 +177,7 @@ "ChangingLicenseType": "Posso cambiare il mio tipo di licenza in futuro?", "ChangingLicenseTypeExplanation": "Puoi sempre aggiungere nuovi sviluppatori con lo stesso tipo di licenza. Vedi anche \"Quanti sviluppatori possono lavorare su ABP Commercial?\". Puoi anche passare a una licenza superiore pagando la differenza di prezzo calcolata. Quando esegui l'aggiornamento a un piano di licenza superiore, ottieni i vantaggi del nuovo piano, ma l'aggiornamento della licenza non modifica la data di scadenza della licenza.", "LicenseExtendUpgradeDiff": "Qual è la differenza tra estensione della licenza e aggiornamento?", - "LicenseExtendUpgradeDiffExplanation": " Estensione: estendendo / rinnovando la tua licenza, continuerai a ricevere supporto premium e a ricevere aggiornamenti importanti per moduli e temi. Inoltre, potrai continuare a creare nuovi progetti. E sarai ancora in grado di utilizzare ABP Suite che accelera il tuo sviluppo.
Upgrade: aggiornando la tua licenza, promuoverai un piano di licenza superiore che ti consentirà di ottenere ulteriori vantaggi . Consulta la tabella di confronto delle licenze per verificare le differenze tra i piani di licenza. D'altra parte, quando esegui l'upgrade, la data di scadenza della licenza non cambierà! Per estendere la data di fine della licenza, è necessario estendere la licenza.", + "LicenseExtendUpgradeDiffExplanation": " Estensione: estendendo/rinnovando la tua licenza, continuerai a ricevere supporto premium e a ricevere aggiornamenti importanti per moduli e temi. Inoltre, potrai continuare a creare nuovi progetti. E sarai ancora in grado di utilizzare ABP Suite che accelera il tuo sviluppo.
Upgrade: aggiornando la tua licenza, promuoverai un piano di licenza superiore che ti consentirà di ottenere ulteriori vantaggi . Consulta la tabella di confronto delle licenze per verificare le differenze tra i piani di licenza. D'altra parte, quando esegui l'upgrade, la data di scadenza della licenza non cambierà! Per estendere la data di fine della licenza, è necessario estendere la licenza.", "LicenseRenewalCost": "Qual è il costo del rinnovo della licenza dopo 1 anno?", "LicenseRenewalCostExplanation": "La tariffa di rinnovo (estensione) di tutte le licenze perpetue ABP Commercial è {0} del prezzo di listino della licenza. Il prezzo di rinnovo della licenza Team standard è $ {1}, la licenza Business standard è $ {2} e la licenza Enterprise standard è $ {3}. Se sei già un cliente, accedi al tuo account per esaminare i prezzi di rinnovo disponibili.", "HowDoIRenewMyLicense": "Come rinnovo la mia licenza?", @@ -185,14 +185,14 @@ "IsSourceCodeIncluded": "La mia licenza include il codice sorgente dei moduli e dei temi commerciali?", "IsSourceCodeIncludedExplanation1": "Dipende dal tipo di licenza che hai acquistato:", "IsSourceCodeIncludedExplanation2": " Team : la soluzione utilizza i moduli e i temi come pacchetti NuGet e NPM. Non include il loro codice sorgente. In questo modo, puoi aggiornare facilmente questi moduli e temi ogni volta che è disponibile una nuova versione. Tuttavia, non è possibile ottenere il codice sorgente dei moduli e dei temi.", - "IsSourceCodeIncludedExplanation3": " Business / Enterprise : oltre alla licenza Team, puoi scaricare il codice sorgente di qualsiasi modulo o tema di cui hai bisogno. È anche possibile rimuovere i riferimenti al pacchetto NuGet / NPM per un particolare modulo e aggiungere il relativo codice sorgente direttamente alla soluzione per modificarlo completamente.", - "IsSourceCodeIncludedExplanation4": "

Includere il codice sorgente di un modulo nella tua soluzione ti dà la massima libertà di personalizzare quel modulo. Tuttavia, non sarà possibile aggiornare automaticamente il modulo quando viene rilasciata una nuova versione.

Nessuna delle licenze include il codice sorgente di ABP Suite, che è uno strumento esterno che genera codice per te e assiste al tuo sviluppo.

Vedi la pagina dei prezzi per altre differenze tra i tipi di licenza.

", + "IsSourceCodeIncludedExplanation3": " Business/Enterprise : in aggiunta alla licenza Team, puoi scaricare il codice sorgente di qualsiasi modulo o tema di cui hai bisogno. È anche possibile rimuovere i riferimenti al pacchetto NuGet/NPM per un particolare modulo e aggiungere il relativo codice sorgente direttamente alla soluzione per modificarlo completamente.", + "IsSourceCodeIncludedExplanation4": "

Includere il codice sorgente di un modulo nella tua soluzione ti dà la massima libertà di personalizzare quel modulo. Tuttavia, non sarà possibile aggiornare automaticamente il modulo quando viene rilasciata una nuova versione.

Nessuna delle licenze include il codice sorgente di ABP Suite, che è uno strumento esterno che genera codice per te e assiste al tuo sviluppo.

Vedi la pagina dei prezzi per altre differenze tra i tipi di licenza.

", "ChangingDevelopers": "Posso cambiare gli sviluppatori registrati della mia organizzazione in futuro?", - "ChangingDevelopersExplanation": "Oltre ad aggiungere nuovi sviluppatori alla tua licenza, puoi anche modificare gli sviluppatori esistenti (puoi rimuovere uno sviluppatore e aggiungerne uno nuovo alla stessa postazione) senza alcun costo aggiuntivo.", + "ChangingDevelopersExplanation": "Oltre ad aggiungere nuovi sviluppatori alla tua licenza, puoi anche Modifica gli sviluppatori esistenti (puoi rimuovere uno sviluppatore e aggiungerne uno nuovo alla stessa postazione) senza alcun costo aggiuntivo.", "WhatHappensWhenLicenseEnds": "Cosa succede al termine del periodo di licenza?", - "WhatHappensWhenLicenseEndsExplanation1": "Il tipo di licenza commerciale ABP è licenza perpetua . Dopo la scadenza della licenza, puoi continuare a sviluppare il tuo progetto. E non sei obbligato a rinnovare la tua licenza. La tua licenza viene fornita con un anno di aggiornamenti e piano di supporto pronto all'uso. Per continuare a ricevere nuove funzionalità, miglioramenti delle prestazioni, correzioni di bug, supporto e continuare a utilizzare ABP Suite, assicurati di rinnovare il tuo piano ogni anno. Quando la tua licenza scade, non sarai in grado di ottenere più dei seguenti vantaggi;", + "WhatHappensWhenLicenseEndsExplanation1": "Il tipo di licenza ABP Commercial è licenza perpetua . Dopo la scadenza della licenza, puoi continuare a sviluppare il tuo progetto. E non sei obbligato a rinnovare la tua licenza. La tua licenza viene fornita con un anno di aggiornamenti e piano di supporto pronto all'uso. Per continuare a ricevere nuove funzionalità, miglioramenti delle prestazioni, correzioni di bug, supporto e continuare a utilizzare ABP Suite, assicurati di rinnovare il tuo piano ogni anno. Quando la tua licenza scade, non sarai in grado di ottenere più dei seguenti vantaggi;", "WhatHappensWhenLicenseEndsExplanation2": "Non è possibile creare nuove soluzioni utilizzando ABP Commercial, ma è possibile continuare a sviluppare le applicazioni esistenti per sempre.", - "WhatHappensWhenLicenseEndsExplanation3": "Sarai in grado di ottenere aggiornamenti per i moduli e i temi nella tua versione MAJOR. Per esempio; se stai usando la v3.2.0 di un modulo, puoi comunque ottenere gli aggiornamenti per la v3.x.x (v3.3.0, v3.5.2 ... ecc.) di quel modulo. Ma non puoi ottenere aggiornamenti per la prossima versione principale (come v4.x, v5.x)", + "WhatHappensWhenLicenseEndsExplanation3": "Sarai in grado di ottenere aggiornamenti per i moduli e i temi nella tua versione MAJOR. Per esempio; se stai usando la v3.2.0 di un modulo, puoi comunque ottenere gli aggiornamenti per la v3.x.x (v3.3.0, v3.5.2... ecc.) di quel modulo. Ma non puoi ottenere aggiornamenti per la prossima versione principale (come v4.x, v5.x)", "WhatHappensWhenLicenseEndsExplanation4": "Non è possibile installare nuovi moduli e temi aggiunti alla piattaforma ABP Commercial al termine della licenza.", "WhatHappensWhenLicenseEndsExplanation5": "Non è possibile utilizzare ABP Suite.", "WhatHappensWhenLicenseEndsExplanation6": "Non puoi più ottenere l ' assistenza premium .", @@ -203,7 +203,7 @@ "TrialPlan": "Hai un piano di prova?", "TrialPlanExplanation": "Per ora, ABP Commercial non ha un piano di prova. Per le licenze Team forniamo una garanzia di rimborso di 30 giorni. Puoi richiedere un rimborso solo nei primi 30 giorni. Per le licenze Business ed Enterprise, forniamo un rimborso del 60% in 30 giorni. Questo perché le licenze Business ed Enterprise includono il codice sorgente completo di tutti i moduli e i temi.", "DoYouAcceptBankWireTransfer": "Accettate bonifici bancari?", - "DoYouAcceptBankWireTransferExplanation": "Sì, accettiamo bonifico bancario.
Dopo aver inviato l'importo della licenza tramite bonifico bancario, inviaci la ricevuta e il tipo di licenza richiesto tramite e-mail.
Informazioni sul nostro conto bancario internazionale:", + "DoYouAcceptBankWireTransferExplanation": "Sì, accettiamo bonifico bancario.
Dopo aver inviato l'importo della licenza tramite bonifico bancario, inviaci la ricevuta e il tipo di licenza richiesto tramite e-mail.
Informazioni sul nostro conto bancario internazionale:", "HowToUpgrade": "Come aggiornare le applicazioni esistenti quando è disponibile una nuova versione?", "HowToUpgradeExplanation1": "Quando crei una nuova applicazione utilizzando ABP Commercial, tutti i moduli e il tema vengono utilizzati come pacchetti NuGet e NPM. Quindi, puoi facilmente aggiornare i pacchetti quando è disponibile una nuova versione.", "HowToUpgradeExplanation2": "Oltre agli aggiornamenti NuGet / NPM standard, ABP CLI fornisce un comando di aggiornamento che trova e aggiorna automaticamente tutti i pacchetti relativi a ABP nella soluzione.", @@ -230,24 +230,24 @@ "HowCanIRefundVatExplanation2": "Accedi al tuo account 2Checkout ", "HowCanIRefundVatExplanation3": "Trova l'ordine appropriato e premi \"Rimborsa IVA tardiva\" (inserisci il tuo numero di partita IVA)", "HowCanIGetMyInvoice": "Come posso ricevere la mia fattura?", - "HowCanIGetMyInvoiceExplanation": "Esistono 2 gateway di pagamento per l'acquisto di una licenza: PayU e 2Checkout. Se acquisti la tua licenza tramite il gateway 2Checkout, invia la fattura in PDF al tuo indirizzo e-mail, vedi Fatturazione 2Checkout. Se acquisti tramite il gateway PayU o tramite bonifico bancario, prepareremo e invieremo la tua fattura. Puoi richiedere la fattura dalla pagina di gestione dell'organizzazione. ", + "HowCanIGetMyInvoiceExplanation": "Esistono 2 gateway di pagamento per l'acquisto di una licenza: PayU e 2Checkout. Se acquisti la tua licenza tramite il gateway 2Checkout, invia la fattura in PDF al tuo indirizzo e-mail, vedi Fatturazione 2Checkout. Se acquisti tramite il gateway PayU o tramite bonifico bancario, prepareremo e invieremo la tua fattura. Puoi richiedere la fattura dalla pagina di gestione dell'organizzazione.", "Forum": "Forum", "SupportExplanation": "Le licenze commerciali di ABP forniscono un supporto forum premium da parte di un team composto dagli esperti di ABP Framework.", - "PrivateTicket": "Biglietto privato", + "PrivateTicket": "Ticket privato", "PrivateTicketExplanation": "Enterprise License include anche un supporto privato con e-mail e sistema di ticket.", "AbpSuiteExplanation1": "ABP Suite ti consente di creare pagine web in pochi minuti. È uno strumento .NET Core Global che può essere installato dalla riga di comando.", "AbpSuiteExplanation2": "Può creare una nuova soluzione ABP, generare pagine CRUD dal database al front-end. Per una panoramica tecnica, vedere il documento ", "FastEasy": "Veloce e facile", "AbpSuiteExplanation3": "ABP Suite ti consente di creare facilmente pagine CRUD. Devi solo definire la tua entità e le sue proprietà, lascia che il resto sia ABP Suite per te! ABP Suite genera tutto il codice necessario per la tua pagina CRUD in pochi secondi. Supporta le interfacce utente Angular, MVC e Blazor.", "RichOptions": "Opzioni ricche", - "AbpSuiteExplanation4": "ABP Suite supporta più opzioni dell'interfaccia utente come Razor Pages e Angular . Supporta anche più database come MongoDB e tutti i database supportati da EntityFramework Core < / strong> (MS SQL Server, Oracle, MySql, PostgreSQL e altro ).", + "AbpSuiteExplanation4": "ABP Suite supporta più opzioni dell'interfaccia utente come Razor Pages e Angular . Supporta anche più database come MongoDB e tutti i database supportati da EntityFramework Core < / strong> (MS SQL Server, Oracle, MySql, PostgreSQL e altro ).", "AbpSuiteExplanation5": "La cosa buona è che non devi preoccuparti di quelle opzioni. ABP Suite comprende il tipo di progetto e genera il codice per il progetto e inserisce il codice generato nella posizione corretta nel progetto.", "SourceCode": "Codice sorgente", "AbpSuiteExplanation6": "ABP Suite genera il codice sorgente per te! Non genera file magici per generare la pagina web. ABP Suite genera il codice sorgente per Entity, Repository, Application Service, Code First Migration, JavaScript / TypeScript e CSHTML / HTML e anche le interfacce necessarie. ABP Suite genera anche il codice in base alle Best Practices di sviluppo software, quindi non devi preoccuparti della qualità del codice generato.", - "AbpSuiteExplanation7": "Dal momento che hai il codice sorgente dei blocchi costitutivi della pagina CRUD generata nei livelli di applicazione corretti, puoi facilmente modificare il codice sorgente e inserire la tua logica personalizzata / aziendale nel codice generato.", + "AbpSuiteExplanation7": "Dal momento che hai il codice sorgente dei blocchi costitutivi della pagina CRUD generata nei livelli di applicazione corretti, puoi facilmente Modifica il codice sorgente e inserire la tua logica personalizzata/aziendale nel codice generato.", "CrossPlatform": "Piattaforma multipla", - "AbpSuiteExplanation8": "ABP Suite è costruito con .NET Core ed è multipiattaforma. Funziona come un'applicazione web sul tuo computer locale. Puoi eseguirlo su Windows , Mac e Linux ", - "OtherFeatures": "Altre caratteristiche", + "AbpSuiteExplanation8": "ABP Suite è costruito con .NET Core ed è multipiattaforma. E` un'applicazione web sul tuo computer locale. Puoi eseguirlo su Windows, Mac e Linux", + "OtherFeatures": "Altre Funzionalità", "OtherFeatures1": "Aggiorna facilmente i pacchetti NuGet e NPM sulla tua soluzione.", "OtherFeatures2": "Rigenera le pagine già generate da zero.", "OtherFeatures3": "Crea nuove soluzioni", @@ -255,7 +255,7 @@ "HotToRunSolution": "Come eseguire la tua soluzione?", "HotToRunSolutionExplanation": "Vedere il documento introduttivo per apprendere come configurare ed eseguire la soluzione.", "GettingStarted": "Iniziare", - "WebAppDevTutorial": "Tutorial per sviluppatori di app Web", + "WebAppDevTutorial": "Tutorial per sviluppatori di App Web", "WebAppDevTutorialExplanation": "Vedere il documento tutorial sullo sviluppo di applicazioni Web per un esempio di sviluppo passo passo.", "Document": "Documento", "UsingABPSuiteToCURD": "Utilizzo di ABP Suite per la generazione di pagine CRUD e gli strumenti", @@ -264,9 +264,9 @@ "Documentation": "Documentazione", "SeeModulesDocument": "Vedere il documento dei moduli per un elenco di tutti i moduli commerciali (pro) e dei relativi documenti.", "Pricing": "Prezzi", - "PricingExplanation": "Scegli le caratteristiche e le funzionalità di cui la tua azienda ha bisogno oggi. Esegui facilmente l'upgrade man mano che la tua attività cresce.", - "Team": "Squadra", - "Business": "Attività commerciale", + "PricingExplanation": "Scegli le Funzionalità e le funzionalità di cui la tua azienda ha bisogno oggi. Esegui facilmente l'upgrade man mano che la tua attività cresce.", + "Team": "Team", + "Business": "Business", "Enterprise": "Enterprise", "Custom": "Personalizzato", "IncludedDeveloperLicenses": "Licenze per sviluppatori incluse", @@ -285,12 +285,79 @@ "UnlimitedServerDeployment": "Distribuzione illimitata del server", "YearUpgrade": "1 anno di aggiornamento", "YearPremiumForumSupport": "1 anno di supporto forum premium", - "ForumSupportIncidentCountYear": "Conteggio degli incidenti di supporto del forum / anno", + "ForumSupportIncidentCountYear": "Conteggio delle richieste di supporto del forum/anno", "PrivateTicketEmailSupport": "Ticket privato e supporto via e-mail", "BuyNow": "Acquista ora", "PayViaAmexCard": "Come posso pagare tramite la mia carta AMEX?", "PayViaAmexCardDescription": "Il gateway di pagamento predefinito \"Iyzico\" potrebbe rifiutare alcune carte di credito AMEX a causa delle misure di sicurezza. In questo caso, puoi pagare tramite il gateway di pagamento alternativo \"2Checkout\".", "ThankYou": "Grazie", - "InvalidReCaptchaErrorMessage": "Si è verificato un errore durante la verifica di reCAPTCHA. Per favore riprova." + "InvalidReCaptchaErrorMessage": "Si è verificato un errore durante la verifica di reCAPTCHA. Per favore riprova.", + "CompanyName": "Nome Azienda", + "YourCompanyName": "Il nome della tua azienda", + "FirstName": "Nome", + "LastName": "Cognome", + "Optional": "Facoltativo", + "YourFirstName": "Il tuo nome", + "YourLastName": "Il tuo cognome", + "SpecialOffer": "Offerta Speciale", + "SpecialOfferMessage": "Fai presto! I prezzi sono validi per un periodo limitato.", + "DiscountRequest": "Richiesta Sconto", + "DiscountRequestDescribeCustomerQuestion": "Quale delle seguenti ti descrive?", + "DiscountRequestStudentEmailMessage": "L'indirizzo email deve contenere 'edu'.", + "DiscountRequestDeveloperCount": "Quanti sviluppatori ci sono?", + "DiscountRequestDeveloperCountExceedMessage": "Non concediamo sconti per le aziende con più di {0} sviluppatori.", + "DiscountRequestOrganizationName": "Nome Azienda/Organizzazione/Istituto Scolastico", + "Website": "Sito web", + "GithubUsername": "Nome utente GitHub", + "PhoneNumber": "Numero Telefono", + "Country": "Nazione", + "DescribeABPCommercialUsage": "Descrivi il progetto che stai pianificando di sviluppare su ABP Commercial", + "DiscountRequestCertifyInformationMessage": "Dichiaro che tutte le informazioni fornite corrispondono al vero e sono corrette", + "DiscountRequestReceived": "Abbiamo ricevuto la tua richiesta di sconto.", + "DiscountRequestStatusMessage": "Risponderemo dopo aver verificato le informazioni fornite.", + "MVCOrRazorPages": "MVC (Razor Pages)", + "Angular": "Angular", + "Blazor": "Blazor", + "New": "Nuovo", + "MongoDB": "MongoDB", + "EBookDDD": "E-Book Domain Driven Design", + "PracticalGuideForImplementingDDD": "Questo libro è una guida pratica per implementare il Domain Driven Design con ABP Framework.", + "IntroducingDDD": "Introduzione al Domain Driven Design", + "DDDLayersAndCleanArchitecture": "DDD Strati e Architettura Pulita", + "LayeringOfADotnetSolution": "Stratificazione di una Soluzione .NET", + "ImplementingDDDBuildingBlocks": "Implementazione delle Fondamenta del DDD", + "DomainVsApplicationLogic": "Logica di Dominio contro Logica Applicazione", + "SamplesAndDiscussions": "Esempi e Discussioni", + "Free": "Gratuito", + "Download": "Download", + "DDDEBook": "DDD E-book", + "ImplementingDDD": "Implementazione Domain Driven Design", + "DDDBookExplanation": "Una guida pratica per implementare il Domain Driven Design con ABP Framework.", + "Overview": "Panoramica", + "DDDBookPracticalGuide": "Questa a una guida pratica per implementare Domain Driven Design (DDD). Mentre i dettagli per l'implementazione sono basati sull'infrastruttura di ABP Framework, i concetti base, principi e modelli possono essere applicati a qualsiasi soluzione, anche se non è una soluzione .NET.", + "TableOfContents": "Sommario", + "IntroductionToImplementingDDD": "Introduzione all'Implementazione Domain Driven Design", + "WhatIsDDD": "Cos'è il Domain Driven Design?", + "Implementation": "Implementazione", + "TheBigPicture": "Il Quadro Generale", + "TheBuildingBlock": "Le Fondamenta", + "ExampleUseCase": "Esempio d'uso", + "DomainAndApplicationLogic": "Logica di Dominio e Logica Applicazione", + "Author": "Autore", + "PublishedOn": "Pubblicato il", + "Page": "Pagina", + "FreeEBook": "E-Book Gratuito", + "EBookSignInForDownload": "Accedi per scaricare l'e-book", + "SignIn": "Accedi", + "Or": "O", + "TellUsAboutYourself": "Dicci qualcosa di te", + "Surname": "Cognome", + "DoYouAgreePrivacyPolicy": "Acconsento ai Termini e Condizioni ed alla Politica Privacy.", + "VolosoftMarketingInformationMessage": "Vorrei informazioni, suggerimenti e offerte sulle Soluzioni per Aziende e Organizzazioni e altri prodotti e servizi Volosoft", + "VolosoftSharingInformationMessage": "Vorrei che Volosoft condividesse le mie informazioni con partner selezionati in modo da poter ricevere informazioni rilevanti sui loro prodotti e servizi", + "WeWillSendYouADownloadLink": "Un link per scaricare il libro e-book è stato spedito a {0}.
Controlla nella posta in arrivo / indesiderata / spam!", + "InvalidFormInputs": "Per favore, digita le informazioni valide specificate nel modulo", + "DDDBookEmailBody": "Grazie.
Per scaricare il tuo libro, clicca qui.", + "FreeDDDEBook": "E-Book Gratuito DDD" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/sk.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/sk.json new file mode 100644 index 0000000000..7e2143a9af --- /dev/null +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/sk.json @@ -0,0 +1,296 @@ +{ + "culture": "sk", + "texts": { + "OrganizationManagement": "Správa organizácii", + "OrganizationList": "Zoznam organizácií", + "Volo.AbpIo.Commercial:010003": "Nie ste vlastníkom tejto organizácie!", + "OrganizationNotFoundMessage": "Nenašla sa žiadna organizácia!", + "DeveloperCount": "Pridelené / celkový počet vývojárov", + "QuestionCount": "Zostávajúce / všetky otázky", + "Unlimited": "Neobmedzené", + "Owners": "Vlastníci", + "AddMember": "Pridať člena", + "AddOwner": "Pridať vlastníka", + "AddDeveloper": "Pridať vývojára", + "UserName": "Používateľské meno", + "Name": "Názov", + "EmailAddress": "Emailová adresa", + "Developers": "Vývojári", + "LicenseType": "Typ licencie", + "Manage": "Spravovať", + "StartDate": "Dátum začiatku", + "EndDate": "Dátum ukončenia", + "Modules": "Moduly", + "LicenseExtendMessage": "Koniec platnosti vašej licencie je predĺžený do {0}.", + "LicenseUpgradeMessage": "Vaša licencia je aktualizovaná na {0}", + "LicenseAddDeveloperMessage": "{0} vývojári boli pridaní do vašej licencie", + "Volo.AbpIo.Commercial:010004": "Nie je možné nájsť zadaného používateľa! Používateľ musí už byť zaregistrovaný.", + "MyOrganizations": "Moje organizácie", + "ApiKey": "API kľúč", + "UserNameNotFound": "Neexistuje žiadny používateľ s používateľským menom {0}", + "SuccessfullyAddedToNewsletter": "Ďakujeme, že ste sa prihlásili na odber nášho newslettera!", + "MyProfile": "Môj profil", + "EmailNotValid": "Zadajte platnú emailovú adresu.", + "JoinOurMarketingNewsletter": "Prihláste sa na odber reklamného newslettra", + "WouldLikeToReceiveMarketingMaterials": "Chcem dostávať reklamné materiály, ponuky produktov a špeciálne ponuky.", + "StartUsingYourLicenseNow": "Teraz začnite používať svoju licenciu!", + "WelcomePage": "Uvítacia stránka", + "UnsubscriptionExpireEmail": "Odhlásenie z odberu emailov informujúcich o dátume vypršania platnosti licencie", + "UnsubscribeLicenseExpireEmailReminderMessage": "Tento email obsahuje iba pripomenutie dátumu vypršania platnosti vašej licencie.", + "UnsubscribeFromLicenseExpireEmails": "Ak nechcete dostávať emaily o dátume vypršania platnosti vašej licencie, môžete sa kedykoľvek odhlásiť.", + "Unsubscribe": "Odhlásiť", + "NotOrganizationMember": "Nie ste členom žiadnej organizácie.", + "UnsubscribeLicenseExpirationEmailSuccessTitle": "Úspešne odhlásené", + "UnsubscribeLicenseExpirationEmailSuccessMessage": "Už nebudete dostávať emaily s upozornením na dátum vypršania platnosti licencie.", + "IndexPageHeroSection": "Kompletná web vývojová platformapostavená na frameworku", + "AbpCommercialShortDescription": "ABP Commercial poskytuje predpripravené aplikačné moduly, nástroje na rýchly vývoj aplikácií, profesionálne témy používateľského rozhrania, prémiovú podporu a ďalšie.", + "LiveDemo": "Live ukážka", + "GetLicence": "Získajte licenciu", + "Application": "Aplikácia", + "StartupTemplates": "Štartovacie šablóny", + "Startup": "Štart", + "Templates": "Šablóny", + "Developer": "Vývojár", + "Tools": "Nástroje", + "Premium": "Premium", + "PremiumSupport": "Prémiová podpora", + "PremiumForumSupport": "Prémiová podpora na fóre", + "UI": "POUŽÍVATEĽSKÉ ROZHRANIE", + "Themes": "Témy", + "JoinOurNewsletter": "Prihláste sa na odber newslettra", + "Send": "Odoslať", + "Learn": "Učiť sa", + "AdditionalServices": "Ďalšie služby", + "WhatIsABPFramework": "ČO JE FRAMEWORK ABP?", + "OpenSourceBaseFramework": "Základný open source framework ", + "ABPFrameworkExplanation": "

ABP Commercial je založené na ABP frameworku, open source a komunitou riadenom frameworku webových aplikácií pre ASP.NET Core.

ABP Framework poskytuje vynikajúcu infraštruktúru na písanie udržiavateľného, rozšíriteľného a testovateľného kódu s osvedčenými postupmi.

Je integrovaný a postavený na populárnych nástrojoch, ktoré už poznáte. Nízka krivka učenia, jednoduché prispôsobenie, pohodlný vývoj.

", + "Modular": "Modulárny", + "MicroserviceCompatible": "Kompatibilita s mikroslužbami", + "DomainDrivenDesignInfrastructure": "Domain Driven Design infraštruktúra", + "MultiTenancy": "Multitenacia", + "DistributedMessaging": "Distribuované zasielanie správ", + "DynamicProxying": "Dynamický Proxying", + "BackgroundJobs": "Automatické úlohy na pozadí", + "AuditLogging": "Audit logovanie", + "BLOBStoring": "Ukladanie BLOBov", + "BundlingMinification": "Bundling a minifikácia", + "AdvancedLocalization": "Pokročilá lokalizácia", + "ManyMore": "Mnohé ďalšie", + "ExploreTheABPFramework": "Preskúmajte ABP framework", + "WhyUseTheABPCommercial": "Prečo používať ABP Commercial?", + "WhyUseTheABPCommercialExplanation": "

Vybudovanie webových aplikácií podnikovej úrovne môže byť zložité a časovo náročné.

ABP Commercial ponúka dokonalú základnú infraštruktúru potrebnú pre všetky moderné riešenia ASP.NET Core podnikovej úrovne. Celý vývojový cyklus, od návrhu až po nasadenie, umožňujú integrované funkcie a moduly ABP.

", + "StartupTemplatesShortDescription": "Štartovacie šablóny vám umožnia spustiť projekt za niekoľko sekúnd.", + "UIFrameworksOptions": "Možnosti používateľského rozhrania frameworku;", + "DatabaseProviderOptions": "Možnosti poskytovateľov databáz;", + "PreBuiltApplicationModules": "Vopred pripravené aplikačné moduly", + "PreBuiltApplicationModulesShortDescription": "Väčšina bežných aplikačných požiadaviek je už pre vás vyvinutá v podobe znovupoužiteľných modulov.", + "Account": "Účet", + "Blogging": "Blogovanie", + "Identity": "Identity", + "IdentityServer": "Identity Server", + "Saas": "Saas", + "LanguageManagement": "Jazykový manažment", + "TextTemplateManagement": "Správa textových šablón", + "See All Modules": "Pozri všetky moduly", + "ABPSuite": "ABP Suite", + "AbpSuiteShortDescription": "ABP Suite je doplnkový nástroj k uABP Commercial.", + "AbpSuiteExplanation": "Umožňuje vytvárať webové stránky v priebehu niekoľkých minút. Je to globálny nástroj .NET Core, ktorý možno nainštalovať z príkazového riadka. Dokáže vytvoriť nové riešenie ABP, generovať CRUD stránky od databázy až po frontend.", + "Details": "Podrobnosti", + "LeptonTheme": "Lepton téma", + "ProfessionalModernUIThemes": "Profesionálne a moderné témy používateľského rozhrania", + "LeptonThemeExplanation": "Lepton poskytuje škálu Bootstrap admin tém, ktoré slúžia ako základ pre akýkoľvek projekt, kde je vyžadovaný administrátorský panel.", + "DefaultTheme": "Predvolená téma", + "MaterialTheme": "Materiál téma", + "Default2Theme": "Predvolená téma 2", + "DarkTheme": "Tmavá téma", + "DarkBlueTheme": "Tmavomodrá téma", + "LightTheme": "Téma Light", + "ProudToWorkWith": "Hrdo spolupracujeme s", + "OurConsumers": "Na ABP Commercial sa spoliehajú stovky podnikov a vývojárov vo viac ako 50 krajinách sveta.", + "JoinOurConsumers": "Pridajte sa k nim a jednoducho vytvorte úžasné produkty.", + "AdditionalServicesExplanation": "Potrebujete ďalšie alebo vlastné služby? My a naši partneri vám ich môžeme poskytnúť;", + "CustomProjectDevelopment": "Vývoj projektu na mieru", + "CustomProjectDevelopmentExplanation": "Špecializovaní vývojári pre vaše vlastné projekty.", + "PortingExistingProjects": "Prenos existujúcich projektov", + "PortingExistingProjectsExplanation": "Migrácia starších projektov do ABP frameworku.", + "LiveSupport": "Live podpora", + "LiveSupportExplanation": "V prípade potreby možnosť Live podpory na diaľku.", + "Training": "Školenie", + "TrainingExplanation": "Školenia určené pre vašich vývojárov.", + "OnBoarding": "Rozbehnutie", + "OnBoardingExplanation": "Pomoc pri nastavovaní vývojárskeho prostredia, CI prostredia a CD prostredia.", + "PrioritizedTechnicalSupport": "Prioritná technická podpora", + "PremiumSupportExplanation": "Popri skvelej podpore od komunity ABP frameworku aj náš tím podpory zodpovedá otázky a rieši problémy komerčných zákazníkov s vysokou prioritou.", + "SeeTheSupportOptions": "Pozrite si možnosti podpory", + "Contact": "Kontakt", + "TellUsWhatYouNeed": "Povedzte nám, čo potrebujete.", + "YourMessage": "Vaša správa", + "YourFullName": "Vaše celé meno", + "EmailField": "Emailová adresa", + "YourEmailAddress": "Vaša emailová adresa", + "HowMayWeHelpYou": "Ako vám môžeme pomôcť?", + "SendMessage": "Odoslať správu", + "Success": "Úspech", + "WeWillReplyYou": "Dostali sme vašu správu a čoskoro vás budeme kontaktovať.", + "GoHome": "Prejsť Domov", + "CreateLiveDemo": "Vytvoriť live ukážku", + "RegisterToTheNewsletter": "Zaregistrujte sa na odber noviniek a dostávajte informácie o ABP.IO, vrátane nových verzií atď.", + "EnterYourEmailOrLogin": "Zadajte svoju emailovú adresu a vytvorte si ukážku alebo sa prihláste pomocou svojho existujúceho konta.", + "ApplicationTemplate": "Šablóna žiadosti", + "ApplicationTemplateExplanation": "Štartovacia šablóna aplikácie sa používa na vytvorenie novej webovej aplikácie.", + "EfCoreProvider": "Entity Framework (podporuje SQL Server, MySQL, PostgreSQL, Oracle a ďalšie)", + "AlreadyIncludedInTemplateModules": "V tejto šablóne sú už zahrnuté a nakonfigurované nasledujúce moduly:", + "ApplicationTemplateArchitecture": "Táto šablóna aplikácie podporuje aj viacúrovňovú architektúru, v ktorej sú fyzicky oddelené vrstva používateľského rozhrania, vrstva API a služba overovania.", + "SeeTheGuideOrGoToTheLiveDemo": "Technické informácie o tejto šablóne nájdete v príručke pre vývojárov alebo v live ukážke.", + "DeveloperGuide": "Príručka pre vývojárov", + "ModuleTemplate": "Šablóna modulu", + "ModuleTemplateExplanation1": "Chcete vytvoriť modul a opätovne ho použiť v iných aplikáciách? Táto štartovacia šablóna pripraví všetko na vytvorenie opakovane použiteľného aplikačného modulu alebo mikroslužby.", + "ModuleTemplateExplanation2": "

Môžete podporovať jeden alebo viacero používateľských rozhraní frameworku, jedného alebo viacerých poskytovateľov databáz pre jeden modul. Štartovacia šablóna je nakonfigurovaná tak, aby okrem infraštruktúry unit a integračných testov spustila a testovala váš modul v minimálnej aplikácii.

Technické informácie o tejto šablóne nájdete v príručke pre vývojárov.

", + "WithAllStyleOptions": "so všetkými možnosťami štýlov", + "Demo": "Ukážka", + "SeeAllModules": "Pozrite si všetky moduly", + "ABPCLIExplanation": "ABP CLI (Command Line Interface) je nástroj v príkazovom riadku na vykonávanie niektorých bežných operácií v riešeniach založených na ABP.", + "ABPSuiteEasilyCURD": "ABP Suite je nástroj, ktorý vám umožňuje jednoducho vytvárať CRUD stránky.", + "WeAreHereToHelp": "Sme tu, aby sme pomohli", + "BrowseOrAskQuestion": "Môžete si prezerať témy našej nápovedy alebo vyhľadávať v často kladených otázkach, prípadne nám môžete položiť otázku pomocou kontaktného formulára.", + "SearchQuestionPlaceholder": "Vyhľadávanie v často kladených otázkach", + "WhatIsTheABPCommercial": "Čo je to ABP Commercial?", + "WhatAreDifferencesThanAbpFramework": "Aké sú rozdiely medzi open source ABP Framework a ABP Commercial?", + "ABPCommercialExplanation": "ABP Commercial je súbor prémiových modulov, nástrojov, tém a služieb postavených na open source ABP frameworku. ABP Commercial vyvíja a podporuje ten istý tím, ktorý stojí za ABP frameworkom.", + "WhatAreDifferencesThanABPFrameworkExplanation": "

ABP framework je modulárny, témovo-upraviteľný, s mikroslužbami kompatibilný framework na vývoj aplikácií pre ASP.NET Core. Poskytuje kompletnú architektúru a silnú infraštruktúru, aby ste sa mohli sústrediť na vlastný biznis kód a nemuseli sa opakovať pri každom novom projekte. Je založený na osvedčených postupoch vývoja softvéru a populárnych nástrojoch, ktoré už poznáte.

ABP framework je úplne bezplatný, má otvorený zdrojový kód a je poháňaný komunitou. Poskytuje aj bezplatnú tému a niektoré predpripravené moduly (napr. správu identít a správu tenantov).

", + "VisitTheFrameworkVSCommercialDocument": "Pre viac informácií navštívte nasledujúci odkaz {1} ", + "ABPCommercialFollowingBenefits": "ABP Commercial má oproti ABP systému nasledujúce výhody;", + "Professional": "Profesionálny", + "UIThemes": "Témy používateľského rozhrania", + "EnterpriseModules": "Podnikovo pripravené, funkčne bohaté, predpripravené aplikačné moduly (napr. správa Identity Server, správa SaaS, správa jazykov)", + "ToolingToSupport": "Nástroje na podporu produktivity vášho vývoja (napr. ABP Suite)", + "PremiumSupportLink": "Prémiová podpora", + "WhatDoIDownloadABPCommercial": "Čo si stiahnem, keď si zakúpim ABP Commercial?", + "CreateUnlimitedSolutions": "Po zakúpení licencie ABP Commercial budete môcť vytvárať neobmedzený počet riešení, ako je popísané v dokumente Začíname.", + "ABPCommercialSolutionExplanation": "Pri vytváraní novej aplikácie dostanete riešenie vo Visual Studio (štartovaciu šablónu) na základe vašich preferencií. Stiahnuté riešenie má pre vás už nainštalované a nakonfigurované komerčné moduly a témy. Ak budete chcieť, môžete odstrániť predinštalovaný modul alebo pridať iný modul. Všetky predvolené moduly a témy používajú balíky NuGet/NPM.", + "StartDevelopWithTutorials": "Stiahnuté riešenie je dobre navrhnuté a zdokumentované. Môžete na jeho základe začať vyvíjať vlastný biznis kód podľa návodov", + "TryTheCommercialDemo": "Môžete vyskúšať demo a pozrieť si ukážku aplikácie vytvorenej pomocou štartovacej šablóny ABP Commercial.", + "HowManyProducts": "Koľko rôznych produktov/riešení môžem vytvoriť pomocou ABP Commercial?", + "HowManyProductsExplanation": "Vytvorenie projektu ABP nie je obmedzené. Môžete vytvoriť toľko projektov, koľko chcete, vyvíjať ich a nahrávať na rôzne servery.", + "HowManyDevelopers": "Koľko vývojárov môže pracovať na ABP Commercial?", + "HowManyDevelopersExplanation": "Licencie ABP Commercial sú jednotkové na vývojára. Rôzne typy licencií majú rôzne limity pre vývojárov. Ktorýkoľvek typ licencie však môžete kedykoľvek rozšíriť o ďalších vývojárov. Informácie o typoch licencií, limitoch pre vývojárov a nákladoch na ďalších vývojárov nájdete na stránke ceny.", + "ChangingLicenseType": "Môžem v budúcnosti zmeniť typ licencie?", + "ChangingLicenseTypeExplanation": "Vždy je možné pridať nových vývojárov v rovnakom type licencie. Pozrite si tiež časť \"Koľko vývojárov môže pracovať na ABP Commercial?\". Môžete tiež prejsť na vyššiu licenciu zaplatením vypočítaného cenového rozdielu. Pri prechode na vyšší licenčný plán získate výhody nového plánu, ale prechodom na vyšší licenčný plán sa nezmení dátum platnosti licencie.", + "LicenseExtendUpgradeDiff": "Aký je rozdiel medzi rozšírením a aktualizáciou licencie?", + "LicenseExtendUpgradeDiffExplanation": "Predĺženie: Predĺžením/obnovením licencie budete naďalej dostávať prémiovú podporu a hlavné aktualizácie modulov a tém. Okrem toho budete môcť pokračovať vo vytváraní nových projektov, naďalej používať balík ABP Suite, ktorý urýchľuje váš vývoj.
Podpísanie licencie: Rozšírením licencie postúpite na vyšší licenčný plán, ktorý vám umožní získať ďalšie výhody. Pozrite si tabuľku porovnania licencií a skontrolujte rozdiely medzi licenčnými plánmi.Na druhej strane, pri upgrade sa dátum skončenia platnosti vašej licencie nezmení! Ak chcete predĺžiť dátum skončenia platnosti vašej licencie, musíte si licenciu predĺžiť.", + "LicenseRenewalCost": "Aké sú náklady na obnovenie licencie po 1 roku?", + "LicenseRenewalCostExplanation": "Sadzba za obnovenie (predĺženie) všetkých trvalých licencií ABP Commercial je {0} z cenníkovej ceny licencie. Cena za obnovenie štandardnej tímovej licencie je {1} USD, štandardnej obchodnej licencie je {2} USD a štandardnej podnikovej licencie je {3} USD. Ak už ste zákazníkom, prihláste sa do svojho konta a prezrite si dostupné ceny za obnovenie.", + "HowDoIRenewMyLicense": "Ako si môžem obnoviť licenciu?", + "HowDoIRenewMyLicenseExplanation": "Svoju licenciu môžete obnoviť tak, že prejdete na stránku správa organizície. Ak chcete využiť naše zľavnené ceny za predčasné obnovenie, uistite sa, že ste licenciu obnovili pred uplynutím jej platnosti. Netrápte sa však tým, že neviete, kedy sa vaša možnosť predčasného obnovenia skončí. Pred vypršaním platnosti predplatného dostanete 2 e-maily s pripomienkami. Budeme ich posielať o 30 dní, teda 7 dní pred vypršaním platnosti.", + "IsSourceCodeIncluded": "Zahŕňa moja licencia zdrojový kód komerčných modulov a tém?", + "IsSourceCodeIncludedExplanation1": "Závisí od typu zakúpenej licencie:", + "IsSourceCodeIncludedExplanation2": "Tím: Vaše riešenie používa moduly a témy ako balíky NuGet a NPM. Neobsahuje ich zdrojový kód. Týmto spôsobom môžete tieto moduly a témy ľahko aktualizovať vždy, keď je k dispozícii nová verzia. Nemôžete však získať zdrojový kód modulov a tém.", + "IsSourceCodeIncludedExplanation3": "Business/Enterprise: Okrem tímovej licencie si môžete stiahnuť zdrojový kód akéhokoľvek modulu alebo témy, ktorú potrebujete. Môžete dokonca odstrániť odkazy na balíky NuGet/NPM pre konkrétny modul a pridať jeho zdrojový kód priamo do svojho riešenia a úplne ho zmeniť.", + "IsSourceCodeIncludedExplanation4": "

Začlenenie zdrojového kódu modulu do vášho riešenia vám dáva maximálnu slobodu prispôsobenia tohto modulu. Potom však nebude možné automaticky aktualizovať modul pri vydaní novej verzie.

Žiadna z licencií nezahŕňa zdrojový kód balíka ABP Suite, čo je externý nástroj, ktorý pre vás generuje kód a pomáha pri vývoji.

Dalšie rozdiely medzi jednotlivými typmi licencií nájdete na stránke o cenách.

", + "ChangingDevelopers": "Môžem v budúcnosti zmeniť registrovaných vývojárov svojej organizácie?", + "ChangingDevelopersExplanation": "Okrem pridávania nových vývojárov do licencie môžete tiež meniť existujúcich vývojárov (môžete odstrániť vývojára a pridať nového na to isté miesto) bez akýchkoľvek ďalších nákladov.", + "WhatHappensWhenLicenseEnds": "Čo sa stane po skončení platnosti licencie?", + "WhatHappensWhenLicenseEndsExplanation1": "Typ komerčnej licencie ABP je trvalá licencia. Po uplynutí platnosti licencie môžete pokračovať vo vývoji svojho projektu. A nie ste povinní obnoviť svoju licenciu. Vaša licencia sa dodáva s ročným plánom aktualizácií a podpory už v balení. Ak chcete aj naďalej dostávať nové funkcie, vylepšenia výkonu, opravy chýb, podporu a pokračovať v používaní balíka ABP Suite, nezabudnite si plán každý rok obnoviť. Po skončení platnosti vašej licencie nebudete môcť získať ďalšie z nasledujúcich výhod;", + "WhatHappensWhenLicenseEndsExplanation2": "Pomocou ABP Commercial nemôžete vytvárať nové riešenia, ale navždy môžete pokračovať vo vývoji existujúcich aplikácií.", + "WhatHappensWhenLicenseEndsExplanation3": "Budete môcť získať aktualizácie modulov a tém v rámci vašej hlavnej verzie. Ak napríklad používate modul vo verzii 3.2.0, stále môžete získať aktualizácie pre verziu 3.x.x (v3.3.0, v3.5.2... atď.) tohto modulu. Nemôžete však získať aktualizácie pre ďalšiu hlavnú verziu (napr. v4.x, v5.x).", + "WhatHappensWhenLicenseEndsExplanation4": "Po skončení platnosti vašej licencie nemôžete inštalovať nové moduly a témy pridané do platformy ABP Commercial.", + "WhatHappensWhenLicenseEndsExplanation5": "Balík ABP Suite nemôžete používať.", + "WhatHappensWhenLicenseEndsExplanation6": "Už nemôžete získať prémiovú podporu.", + "WhatHappensWhenLicenseEndsExplanation7": "Ak chcete tieto výhody využívať aj naďalej, môžete si predplatné obnoviť. Pri obnovení predplatného sa poskytuje 20 % zľava.", + "WhenShouldIRenewMyLicense": "Kedy si mám obnoviť licenciu?", + "WhenShouldIRenewMyLicenseExplanation1": "Ak si licenciu obnovíte do 1 mesiaca po vypršaní platnosti licencie, na celkovú cenu licencie bude uplatnená zľava 20%.", + "WhenShouldIRenewMyLicenseExplanation2": "Ak obnovíte licenciu po uplynutí 1 mesiaca od dátumu vypršania platnosti licencie, cena za obnovenie bude rovnaká ako cena za nákup licencie a na obnovenie nebude poskytnutá žiadna zľava.", + "TrialPlan": "Máte skúšobný plán?", + "TrialPlanExplanation": "Spoločnosť ABP Commercial zatiaľ nemá skúšobný plán. Pre tímové licencie poskytujeme 30-dňovú záruku vrátenia peňazí. Stačí požiadať o vrátenie peňazí počas prvých 30 dní. V prípade licencií Business a Enterprise poskytujeme 60% vrátenie peňazí do 30 dní. Je to preto, že licencie Business a Enterprise obsahujú úplný zdrojový kód všetkých modulov a tém.", + "DoYouAcceptBankWireTransfer": "Prijímate bankový prevod?", + "DoYouAcceptBankWireTransferExplanation": "Áno, akceptujeme bankový prevod.
Po zaslaní sumy za licenciu bankovým prevodom nám pošlite potvrdenie a požadovaný typ licencie e-mailom.
Naše informácie o medzinárodnom bankovom účte:", + "HowToUpgrade": "Ako aktualizovať existujúce aplikácie, keď je k dispozícii nová verzia?", + "HowToUpgradeExplanation1": "Pri vytváraní novej aplikácie pomocou ABP Commercial sa všetky moduly a téma použijú ako balíky NuGet a NPM. Balíky teda môžete ľahko aktualizovať, keď je k dispozícii nová verzia.", + "HowToUpgradeExplanation2": "Okrem štandardných aktualizácií NuGet/NPM poskytuje ABP CLI príkaz update, ktorý automaticky nájde a aktualizuje všetky balíky súvisiace s ABP vo vašom riešení.", + "DatabaseSupport": "Ktoré databázové systémy sú podporované?", + "DatabaseSupportExplanation": "Samotný ABP Framework je databázovo agnostický a môže pracovať s akýmkoľvek poskytovateľom databázy. Zoznam aktuálne implementovaných poskytovateľov nájdete v dokumente o prístupe k údajom.", + "UISupport": "Ktoré používateľské rozhrania frameworku sú podporované?", + "Supported": "Podporované", + "UISupportExplanation": "Samotný ABP framework je agnostický a môže pracovať s akýmkoľvek UI frameworkom. Štartovacie šablóny, moduly používateľského rozhrania a témy však neboli implementované pre všetky UI frameworky. Aktuálny zoznam možností používateľského rozhrania nájdete v dokumente Začíname.", + "MicroserviceSupport": "Podporuje architektúru mikroslužieb?", + "MicroserviceSupportExplanation1": "Jedným z hlavných cieľov ABP frameworku je poskytnúť vhodnú infraštruktúru na vytváranie riešení mikroslužieb. Na lepšie pochopenie si pozrite dokument architektúra mikroslužieb.", + "MicroserviceSupportExplanation2": "Všetky komerčné moduly ABP sú navrhnuté tak, aby podporovali scenáre nasadenia mikroslužieb (s vlastnou API a databázou) podľa dokumentu Osvedčené postupy vývoja modulov.", + "MicroserviceSupportExplanation3": "Poskytujeme vzorové micro-service demo riešenie, ktoré demonštruje implementáciu architektúry mikroslužby a pomôže vám vytvoriť vlastné riešenie.", + "MicroserviceSupportExplanation4": "Krátka odpoveď teda znie \"áno, podporuje architektúru mikroslužieb\".", + "MicroserviceSupportExplanation5": "Riešenie je systém mikroslužieb a každé riešenie má iné požiadavky, sieťovú topológiu, komunikačné scenáre, možnosti autentifikácie, databázové rozdelenia/zdieľanie databáz, runtime konfigurácie, integrácie systémov tretích strán a mnohé ďalšie.", + "MicroserviceSupportExplanation6": "ABP Framework a ABP Commercial poskytujú infraštruktúru pre scenáre mikroslužieb, moduly kompatibilné s mikroslužbami, vzorky a dokumentáciu, ktoré vám pomôžu vytvoriť vlastné riešenie. Neočakávajte však, že si priamo stiahnete svoje vysnívané riešenie, ktoré je pre vás vopred zostavené. Budete mu musieť porozumieť a spojiť niektoré časti na základe vašich požiadaviek.", + "WhereCanIDownloadSourceCode": "Kde si môžem stiahnuť zdrojový kód?", + "WhereCanIDownloadSourceCodeExplanation": "Zdrojový kód všetkých modulov ABP, balíkov Angular a tém si môžete stiahnuť prostredníctvom balíka ABP Suite alebo ABP CLI. Pozrite si Ako stiahnuť zdrojový kód?", + "ComputerLimitation": "Koľko počítačov môže vývojár prihlásiť pri vývoji ABP?", + "ComputerLimitationExplanation": "Osobitne povoľujeme {0} počítačov na jednotlivca/licencovaného vývojára. Vždy, keď je potrebné, aby vývojár vyvíjal komerčné ABP produkty na treťom počítači, je potrebné poslať e-mail na adresu license@abp.io s vysvetlením situácie a my potom vykonáme príslušné pridelenie v našom systéme.", + "RefundPolicy": "Máte podmienky vrátenia peňazí?", + "RefundPolicyExplanation": "O vrátenie peňazí môžete požiadať do 30 dní od zakúpenia licencie. Typy licencií Business a Enterprise majú možnosť stiahnutia zdrojového kódu, preto nie je možné vrátiť peniaze za licencie Business a Enterprise (a všetky licencie, ktoré zahŕňajú právo na získanie zdrojového kódu). Okrem toho sa nevracajú peniaze za obnovenie a druhý nákup licencie.", + "HowCanIRefundVat": "Ako môžem vrátiť DPH?", + "HowCanIRefundVatExplanation1": "Ak ste platbu uskutočnili prostredníctvom služby 2Checkout, DPH môžete vrátiť prostredníctvom svojho účtu 2Checkout:", + "HowCanIRefundVatExplanation2": "Prihláste sa do svojho účtu 2Checkout", + "HowCanIRefundVatExplanation3": "Vyhľadajte príslušnú objednávku a stlačte tlačidlo \"Vrátiť oneskorenú DPH\" (zadajte svoje IČ DPH).", + "HowCanIGetMyInvoice": "Ako môžem získať faktúru?", + "HowCanIGetMyInvoiceExplanation": "Na zakúpenie licencie sú k dispozícii 2 platobné brány: PayU a 2Checkout. Ak si zakúpite licenciu prostredníctvom brány 2Checkout, odošle vám faktúru vo formáte PDF na vašu e-mailovú adresu, pozrite si fakturovanie cez 2Checkout. Ak si zakúpite licenciu prostredníctvom brány PayU alebo bankovým prevodom, pripravíme a pošleme vám faktúru. O faktúru môžete požiadať na stránke správy organizácie.", + "Forum": "Fórum", + "SupportExplanation": "Komerčné ABP licencie poskytujú prémiovú podporu fóra zloženú z tímu odborníkov na ABP Framework.", + "PrivateTicket": "Privátny ticket", + "PrivateTicketExplanation": "Licencia Enterprise zahŕňa aj súkromnú podporu e-mailom a ticket systém.", + "AbpSuiteExplanation1": "Sada ABP Suite umožňuje vytvárať webové stránky v priebehu niekoľkých minút. Je to globálny nástroj .NET Core, ktorý možno nainštalovať z príkazového riadka.", + "AbpSuiteExplanation2": "Môže vytvoriť nové riešenie ABP, generovať stránky CRUD z databázy do front-endu. Technický prehľad nájdete v dokumente", + "FastEasy": "Rýchlo & jednoducho", + "AbpSuiteExplanation3": "Sada ABP Suite umožňuje jednoducho vytvárať CRUD stránky. Stačí definovať vašu entitu a jej vlastnosti, ostatné nechajte na ABP Suite! Sada ABP Suite vygeneruje všetok potrebný kód pre vašu CRUD stránku za niekoľko sekúnd. Podporuje používateľské rozhrania Angular, MVC a Blazor.", + "RichOptions": "Bohaté možnosti", + "AbpSuiteExplanation4": "ABP Suite podporuje viacero možností používateľského rozhrania, ako napríklad Razor Pages a Angular. Podporuje tiež viacero databáz, ako napríklad MongoDB a všetky databázy podporované EntityFramework Core (MS SQL Server, Oracle, MySql, PostgreSQL a ďalšie).", + "AbpSuiteExplanation5": "Dobré je, že sa o tieto možnosti nemusíte starať. ABP Suite rozumie typu vášho projektu a vygeneruje kód pre váš projekt a umiestni vygenerovaný kód na správne miesto vo vašom projekte.", + "SourceCode": "Zdrojový kód", + "AbpSuiteExplanation6": "ABP Suite generuje zdrojový kód za vás! Nevygeneruje magické súbory na generovanie webovej stránky. ABP Suite vygeneruje zdrojový kód pre Entity, repozitáre, aplication service, Code First Migration, JavaScript/TypeScript a CSHTML/HTML a tiež potrebné rozhrania. ABP Suite tiež generuje kód podľa Najlepších postupov vývoja softvéru, takže sa nemusíte obávať o kvalitu vygenerovaného kódu.", + "AbpSuiteExplanation7": "Pretože máte zdrojový kód stavebných blokov vygenerovanej stránky CRUD v správnych aplikačných vrstvách, môžete zdrojový kód jednoducho upraviť a do vygenerovaného kódu vložiť vlastnú/obslužnú logiku.", + "CrossPlatform": "Naprieč platformami", + "AbpSuiteExplanation8": "ABP Suite je vytvorená pomocou .NET Core a je multiplatformová. Spustí sa ako webová aplikácia na vašom lokálnom počítači. Môžete ju spustiť na Windowse, Macu a Linuxe.", + "OtherFeatures": "Ďalšie funkcie", + "OtherFeatures1": "Ľahko aktualizuje balíky NuGet a NPM na vašom riešení.", + "OtherFeatures2": "Obnovte už vygenerované stránky od začiatku.", + "OtherFeatures3": "Vytvára nové riešenia", + "ThanksForCreatingProject": "Ďakujeme za vytvorenie projektu!", + "HotToRunSolution": "Ako spustiť vaše riešenie?", + "HotToRunSolutionExplanation": "Pozrite si dokument Začíname a dozviete sa, ako nakonfigurovať a spustiť riešenie.", + "GettingStarted": "Začíname", + "WebAppDevTutorial": "Webová aplikácia Dev Tutorial", + "WebAppDevTutorialExplanation": "Pozrite si dokument s návodom na vývoj webovej aplikácie, kde nájdete ukážku vývoja krok za krokom.", + "Document": "Dokument", + "UsingABPSuiteToCURD": "Použite ABP Suite na generovanie CRUD stránok a nástrojov", + "SeeABPSuiteDocument": "Pozrite si dokument ABP Suite, v ktorom sa dozviete o používaní ABP Suite.", + "AskQuestionsOnSupport": "Otázky môžete klásť na stránke ABP Commercial Support.", + "Documentation": "Dokumentácia", + "SeeModulesDocument": "Zoznam všetkých komerčných (pro) modulov a ich dokumentov nájdete v dokumente Moduly.", + "Pricing": "Cenotvorba", + "PricingExplanation": "Vyberte si funkcie a vlastnosti, ktoré vaša firma potrebuje. V prípade rastu ju môžete ľahko upgradovať.", + "Team": "Tím", + "Business": "Obchod", + "Enterprise": "Podnik", + "Custom": "Vlastné", + "IncludedDeveloperLicenses": "Zahrnuté licencie pre vývojárov", + "CustomLicenceOrAdditionalServices": "Potrebujete vlastnú licenciu alebo ďalšie služby?", + "CustomOrVolumeLicense": "Vlastná alebo objemová licencia", + "LiveTrainingSupport": "Live školenia a podpora", + "AndMore": "a viac", + "AdditionalDeveloperLicense": "Dodatočná licencia pre vývojárov", + "ProjectCount": "Počet projektov", + "AllProModules": "Všetky PRO moduly", + "AllProThemes": "Všetky PRO témy", + "AllProStartupTemplates": "Všetky PRO štartovacie šablóny", + "SourceCodeOfAllModules": "Zdrojový kód všetkých modulov", + "SourceCodeOfAllThemes": "Zdrojový kód všetkých tém", + "PerpetualLicense": "Trvalá licencia", + "UnlimitedServerDeployment": "Neobmedzené nasadenie servera", + "YearUpgrade": "Aktualizácia na 1 rok", + "YearPremiumForumSupport": "1 rok prémiovej podpory fóra", + "ForumSupportIncidentCountYear": "Počet incidentov podpory fóra/rok", + "PrivateTicketEmailSupport": "Súkromné tickety & e-mailová podpora", + "BuyNow": "Kúpiť teraz", + "PayViaAmexCard": "Ako môžem platiť kartou AMEX?", + "PayViaAmexCardDescription": "Predvolená platobná brána \"Iyzico\" môže odmietnuť niektoré kreditné karty AMEX z dôvodu bezpečnostných opatrení. V takom prípade môžete platiť prostredníctvom alternatívnej platobnej brány \"2Checkout\".", + "ThankYou": "Ďakujem", + "InvalidReCaptchaErrorMessage": "Pri overovaní reCAPTCHA došlo k chybe. Skúste to znova prosím." + } +} \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json index 76d6fc7c57..c07678b890 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json @@ -97,6 +97,7 @@ "VolosoftSharingInformationMessage": "Volosoft'un ürünlerimi seçkin ortaklarla paylaşmasını kabul ediyorum, böylece ilgili ortakların ürünleri ve hizmetleri hakkında ilgili bilgileri alabilirim.", "WeWillSendYouADownloadLink": "{0} adresine email gönderilmiştir.", "InvalidFormInputs": "Lütfen formda belirtilen geçerli bilgileri yazınız.", - "DDDBookEmailBody": "Teşekkürler.
Kitabı indirmek için, buraya tıklayınız." + "DDDBookEmailBody": "Teşekkürler.
Kitabı indirmek için, buraya tıklayınız.", + "FreeDDDEBook": "Ücretsiz DDD E-Kitap" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/fi.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/fi.json index cba33bfc30..e2fef9e68d 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/fi.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/fi.json @@ -29,14 +29,14 @@ "ContributionGuide": "Contribution Guide", "BugReport": "Virhe raportti", "SeeAllArticles": "Katso kaikki viestit", - "WelcomeToABPCommunity!": "Tervetuloa ABP-yhteisöön!", + "WelcomeToABPCommunity!": "Tervetuloa ABP Community!", "MyProfile": "Profiilini", "MyOrganizations": "Omat organisaatiot", "EmailNotValid": "Ole hyvä ja syötä toimiva sähköpostiosoite.", "FeatureRequest": "Ominaisuuspyyntö", "CreateArticleTitleInfo": "Viestiluettelossa näytettävän viestin nimi.", "CreateArticleSummaryInfo": "Lyhyt yhteenveto viestistä, joka näytetään postituslistalla.", - "CreateArticleCoverInfo": "Lisää tehokkaan artikkelin luomiseksi kansikuva. Lataa 16: 9-kuvasuhteen kuvat parhaan näkymän saamiseksi. Tiedoston enimmäiskoko: 1 Mt.", + "CreateArticleCoverInfo": "Lisää tehokkaan artikkelin luomiseksi kansikuva. Lataa 16:9 -kuvasuhteen kuvat parhaan näkymän saamiseksi. Tiedoston enimmäiskoko: 1MB.", "ThisExtensionIsNotAllowed": "Tätä laajennusta ei sallita.", "TheFileIsTooLarge": "Tiedosto on liian suuri.", "GoToTheArticle": "Siirry artikkeliin", @@ -55,18 +55,18 @@ "Asked": "kysyi", "AllQuestions": "Kaikki kysymykset", "NextVersion": "Seuraava versio", - "MilestoneErrorMessage": "Nykyisiä virstanpylväitä koskevia tietoja ei saatu Githubilta.", + "MilestoneErrorMessage": "Nykyisiä virstanpylväitä koskevia tietoja ei saatu Github.", "QuestionItemErrorMessage": "Viimeisimmät kysymystiedot Stackoverflow'sta epäonnistui.", "Oops": "Oho!", "CreateArticleSuccessMessage": "Artikkeli on lähetetty onnistuneesti. Se julkaistaan sivuston järjestelmänvalvojan tarkistuksen jälkeen.", - "ChooseCoverImage": "Valitse kansikuva ...", + "ChooseCoverImage": "Valitse kansikuva...", "CoverImage": "Kansikuva", "ShareYourExperiencesWithTheABPFramework": "Jaa kokemuksesi ABP-puitteista!", "Optional": "Valinnainen", "UpdateUserWebSiteInfo": "Esimerkki: https://johndoe.com", "UpdateUserTwitterInfo": "Esimerkki: johndoe", "UpdateUserGithubInfo": "Esimerkki: johndoe", - "UpdateUserLinkedinInfo": "Esimerkki: https: //www.linkedin.com / ...", + "UpdateUserLinkedinInfo": "Esimerkki: https://www.linkedin.com/...", "UpdateUserCompanyInfo": "Esimerkki: Volosoft", "UpdateUserJobTitleInfo": "Esimerkki: Ohjelmistokehittäjä", "UserName": "Käyttäjänimi", @@ -84,7 +84,7 @@ "ProfileImageChange": "Vaihda profiilikuva", "BlogItemErrorMessage": "Viimeisimpiä blogiviestitietoja ei saatu ABP: ltä.", "PlannedReleaseDate": "Suunniteltu julkaisupäivä", - "CommunityArticleRequestErrorMessage": "Uusinta artikkelipyyntöä ei saatu Githubilta.", + "CommunityArticleRequestErrorMessage": "Uusinta artikkelipyyntöä ei saatu Github.", "ArticleRequestFromGithubIssue": "Artikkelipyyntöjä ei ole nyt.", "LatestArticles": "Uusimmat viestit", "ArticleRequests": "Artikkelipyynnöt", @@ -115,30 +115,30 @@ "VideoUrl": "Videon URL-osoite", "GithubArticleUrl": "Github-artikkelien URL-osoite", "ExternalArticleUrl": "Ulkoisen artikkelin URL-osoite", - "CreatePostCoverInfo": "Lisää kansikuva, jotta voit luoda tehokkaan viestin. Lataa 16: 9-kuvasuhteen kuvat parhaan näkymän saamiseksi. Tiedoston enimmäiskoko: 1 Mt.", - "ThankYouForContribution": "Kiitos osallistumisesta ABP-yhteisöön.", + "CreatePostCoverInfo": "Lisää kansikuva, jotta voit luoda tehokkaan viestin. Lataa 16:9 -kuvasuhteen kuvat parhaan näkymän saamiseksi. Tiedoston enimmäiskoko: 1MB.", + "ThankYouForContribution": "Kiitos osallistumisesta ABP Community.", "GithubArticle": "Github-artikkeli", - "GithubArticleSubmitStepOne": " 1. Kirjoita artikkeli mistä tahansa julkisesta GitHub-arkistosta Markdown-muodossa. esimerkki ", - "GithubArticleSubmitStepTwo": " 2. Lähetä artikkelin URL-osoite lomaketta käyttämällä.", - "GithubArticleSubmitStepThree": " 3. Artikkelisi renderöidään tällä verkkosivustolla.", + "GithubArticleSubmitStepOne": "1. Kirjoita artikkeli mistä tahansa julkisesta GitHub-arkistosta Markdown-muodossa. esimerkki ", + "GithubArticleSubmitStepTwo": " 2. Lähetä artikkelin URL-osoite lomaketta käyttämällä.", + "GithubArticleSubmitStepThree": " 3. Artikkelisi renderöidään tällä verkkosivustolla.", "YoutubeVideo": "Youtube-video", - "YoutubeVideoSubmitStepOne": " 1. Julkaise videosi YouTubessa.", - "YoutubeVideoSubmitStepTwo": " 2. Lähetä videon URL-osoite lomaketta käyttäen.", - "YoutubeVideoSubmitStepThree": " 3. Vierailijat voivat katsella videosisältöäsi suoraan tällä verkkosivustolla.", + "YoutubeVideoSubmitStepOne": " 1. Julkaise videosi YouTubessa.", + "YoutubeVideoSubmitStepTwo": " 2. Lähetä videon URL-osoite lomaketta käyttäen.", + "YoutubeVideoSubmitStepThree": " 3. Vierailijat voivat katsella videosisältöäsi suoraan tällä verkkosivustolla.", "ExternalContent": "Ulkoinen sisältö", - "ExternalContentSubmitStepOne": " 1. Luo sisältöä mille tahansa julkiselle alustalle (media, oma blogi tai mihin tahansa haluat).", - "ExternalContentSubmitStepTwo": " 2. Lähetä sisällön URL-osoite lomaketta käyttämällä.", - "ExternalContentSubmitStepThree": " 3. Vierailijat ohjataan alkuperäisen verkkosivuston sisältöön.", + "ExternalContentSubmitStepOne": " 1. Luo sisältöä mille tahansa julkiselle alustalle (media, oma blogi tai mihin tahansa haluat).", + "ExternalContentSubmitStepTwo": " 2. Lähetä sisällön URL-osoite lomaketta käyttämällä.", + "ExternalContentSubmitStepThree": " 3. Vierailijat ohjataan alkuperäisen verkkosivuston sisältöön.", "ChooseYourContentType": "Valitse tapa, jolla haluat lisätä sisältöä.", - "PostContentViaGithub": "Haluan lisätä artikkelini GitHub -merkintäsääntöjen mukaisesti.", - "PostContentViaYoutube": "Haluan jakaa videoni, jotka ovat käytettävissä Youtube täällä.", - "PostContentViaExternalSource": "Haluan lisätä toisella alustalla julkaisemani sisällön tähän.", + "PostContentViaGithub": "Haluan lisätä artikkelini GitHub -merkintäsääntöjen mukaisesti.", + "PostContentViaYoutube": "Haluan jakaa videoni, jotka ovat käytettävissä Youtube täällä.", + "PostContentViaExternalSource": "Haluan lisätä toisella alustalla julkaisemani sisällön tähän.", "GitHubUserNameValidationMessage": "Github-käyttäjänimesi ei voi sisältää välilyöntiä. Varmista, että Github-käyttäjänimesi on oikea.", "PersonalSiteUrlValidationMessage": "Henkilökohtaisen sivuston URL-osoite ei voi sisältää välilyöntiä. Varmista, että henkilökohtaisen sivuston URL-osoite on oikea.", "TwitterUserNameValidationMessage": "Twitter-käyttäjänimesi ei voi sisältää välilyöntiä. Varmista, että Twitter-käyttäjänimesi on oikea.", "LinkedinUrlValidationMessage": "Linkedin-URL-osoitteesi ei voi sisältää välilyöntiä. Varmista, että Linkedin-URL-osoitteesi on oikea.", "NoPostsFound": "Viestejä ei löytynyt!", - "SearchInPosts": "Hae viesteistä ...", + "SearchInPosts": "Hae viesteistä...", "MinimumSearchContent": "Sinun on annettava vähintään 3 merkkiä!" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/fr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/fr.json index 734dadadcd..5530d28100 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/fr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/fr.json @@ -59,14 +59,14 @@ "QuestionItemErrorMessage": "Impossible d'obtenir les derniers détails de la question de Stackoverflow.", "Oops": "Oops!", "CreateArticleSuccessMessage": "L'article a été soumis avec succès. Il sera publié après un examen de l'administrateur du site.", - "ChooseCoverImage": "Choisissez une image de couverture ...", + "ChooseCoverImage": "Choisissez une image de couverture...", "CoverImage": "Image de couverture", "ShareYourExperiencesWithTheABPFramework": "Partagez vos expériences avec le Framework ABP!", "Optional": "Optionnel", "UpdateUserWebSiteInfo": "Exemple: https://johndoe.com", "UpdateUserTwitterInfo": "Exemple: johndoe", "UpdateUserGithubInfo": "Exemple: johndoe", - "UpdateUserLinkedinInfo": "Exemple: https: //www.linkedin.com / ...", + "UpdateUserLinkedinInfo": "Exemple: https://www.linkedin.com/...", "UpdateUserCompanyInfo": "Exemple: Volosoft", "UpdateUserJobTitleInfo": "Exemple: développeur de logiciels", "UserName": "Nom d'utilisateur", @@ -118,27 +118,27 @@ "CreatePostCoverInfo": "Pour créer un article efficace, ajoutez une photo de couverture. Téléchargez des images au format 16: 9 pour une meilleure vue. Taille maximale du fichier: 1 Mo.", "ThankYouForContribution": "Merci de contribuer à la communauté ABP.", "GithubArticle": "Article Github", - "GithubArticleSubmitStepOne": " 1. Rédigez un article sur n'importe quel référentiel GitHub public au format Markdown. Exemple de ", - "GithubArticleSubmitStepTwo": " 2. Envoyez l'URL de votre article à l'aide du formulaire.", - "GithubArticleSubmitStepThree": " 3. Votre article sera rendu sur ce site Web.", + "GithubArticleSubmitStepOne": " 1. Rédigez un article sur n'importe quel référentiel GitHub public au format Markdown. Exemple de ", + "GithubArticleSubmitStepTwo": " 2. Envoyez l'URL de votre article à l'aide du formulaire.", + "GithubArticleSubmitStepThree": " 3. Votre article sera rendu sur ce site Web.", "YoutubeVideo": "Vidéo Youtube", - "YoutubeVideoSubmitStepOne": " 1. Publiez votre vidéo sur YouTube.", - "YoutubeVideoSubmitStepTwo": " 2. Envoyez l'URL de la vidéo à l'aide du formulaire.", - "YoutubeVideoSubmitStepThree": " 3. Les visiteurs pourront visionner votre contenu vidéo directement sur ce site Web.", + "YoutubeVideoSubmitStepOne": " 1. Publiez votre vidéo sur YouTube.", + "YoutubeVideoSubmitStepTwo": " 2. Envoyez l'URL de la vidéo à l'aide du formulaire.", + "YoutubeVideoSubmitStepThree": " 3. Les visiteurs pourront visionner votre contenu vidéo directement sur ce site Web.", "ExternalContent": "Contenu externe", - "ExternalContentSubmitStepOne": " 1. Créez un contenu sur n'importe quelle plate-forme publique (support, votre propre blog ou partout où vous le souhaitez).", - "ExternalContentSubmitStepTwo": " 2. Envoyez votre URL de contenu à l'aide du formulaire.", - "ExternalContentSubmitStepThree": " 3. Les visiteurs sont redirigés vers le contenu du site Web d'origine.", + "ExternalContentSubmitStepOne": " 1. Créez un contenu sur n'importe quelle plate-forme publique (support, votre propre blog ou partout où vous le souhaitez).", + "ExternalContentSubmitStepTwo": " 2. Envoyez votre URL de contenu à l'aide du formulaire.", + "ExternalContentSubmitStepThree": " 3. Les visiteurs sont redirigés vers le contenu du site Web d'origine.", "ChooseYourContentType": "Veuillez choisir la manière dont vous souhaitez ajouter votre contenu.", - "PostContentViaGithub": "Je souhaite ajouter mon article avec GitHub conformément aux règles de démarque.", - "PostContentViaYoutube": "Je souhaite partager mes vidéos disponibles sur Youtube ici.", - "PostContentViaExternalSource": "Je souhaite ajouter le contenu que j'ai publié sur une autre plate-forme ici.", + "PostContentViaGithub": "Je souhaite ajouter mon article avec GitHub conformément aux règles de démarque.", + "PostContentViaYoutube": "Je souhaite partager mes vidéos disponibles sur Youtube ici.", + "PostContentViaExternalSource": "Je souhaite ajouter le contenu que j'ai publié sur une autre plate-forme ici.", "GitHubUserNameValidationMessage": "Votre nom d'utilisateur Github ne peut pas inclure d'espaces, veuillez vous assurer que votre nom d'utilisateur Github est correct.", "PersonalSiteUrlValidationMessage": "L'URL de votre site personnel ne peut pas inclure d'espaces, veuillez vous assurer que l'URL de votre site personnel est correcte.", "TwitterUserNameValidationMessage": "Votre nom d'utilisateur Twitter ne peut pas inclure d'espaces, veuillez vous assurer que votre nom d'utilisateur Twitter est correct.", "LinkedinUrlValidationMessage": "Votre URL Linkedin ne peut pas inclure d'espace blanc, veuillez vous assurer que votre URL Linkedin est correcte.", "NoPostsFound": "Aucun article trouvé!", - "SearchInPosts": "Rechercher dans les messages ...", + "SearchInPosts": "Rechercher dans les messages...", "MinimumSearchContent": "Vous devez saisir au moins 3 caractères!" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/hi.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/hi.json index a3f824d6ba..c7977142c4 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/hi.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/hi.json @@ -59,14 +59,14 @@ "QuestionItemErrorMessage": "Stackoverflow से नवीनतम प्रश्न विवरण प्राप्त नहीं कर सका।", "Oops": "उफ़!", "CreateArticleSuccessMessage": "अनुच्छेद सफलतापूर्वक प्रस्तुत किया गया है। इसे साइट व्यवस्थापक से समीक्षा के बाद प्रकाशित किया जाएगा।", - "ChooseCoverImage": "कवर छवि चुनें ...", + "ChooseCoverImage": "कवर छवि चुनें...", "CoverImage": "कवर छवि", "ShareYourExperiencesWithTheABPFramework": "ABP फ्रेमवर्क के साथ अपने अनुभव साझा करें!", "Optional": "ऐच्छिक", "UpdateUserWebSiteInfo": "उदाहरण: https://johndoe.com", "UpdateUserTwitterInfo": "उदाहरण: जॉन्डो", "UpdateUserGithubInfo": "उदाहरण: जॉन्डो", - "UpdateUserLinkedinInfo": "उदाहरण: https: //www.linkedin.com / ...", + "UpdateUserLinkedinInfo": "उदाहरण: https://www.linkedin.com/...", "UpdateUserCompanyInfo": "उदाहरण: वोलोसॉफ्ट", "UpdateUserJobTitleInfo": "उदाहरण: सॉफ्टवेयर डेवलपर", "UserName": "उपयोगकर्ता नाम", @@ -96,7 +96,7 @@ "JoinOurArticleNewsletter": "हमारे लेख समाचार पत्र में शामिल हों", "Community": "समुदाय", "Marketing": "विपणन", - "CommunityPrivacyPolicyConfirmation": "मैं नियम और शर्तों और गोपनीयता नीति से सहमत हूं।", + "CommunityPrivacyPolicyConfirmation": "मैं नियम और शर्तों और गोपनीयता नीति से सहमत हूं।", "ArticleRequestMessageTitle": "GitHub पर एक लेख / ट्यूटोरियल जिसे आप इस वेब साइट पर देखना चाहते हैं, का अनुरोध करने के लिए एक समस्या खोलें ।", "ArticleRequestMessageBody": "यहाँ, समुदाय द्वारा अनुरोधित लेखों की सूची। क्या आप एक अनुरोधित लेख लिखना चाहते हैं? कृपया अनुरोध पर क्लिक करें और चर्चा में शामिल हों।", "Language": "भाषा: हिन्दी", @@ -118,27 +118,27 @@ "CreatePostCoverInfo": "एक प्रभावी पोस्ट बनाने के लिए, एक कवर फ़ोटो जोड़ें। सर्वश्रेष्ठ दृश्य के लिए 16: 9 पहलू अनुपात चित्र अपलोड करें। अधिकतम फ़ाइल आकार: 1 एमबी।", "ThankYouForContribution": "ABP समुदाय में योगदान के लिए धन्यवाद।", "GithubArticle": "गीथब लेख", - "GithubArticleSubmitStepOne": " 1। मार्कडाउन प्रारूप के साथ किसी भी सार्वजनिक GitHub रिपॉजिटरी पर एक लेख लिखें। उदाहरण ", - "GithubArticleSubmitStepTwo": " 2। फ़ॉर्म का उपयोग करके अपना लेख URL सबमिट करें।", - "GithubArticleSubmitStepThree": " 3। इस वेब साइट में आपका लेख प्रस्तुत किया जाएगा।", + "GithubArticleSubmitStepOne": "1. मार्कडाउन प्रारूप के साथ किसी भी सार्वजनिक GitHub रिपॉजिटरी पर एक लेख लिखें। उदाहरण ", + "GithubArticleSubmitStepTwo": " 2। फ़ॉर्म का उपयोग करके अपना लेख URL सबमिट करें।", + "GithubArticleSubmitStepThree": " 3। इस वेब साइट में आपका लेख प्रस्तुत किया जाएगा।", "YoutubeVideo": "यूट्यूब वीडियो", - "YoutubeVideoSubmitStepOne": " 1। YouTube पर अपना वीडियो प्रकाशित करें।", - "YoutubeVideoSubmitStepTwo": " 2। फ़ॉर्म का उपयोग करके वीडियो URL सबमिट करें।", - "YoutubeVideoSubmitStepThree": " 3। आगंतुक इस वेबसाइट पर सीधे आपकी वीडियो सामग्री देख सकेंगे।", + "YoutubeVideoSubmitStepOne": " 1। YouTube पर अपना वीडियो प्रकाशित करें।", + "YoutubeVideoSubmitStepTwo": " 2। फ़ॉर्म का उपयोग करके वीडियो URL सबमिट करें।", + "YoutubeVideoSubmitStepThree": " 3। आगंतुक इस वेबसाइट पर सीधे आपकी वीडियो सामग्री देख सकेंगे।", "ExternalContent": "बाहरी सामग्री", - "ExternalContentSubmitStepOne": " 1। किसी भी सार्वजनिक मंच पर एक सामग्री बनाएं (मध्यम, अपना स्वयं का ब्लॉग या कहीं भी आप चाहें)।", - "ExternalContentSubmitStepTwo": " 2। फ़ॉर्म का उपयोग करके अपना सामग्री URL सबमिट करें।", - "ExternalContentSubmitStepThree": " 3। मूल वेबसाइट पर आगंतुकों को सामग्री पर पुनर्निर्देशित किया गया है।", + "ExternalContentSubmitStepOne": " 1। किसी भी सार्वजनिक मंच पर एक सामग्री बनाएं (मध्यम, अपना स्वयं का ब्लॉग या कहीं भी आप चाहें)।", + "ExternalContentSubmitStepTwo": " 2। फ़ॉर्म का उपयोग करके अपना सामग्री URL सबमिट करें।", + "ExternalContentSubmitStepThree": " 3। मूल वेबसाइट पर आगंतुकों को सामग्री पर पुनर्निर्देशित किया गया है।", "ChooseYourContentType": "कृपया अपनी सामग्री जोड़ने का तरीका चुनें।", - "PostContentViaGithub": "मैं अपना लेख जोड़ना चाहता हूं GitHub अंकन नियमों के अनुसार।", - "PostContentViaYoutube": "मैं यहां उपलब्ध अपने वीडियो को Youtube पर साझा करना चाहता हूं।", - "PostContentViaExternalSource": "मैं यहां एक अन्य प्लेटफ़ॉर्म पर प्रकाशित सामग्री जोड़ना चाहता हूं।", + "PostContentViaGithub": "मैं अपना लेख जोड़ना चाहता हूं GitHub अंकन नियमों के अनुसार।", + "PostContentViaYoutube": "मैं यहां उपलब्ध अपने वीडियो को Youtube पर साझा करना चाहता हूं।", + "PostContentViaExternalSource": "मैं यहां एक अन्य प्लेटफ़ॉर्म पर प्रकाशित सामग्री जोड़ना चाहता हूं।", "GitHubUserNameValidationMessage": "आपके Github उपयोगकर्ता नाम में व्हॉट्सएप शामिल नहीं हो सकता है, कृपया सुनिश्चित करें कि आपका Github उपयोगकर्ता नाम सही है।", "PersonalSiteUrlValidationMessage": "आपके व्यक्तिगत साइट URL में व्हॉट्सएप शामिल नहीं हो सकता है, कृपया सुनिश्चित करें कि आपका व्यक्तिगत साइट URL सही है।", "TwitterUserNameValidationMessage": "आपके ट्विटर उपयोगकर्ता नाम में व्हाट्सएप शामिल नहीं हो सकता है, कृपया सुनिश्चित करें कि आपका ट्विटर उपयोगकर्ता नाम सही है।", "LinkedinUrlValidationMessage": "आपके लिंक्डइन URL में व्हॉट्सएप शामिल नहीं हो सकता है, कृपया सुनिश्चित करें कि आपका लिंक्डइन URL सही है।", "NoPostsFound": "कोई प्रकाशन नहीं मिला!", - "SearchInPosts": "पदों में खोजें ...", + "SearchInPosts": "पदों में खोजें...", "MinimumSearchContent": "आपको कम से कम 3 वर्ण दर्ज करने होंगे!", "Volo.AbpIo.Domain:060001": "स्रोत URL (\"{ArticleUrl}\") जीथब URL नहीं है", "Volo.AbpIo.Domain:060002": "लेख सामग्री Github (\"{ArticleUrl}\") संसाधन से उपलब्ध नहीं है।", diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/it.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/it.json index 6e8dfc31e7..513606bd70 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/it.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/it.json @@ -1,21 +1,21 @@ { "culture": "it", "texts": { - "Permission:CommunityArticle": "Articolo comunitario", - "Permission:Edit": "modificare", + "Permission:CommunityArticle": "Articolo della comunità", + "Permission:Edit": "Modifica", "Waiting": "In attesa", "Approved": "Approvato", "Rejected": "Respinto", - "Wait": "Aspettare", - "Approve": "Approvare", - "Reject": "Rifiutare", + "Wait": "Attendere", + "Approve": "Approva", + "Reject": "Rifiuta", "ReadArticle": "Leggi l'articolo", "Status": "Stato", - "ContentSource": "Fonte di contenuto", + "ContentSource": "Fonte contenuto", "Details": "Dettagli", "Url": "Url", "Title": "Titolo", - "CreationTime": "Tempo di creazione", + "CreationTime": "Orario di creazione", "Save": "Salva", "SameUrlAlreadyExist": "Lo stesso URL esiste già se vuoi aggiungere questo articolo, dovresti cambiare l'URL!", "UrlIsNotValid": "URL non valido.", @@ -26,8 +26,8 @@ "Latest": "Ultimo", "ContributeAbpCommunity": "Contribuisci alla comunità ABP", "SubmitYourArticle": "Invia il tuo post", - "ContributionGuide": "Guida al contributo", - "BugReport": "Riportare un errore", + "ContributionGuide": "Guida per i contributi", + "BugReport": "Segnala un errore", "SeeAllArticles": "Vedi tutti i post", "WelcomeToABPCommunity!": "Benvenuto nella comunità ABP!", "MyProfile": "Il mio profilo", @@ -36,11 +36,11 @@ "FeatureRequest": "Richiesta di funzionalità", "CreateArticleTitleInfo": "Titolo del post da mostrare nell'elenco dei post.", "CreateArticleSummaryInfo": "Un breve riassunto del post da mostrare nell'elenco dei post.", - "CreateArticleCoverInfo": "Per creare un articolo efficace, aggiungi una foto di copertina. Carica immagini in formato 16: 9 per una visualizzazione migliore. Dimensione massima del file: 1 MB.", + "CreateArticleCoverInfo": "Per creare un articolo efficace, aggiungi una foto di copertina. Carica immagini in formato 16: 9 per una visualizzazione migliore. Dimensione massima del file: 1MB.", "ThisExtensionIsNotAllowed": "Questa estensione non è consentita.", "TheFileIsTooLarge": "Il file è troppo grande.", "GoToTheArticle": "Vai all'articolo", - "Contribute": "Contribuire", + "Contribute": "Contribuisci", "OverallProgress": "Progressi generali", "Done": "Fatto", "Open": "Aperto", @@ -59,28 +59,28 @@ "QuestionItemErrorMessage": "Impossibile ottenere gli ultimi dettagli della domanda da Stackoverflow.", "Oops": "Ops!", "CreateArticleSuccessMessage": "L'articolo è stato inviato con successo. Verrà pubblicato dopo una revisione da parte dell'amministratore del sito.", - "ChooseCoverImage": "Scegli un'immagine di copertina ...", + "ChooseCoverImage": "Scegli un'immagine di copertina...", "CoverImage": "Immagine di copertina", "ShareYourExperiencesWithTheABPFramework": "Condividi le tue esperienze con ABP Framework!", "Optional": "Opzionale", "UpdateUserWebSiteInfo": "Esempio: https://johndoe.com", "UpdateUserTwitterInfo": "Esempio: johndoe", "UpdateUserGithubInfo": "Esempio: johndoe", - "UpdateUserLinkedinInfo": "Esempio: https: //www.linkedin.com / ...", + "UpdateUserLinkedinInfo": "Esempio: https://www.linkedin.com/...", "UpdateUserCompanyInfo": "Esempio: Volosoft", "UpdateUserJobTitleInfo": "Esempio: sviluppatore di software", "UserName": "Nome utente", "Company": "Azienda", "PersonalWebsite": "Sito web personale", "RegistrationDate": "Data di registrazione", - "Social": "Sociale", + "Social": "Social", "Biography": "Biografia", "HasNoPublishedArticlesYet": "non ha ancora articoli pubblicati", "Author": "Autore", "LatestGithubAnnouncements": "Ultimi annunci su GitHub", "SeeAllAnnouncements": "Vedi tutti gli annunci", "LatestBlogPost": "Ultimo post sul blog", - "Edit": "modificare", + "Edit": "Modifica", "ProfileImageChange": "Cambia l'immagine del profilo", "BlogItemErrorMessage": "Impossibile ottenere i dettagli più recenti del post del blog da ABP.", "PlannedReleaseDate": "Data di rilascio prevista", @@ -96,14 +96,14 @@ "JoinOurArticleNewsletter": "Iscriviti alla nostra newsletter articolo", "Community": "Comunità", "Marketing": "Marketing", - "CommunityPrivacyPolicyConfirmation": "Accetto i Termini e condizioni e l ' Informativa sulla privacy .", - "ArticleRequestMessageTitle": " Apri un numero su GitHub per richiedere un articolo / tutorial che desideri vedere su questo sito web.", + "CommunityPrivacyPolicyConfirmation": "Accetto i Termini e condizioni e l 'Informativa sulla privacy.", + "ArticleRequestMessageTitle": "Apri un numero su GitHub per richiedere un articolo / tutorial che desideri vedere su questo sito web.", "ArticleRequestMessageBody": "Di seguito l'elenco degli articoli richiesti dalla community. Vuoi scrivere un articolo richiesto? Fare clic sulla richiesta e partecipare alla discussione.", - "Language": "linguaggio", + "Language": "Lingua", "CreateArticleLanguageInfo": "La lingua per il contenuto del post.", "VideoPost": "Post video", "Article": "Articolo", - "Read": "Leggere", + "Read": "Leggi", "CreateGithubArticleUrlInfo": "URL originale di GitHub dell'articolo.", "CreateVideoContentUrlInfo": "URL YouTube originale del post.", "CreateExternalArticleUrlInfo": "URL esterno originale dell'articolo.", @@ -115,33 +115,33 @@ "VideoUrl": "URL video", "GithubArticleUrl": "URL articolo Github", "ExternalArticleUrl": "URL articolo esterno", - "CreatePostCoverInfo": "Per creare un post efficace, aggiungi una foto di copertina. Carica immagini in formato 16: 9 per una visualizzazione migliore. Dimensione massima del file: 1 MB.", + "CreatePostCoverInfo": "Per creare un post efficace, aggiungi una foto di copertina. Carica immagini in formato 16: 9 per una visualizzazione migliore. Dimensione massima del file: 1MB.", "ThankYouForContribution": "Grazie per aver contribuito alla comunità ABP.", "GithubArticle": "Articolo GitHub", - "GithubArticleSubmitStepOne": " 1. Scrivi un articolo su qualsiasi repository GitHub pubblico con il formato Markdown. esempio ", - "GithubArticleSubmitStepTwo": " 2. Invia l'URL del tuo articolo utilizzando il modulo.", - "GithubArticleSubmitStepThree": " 3. Il tuo articolo verrà visualizzato in questo sito web.", + "GithubArticleSubmitStepOne": "1. Scrivi un articolo su qualsiasi repository GitHub pubblico con il formato Markdown.esempio", + "GithubArticleSubmitStepTwo": "2. Invia l'URL del tuo articolo utilizzando il modulo.", + "GithubArticleSubmitStepThree": "3. Il tuo articolo verrà visualizzato in questo sito web.", "YoutubeVideo": "Video Youtube", - "YoutubeVideoSubmitStepOne": " 1. Pubblica il tuo video su YouTube.", - "YoutubeVideoSubmitStepTwo": " 2. Invia l'URL del video utilizzando il modulo.", - "YoutubeVideoSubmitStepThree": " 3. I visitatori potranno guardare i tuoi contenuti video direttamente su questo sito web.", + "YoutubeVideoSubmitStepOne": "1. Pubblica il tuo video su YouTube.", + "YoutubeVideoSubmitStepTwo": "2. Invia l'URL del video utilizzando il modulo.", + "YoutubeVideoSubmitStepThree": "3. I visitatori potranno guardare i tuoi contenuti video direttamente su questo sito web.", "ExternalContent": "Contenuto esterno", - "ExternalContentSubmitStepOne": " 1. Crea un contenuto su qualsiasi piattaforma pubblica (mezzo, il tuo blog o ovunque tu voglia).", - "ExternalContentSubmitStepTwo": " 2. Invia l'URL dei tuoi contenuti utilizzando il modulo.", - "ExternalContentSubmitStepThree": " 3. I visitatori vengono reindirizzati al contenuto del sito web originale.", + "ExternalContentSubmitStepOne": "1. Crea un contenuto su qualsiasi piattaforma pubblica (mezzo, il tuo blog o ovunque tu voglia).", + "ExternalContentSubmitStepTwo": "2. Invia l'URL dei tuoi contenuti utilizzando il modulo.", + "ExternalContentSubmitStepThree": "3. I visitatori vengono reindirizzati al contenuto del sito web originale.", "ChooseYourContentType": "Scegli il modo in cui desideri aggiungere i tuoi contenuti.", - "PostContentViaGithub": "Voglio aggiungere il mio articolo con GitHub in conformità con le regole di markdown.", - "PostContentViaYoutube": "Voglio condividere i miei video disponibili su Youtube qui.", - "PostContentViaExternalSource": "Voglio aggiungere qui il contenuto che ho pubblicato su un'altra piattaforma .", + "PostContentViaGithub": "Voglio aggiungere il mio articolo con GitHub in conformità con le regole di markdown.", + "PostContentViaYoutube": "Voglio condividere i miei video disponibili su Youtube qui.", + "PostContentViaExternalSource": "Voglio aggiungere qui il contenuto che ho pubblicato su un'altra piattaforma.", "GitHubUserNameValidationMessage": "Il tuo nome utente Github non può includere spazi bianchi, assicurati che il tuo nome utente Github sia corretto.", "PersonalSiteUrlValidationMessage": "L'URL del tuo sito personale non può includere spazi bianchi, assicurati che l'URL del tuo sito personale sia corretto.", "TwitterUserNameValidationMessage": "Il tuo nome utente Twitter non può includere spazi bianchi, assicurati che il tuo nome utente Twitter sia corretto.", "LinkedinUrlValidationMessage": "Il tuo URL di Linkedin non può includere spazi bianchi, assicurati che il tuo URL di Linkedin sia corretto.", "NoPostsFound": "Nessun post trovato!", - "SearchInPosts": "Cerca nei post ...", + "SearchInPosts": "Cerca nei post...", "MinimumSearchContent": "Devi inserire almeno 3 caratteri!", - "Volo.AbpIo.Domain:060001": "L'URL di origine (\"{ArticleUrl}\") non è l'URL di GitHub", - "Volo.AbpIo.Domain:060002": "Il contenuto dell'articolo non è disponibile dalla risorsa Github (\"{ArticleUrl}\").", + "Volo.AbpIo.Domain:060001": "Source URL(\"{ArticleUrl}\") non è un URL di GitHub", + "Volo.AbpIo.Domain:060002": "Il contenuto dell'articolo non è disponibile dalla risorsa Github(\"{ArticleUrl}\").", "Volo.AbpIo.Domain:060003": "Nessun contenuto dell'articolo trovato!" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/sk.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/sk.json new file mode 100644 index 0000000000..e01e201969 --- /dev/null +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/sk.json @@ -0,0 +1,147 @@ +{ + "culture": "sk", + "texts": { + "Permission:CommunityArticle": "Článok komunity", + "Permission:Edit": "Upraviť", + "Waiting": "Čakajte", + "Approved": "Schválené", + "Rejected": "Zamietnuté", + "Wait": "Čakajte", + "Approve": "Schváliť", + "Reject": "Zamietnuť", + "ReadArticle": "Prečítajte si článok", + "Status": "Stav", + "ContentSource": "Zdroj obsahu", + "Details": "Podrobnosti", + "Url": "URL", + "Title": "Názov", + "CreationTime": "Čas vytvorenia", + "Save": "Uložiť", + "SameUrlAlreadyExist": "Rovnaká URL už existuje, ak chcete pridať tento článok, mali by ste zmeniť URL!", + "UrlIsNotValid": "URL nie je platná.", + "UrlNotFound": "URL adresa nebola nájdená.", + "UrlContentNotFound": "Obsah URL sa nenašiel.", + "Summary": "Zhrnutie", + "MostRead": "Najčastejšie čítané", + "Latest": "Najnovšie", + "ContributeAbpCommunity": "Prispieť do ABPkomunity", + "SubmitYourArticle": "Odoslanie príspevku", + "ContributionGuide": "Sprievodca príspevkami", + "BugReport": "Bug report", + "SeeAllArticles": "Zobraziť všetky príspevky", + "WelcomeToABPCommunity!": "Vitajte v ABP komunite!", + "MyProfile": "Môj profil", + "MyOrganizations": "Moje organizácie", + "EmailNotValid": "Zadajte platnú e-mailovú adresu.", + "FeatureRequest": "Nová funkčnosť", + "CreateArticleTitleInfo": "Názov príspevku, ktorý sa má zobraziť nad zoznamom príspevkov.", + "CreateArticleSummaryInfo": "Krátke zhrnutie príspevku, ktoré sa zobrazí nad zoznamom príspevkov.", + "CreateArticleCoverInfo": "Ak chcete vytvoriť efektívny článok, pridajte titulnú fotografiu. Pre optimálne zobrazenie nahrávajte obrázky s pomerom strán 16:9. Maximálna veľkosť súboru: 1 MB.", + "ThisExtensionIsNotAllowed": "Toto rozšírenie nie je povolené.", + "TheFileIsTooLarge": "Súbor je príliš veľký.", + "GoToTheArticle": "Prejsť na článok", + "Contribute": "Prispieť", + "OverallProgress": "Celkový pokrok", + "Done": "Hotovo", + "Open": "Otvoriť", + "Closed": "Uzavreté", + "LatestQuestionOnThe": "Posledná otázka", + "Stackoverflow": "Pretečenie zásobníka", + "Votes": "hlasy", + "Answer": "Odpoveď", + "Views": "zobrazenia", + "Answered": "Zodpovedané", + "WaitingForYourAnswer": "Čakáme na vašu odpoveď", + "Asked": "Otázka znela", + "AllQuestions": "Všetky otázky", + "NextVersion": "Ďalšia verzia", + "MilestoneErrorMessage": "Nepodarilo sa získať detaily current milestone z Githubu.", + "QuestionItemErrorMessage": "Z dôvodu pretečenia zísobníka nie je možné načítať detaily poslednej otázky.", + "Oops": "Ups!", + "CreateArticleSuccessMessage": "Článok bol úspešne odoslaný. Bude uverejnený po kontrole administrátorom stránky.", + "ChooseCoverImage": "Vyberte si titulný obrázok...", + "CoverImage": "Obrázok obálky", + "ShareYourExperiencesWithTheABPFramework": "Podeľte sa o svoje skúsenosti s ABP frameworkom!", + "Optional": "Voliteľné", + "UpdateUserWebSiteInfo": "Príklad: https://johndoe.com", + "UpdateUserTwitterInfo": "Príklad: johndoe", + "UpdateUserGithubInfo": "Príklad: johndoe", + "UpdateUserLinkedinInfo": "Príklad: https://www.linkedin.com/...", + "UpdateUserCompanyInfo": "Príklad: Volosoft", + "UpdateUserJobTitleInfo": "Príklad: Vývojár softvéru", + "UserName": "Meno používateľa", + "Company": "Spoločnosť", + "PersonalWebsite": "Osobná webová stránka", + "RegistrationDate": "Dátum registrácie", + "Social": "Sociálna sieť", + "Biography": "Životopis", + "HasNoPublishedArticlesYet": "zatiaľ nemá publikované žiadne články", + "Author": "Autor", + "LatestGithubAnnouncements": "Najnovšie oznámenia Githubu", + "SeeAllAnnouncements": "Pozrite si všetky oznámenia", + "LatestBlogPost": "Najnovší príspevok na blogu", + "Edit": "Upraviť", + "ProfileImageChange": "Zmeniť profilový obrázok", + "BlogItemErrorMessage": "Z ABP nie je možné načítať posledné zmeny príspevku na blogu.", + "PlannedReleaseDate": "Plánovaný dátum nového vydania", + "CommunityArticleRequestErrorMessage": "Nepodarilo sa načítať najnovšiu požiadavku na článok z Githubu.", + "ArticleRequestFromGithubIssue": "Nie sú žiadne žiadosti o článok.", + "LatestArticles": "Posledne pridané príspevky", + "ArticleRequests": "Žiadané články", + "AllArticleRequests": "Zobraziť všetky žiadosti o články", + "SubscribeToTheNewsletter": "Prihlásiť sa k odberu noviniek", + "NewsletterEmailDefinition": "Získajte informácie o dianí v ABP, ako sú nové vydania, bezplatné zdroje, články a ďalšie.", + "NoThanks": "Nie, ďakujem", + "MaybeLater": "Možno neskôr", + "JoinOurArticleNewsletter": "Pripojte sa k odberu noviniek", + "Community": "Komunita", + "Marketing": "Marketing", + "CommunityPrivacyPolicyConfirmation": "Súhlasím so zmluvnými podmienkami a zásadami ochrany osobných údajov.", + "ArticleRequestMessageTitle": "Otvorte problém na GitHube a požiadajte o článok/návod, ktorý chcete vidieť na tejto webovej stránke.", + "ArticleRequestMessageBody": "Tu je zoznam požadovaných článkov komunity. Chcete napísať požadovaný článok? Kliknite na žiadosť a zapojte sa do diskusie.", + "Language": "Jazyk", + "CreateArticleLanguageInfo": "Jazyk pre obsah príspevku.", + "VideoPost": "Video príspevok", + "Article": "Článok", + "Read": "Prečítajte si", + "CreateGithubArticleUrlInfo": "Pôvodná URL adresa GitHub článku.", + "CreateVideoContentUrlInfo": "Pôvodná URL adresa príspevku na Youtube.", + "CreateExternalArticleUrlInfo": "Pôvodná externá URL adresa článku.", + "VideoContentForm": "Odoslanie videa na YouTube", + "GithubPostForm": "Odoslanie článku na GitHub", + "ExternalPostForm": "Odoslanie externého obsahu", + "HowToPost": "Ako zverejňovať príspevky?", + "Posts": "Príspevky", + "VideoUrl": "URL adresa videa", + "GithubArticleUrl": "URL článku na Githube ", + "ExternalArticleUrl": "URL externého článku", + "CreatePostCoverInfo": "Ak chcete vytvoriť efektívny príspevok, pridajte titulnú fotografiu. Pre optimálne zobrazenie nahrávajte obrázky s pomerom strán 16:9. Maximálna veľkosť súboru: 1 MB.", + "ThankYouForContribution": "Ďakujeme, že ste prispeli do ABP komunity.", + "GithubArticle": "Článok na Githube", + "GithubArticleSubmitStepOne": "1. Napíšte článok v ľubovoľnom verejnom repozitári GitHub vo formáte Markdown. príklad", + "GithubArticleSubmitStepTwo": "2. Odoslať URL adresu článku pomocou formulára.", + "GithubArticleSubmitStepThree": "3. Váš článok sa zobrazí na tejto webovej stránke.", + "YoutubeVideo": "Video na YouTube", + "YoutubeVideoSubmitStepOne": "1. Uverejnite svoje video na YouTube.", + "YoutubeVideoSubmitStepTwo": "2. Odoslať URL adresu videa pomocou formulára.", + "YoutubeVideoSubmitStepThree": "3. Návštevníci budú môcť sledovať váš video obsah priamo na tejto webovej stránke.", + "ExternalContent": "Externý obsah", + "ExternalContentSubmitStepOne": "1. Vytvorte obsah na akejkoľvek verejnej platforme (médium, vlastný blog alebo kdekoľvek chcete).", + "ExternalContentSubmitStepTwo": "2. Pomocou formulára odošlite URL adresu obsahu.", + "ExternalContentSubmitStepThree": "3. Návštevníci sú presmerovaní na obsah pôvodnej webovej stránky.", + "ChooseYourContentType": "Vyberte si spôsob, akým chcete pridať obsah.", + "PostContentViaGithub": "Chcem pridať svoj článok s GitHub v súlade s markdown pravidlami.", + "PostContentViaYoutube": "Chcem sa podeliť o svoje videá dostupné na Youtube tu.", + "PostContentViaExternalSource": "Chcem sem pridať obsah, ktorý som uverejnil na inej platforme.", + "GitHubUserNameValidationMessage": "Vaše používateľské Github meno nesmie obsahovať medzery, uistite sa, že vaše používateľské Github meno je správne.", + "PersonalSiteUrlValidationMessage": "URL adresa vašej osobnej stránky nesmie obsahovať medzery. Uistite sa, že URL adresa vašej osobnej stránky je správna.", + "TwitterUserNameValidationMessage": "Vaše používateľské meno na Twitteri nesmie obsahovať prázdne znaky. Uistite sa, že je vaše používateľské meno na Twitteri správne.", + "LinkedinUrlValidationMessage": "Vaša adresa URL na sieti Linkedin nesmie obsahovať prázdne znaky. Uistite sa, že vaša URL adresa na sieti Linkedin je správna.", + "NoPostsFound": "Nenašli sa žiadne príspevky!", + "SearchInPosts": "Vyhľadávanie v príspevkoch...", + "MinimumSearchContent": "Musíte zadať aspoň 3 znaky!", + "Volo.AbpIo.Domain:060001": "Zdrojová URL(\"{ArticleUrl}\") nie je URL Githubu", + "Volo.AbpIo.Domain:060002": "Obsah článku nie je dostupný v Github zdroji(\"{ArticleUrl}\").", + "Volo.AbpIo.Domain:060003": "Nenašiel sa žiadny obsah článku!" + } +} \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en-GB.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en-GB.json index 1f060d00b0..c53bf82d37 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en-GB.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en-GB.json @@ -255,8 +255,6 @@ "Surname": "Surname", "CompanyName": "Company Name", "DoYouAgreePrivacyPolicy": "I agree to the Terms & Conditions and Privacy Policy.", - "VolosoftMarketingInformationMessage": "I would like information, tips, and offers about Solutions for Businesses and Organizations and other Volosoft products and services.", - "VolosoftSharingInformationMessage": "I would like Volosoft to share my information with select partners so I can receive relevant information about their products and services.", "Free": "Free", "DDDEBook": "DDD E-book", "PracticalGuideForImplementingDDD": "This book is a practical guide for implementing the Domain Driven Design with the ABP Framework.", @@ -270,6 +268,8 @@ "WeWillSendYouADownloadLink": "We've sent the file to {0}.", "GoHome": "Go Home", "InvalidFormInputs": "Please, type the valid information specified on the form.", - "DDDBookEmailBody": "Thank you.
To download your book, click here." + "DDDBookEmailBody": "Thank you.
To download your book, click here.", + "SubscribeToNewsletter": "Subscribe to the newsletter to get information about happenings in the ABP.IO Platform, like new releases, articles, offers, and more.", + "FirstEdition": "First Edition" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json index 87da55dbab..1385845f2e 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json @@ -244,7 +244,7 @@ "ExampleUseCase": "Example Use Case", "DomainAndApplicationLogic": "Domain Logic & Application Logic", "Author": "Author", - "Page": "Page", + "Pages": "Pages", "PublishedOn": "Published on", "FreeEBook": "Free E-Book", "Download": "Download", @@ -256,8 +256,6 @@ "Surname": "Surname", "CompanyName": "Company Name", "DoYouAgreePrivacyPolicy": "I agree to the Terms & Conditions and Privacy Policy.", - "VolosoftMarketingInformationMessage": "I would like information, tips, and offers about Solutions for Businesses and Organizations and other Volosoft products and services.", - "VolosoftSharingInformationMessage": "I would like Volosoft to share my information with select partners so I can receive relevant information about their products and services.", "Free": "Free", "DDDEBook": "DDD E-book", "PracticalGuideForImplementingDDD": "This book is a practical guide for implementing the Domain Driven Design with the ABP Framework.", @@ -268,9 +266,13 @@ "DomainVsApplicationLogic": "Domain Logic vs Application Logic", "SamplesAndDiscussions": "Samples & Discussions", "EmailNotValid": "Please enter a valid email address.", - "WeWillSendYouADownloadLink": "A link to download the e-book has been sent to {0}.
Check your inbox / junk / spam boxes!", + "WeWillSendYouADownloadLink": "A link to download the e-book has been sent to {0}. Check your inbox, junk or spam boxes!", "GoHome": "Go Home", "InvalidFormInputs": "Please, type the valid information specified on the form.", - "DDDBookEmailBody": "Thank you.
To download your book, click here." + "DDDBookEmailBody": "Thank you.
To download your book, click here.", + "SubscribeToNewsletter": "Subscribe to the newsletter to get information about happenings in the ABP.IO Platform, like new releases, articles, offers, and more.", + "FirstEdition": "First Edition", + "ThankYou": "Thank you!", + "CheckboxMandatory": "You need to check this to proceed!" } } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/fi.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/fi.json index 9b15582b38..3f0ab0a153 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/fi.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/fi.json @@ -36,13 +36,13 @@ "BuiltInBundlingMinification": "Sisäänrakennettu niputtaminen ja pienentäminen", "BuiltInBundlingMinificationExplanation": "Niputtamiseen ja pienentämiseen ei tarvitse käyttää ulkoisia työkaluja. ABP tarjoaa yksinkertaisemman, dynaamisemman, tehokkaamman, modulaarisemman ja sisäänrakennetun tavan!", "VirtualFileSystem": "Virtuaalinen tiedostojärjestelmä", - "VirtualFileSystemExplanation": "Upota näkymät, komentosarjat, tyylit, kuvat ... paketteihin / kirjastoihin ja käytä niitä uudelleen eri sovelluksissa.", + "VirtualFileSystemExplanation": "Upota näkymät, komentosarjat, tyylit, kuvat... paketteihin / kirjastoihin ja käytä niitä uudelleen eri sovelluksissa.", "Theming": "Heidät", "ThemingExplanationShort": "Käytä ja muokkaa bootstrap-pohjaista vakiokäyttöliittymän teemaa tai luo oma.", "BootstrapTagHelpersDynamicForms": "Bootstrap Tag Helpers ja dynaamiset lomakkeet", - "BootstrapTagHelpersDynamicFormsExplanation": "Sen sijaan, että kirjoittaisit manuaalisesti käynnistysstrap-komponenttien toistuvia yksityiskohtia, käytä ABP: n tag-avustajia yksinkertaistaaksesi sitä ja hyödyntääksesi älykkäitä ominaisuuksia. Rakenna käyttöliittymälomakkeet nopeasti C # -mallin perusteella käyttämällä dynaamista lomaketunnisteen auttajaa.", + "BootstrapTagHelpersDynamicFormsExplanation": "Sen sijaan, että kirjoittaisit manuaalisesti käynnistysstrap-komponenttien toistuvia yksityiskohtia, käytä ABP: n tag-avustajia yksinkertaistaaksesi sitä ja hyödyntääksesi älykkäitä ominaisuuksia. Rakenna käyttöliittymälomakkeet nopeasti C# -mallin perusteella käyttämällä dynaamista lomaketunnisteen auttajaa.", "HTTPAPIsDynamicProxies": "HTTP-sovellusliittymät ja dynaamiset välityspalvelimet", - "HTTPAPIsDynamicProxiesExplanation": "Altista sovelluspalvelut automaattisesti REST-tyylisiksi HTTP-sovellusliittymiksi ja kuluta niitä dynaamisilla JavaScript- ja C # -välityspalvelimilla.", + "HTTPAPIsDynamicProxiesExplanation": "Altista sovelluspalvelut automaattisesti REST-tyylisiksi HTTP-sovellusliittymiksi ja kuluta niitä dynaamisilla JavaScript- ja C# -välityspalvelimilla.", "CompleteArchitectureInfo": "Moderni arkkitehtuuri ylläpidettävien ohjelmistoratkaisujen luomiseksi.", "DomainDrivenDesignBasedLayeringModelExplanation": "Auttaa sinua toteuttamaan DDD-pohjaisen kerrostetun arkkitehtuurin ja rakentamaan ylläpidettävän koodipohjan.", "DomainDrivenDesignBasedLayeringModelExplanationCont": "Tarjoaa käynnistysmalleja, abstrakteja, perusluokkia, palveluja, dokumentaatiota ja oppaita, joiden avulla voit kehittää sovellustasi DDD-mallien ja -periaatteiden perusteella.", @@ -70,7 +70,7 @@ "ConventionOverConfigurationExplanation": "ABP toteuttaa oletusarvoisesti yleiset sovelluskäytännöt minimaalisella tai nolla-kokoonpanolla.", "ConventionOverConfigurationExplanationList1": "Auto rekisteröi tunnetut palvelut riippuvuusinjektioon.", "ConventionOverConfigurationExplanationList2": "Paljastaa sovelluspalvelut HTTP-sovellusliittyminä nimeämällä käytäntöjä.", - "ConventionOverConfigurationExplanationList3": "Luo dynaamiset HTTP-asiakasvälityspalvelimet C #: lle ja JavaScriptille.", + "ConventionOverConfigurationExplanationList3": "Luo dynaamiset HTTP-asiakasvälityspalvelimet C# : lle ja JavaScript.", "ConventionOverConfigurationExplanationList4": "Tarjoaa oletusvarastoja yhteisöillesi.", "ConventionOverConfigurationExplanationList5": "Hallitsee työyksikköä verkkopyynnön tai sovelluspalvelumenetelmän mukaan.", "ConventionOverConfigurationExplanationList6": "Julkaisee luoda, päivittää ja poistaa tapahtumia yhteisöillesi.", @@ -94,7 +94,7 @@ "DynamicForms": "Dynaamiset lomakkeet", "BundlingMinification": "Niputtaminen ja minimointi", "BackgroundJobs": "Taustatyöt", - "BackgroundJobsExplanation": "Määritä yksinkertaiset luokat taustalla olevien töiden suorittamiseksi jonossa. Käytä sisäänrakennettua työnhallintaa tai integroi oma. Hangfire ja RabbitMQ -integraatiot ovat jo käytettävissä.", + "BackgroundJobsExplanation": "Määritä yksinkertaiset luokat taustalla olevien töiden suorittamiseksi jonossa. Käytä sisäänrakennettua työnhallintaa tai integroi oma. Hangfire ja RabbitMQ -integraatiot ovat jo käytettävissä.", "DDDInfrastructure": "DDD-infrastruktuuri", "DomainDrivenDesignInfrastructure": "Toimialueohjattu suunnittelun infrastruktuuri", "AutoRESTAPIs": "Auto REST -sovellusliittymät", @@ -121,13 +121,13 @@ "MultiTenancyExplanation3": "Tukee yhtä tietokantaa, tietokantaa vuokralaista kohti ja hybridi-lähestymistapoja.", "MultiTenancyExplanation4": "Keskity yrityksesi koodiin ja annat kehyksen hoitamaan monivuokrausta puolestasi.", "BootstrapTagHelpersExplanation": "Sen sijaan, että kirjoittaisit manuaalisesti uudelleenkäynnistyskomponenttien yksityiskohtia, käytä ABP: n tunnisteita yksinkertaistaaksesi sitä ja hyödyntäksesi älykkäitä ominaisuuksia. Voit ehdottomasti käyttää Bootstrapia milloin tahansa.", - "DynamicFormsExplanation": "Dynaamiset lomake- ja syöttötunnisteiden apurit voivat luoda täydellisen lomakkeen mallina C # -luokasta.", + "DynamicFormsExplanation": "Dynaamiset lomake- ja syöttötunnisteiden apurit voivat luoda täydellisen lomakkeen mallina C# -luokasta.", "AuthenticationAuthorizationExplanation": "ASP.NET Core Identity & IdentityServer4 -palveluun integroidut monipuoliset todennus- ja todennusvaihtoehdot. Tarjoaa laajennettavan ja yksityiskohtaisen lupajärjestelmän.", "CrossCuttingConcernsExplanation": "Älä toista itseäsi kaikkien näiden yleisten asioiden toteuttamiseksi uudelleen ja uudelleen. Keskity yrityskoodiin ja anna ABP: n automatisoida ne käytäntöjen mukaan.", "DatabaseConnectionTransactionManagement": "Tietokantayhteys ja tapahtumien hallinta", "CorrelationIdTracking": "Korrelaatio-Id-seuranta", "BundlingMinificationExplanation": "ABP tarjoaa yksinkertaisen, dynaamisen, tehokkaan, modulaarisen ja sisäänrakennetun niputus- ja pienentämisjärjestelmän.", - "VirtualFileSystemnExplanation": "Virtuaalinen tiedostojärjestelmä mahdollistaa sellaisten tiedostojen hallinnan, joita ei ole fyysisesti tiedostojärjestelmässä (levyllä). Sitä käytetään pääasiassa upottamaan (js, css, kuva, cshtml ...) tiedostot kokoonpanoiksi ja käyttämään niitä kuin fyysisiä tiedostoja ajon aikana.", + "VirtualFileSystemnExplanation": "Virtuaalinen tiedostojärjestelmä mahdollistaa sellaisten tiedostojen hallinnan, joita ei ole fyysisesti tiedostojärjestelmässä (levyllä). Sitä käytetään pääasiassa upottamaan (js, css, kuva, cshtml...) tiedostot kokoonpanoiksi ja käyttämään niitä kuin fyysisiä tiedostoja ajon aikana.", "ThemingExplanation": "Theming-järjestelmän avulla voit kehittää sovellus- ja moduuliteemasi itsenäisesti määrittelemällä joukon yhteisiä peruskirjastoja ja asetteluja uusimman Bootstrap-kehyksen perusteella.", "DomainDrivenDesignInfrastructureExplanation": "Täydellinen infrastruktuuri kerrostettujen sovellusten rakentamiseen, joka perustuu toimialueohjattuihin suunnittelumalleihin ja periaatteisiin;", "Specification": "Erittely", @@ -138,7 +138,7 @@ "DataTransferObject": "Tiedonsiirtokohde", "AggregateRootEntity": "Kokonaisjuuri, entiteetti", "AutoRESTAPIsExplanation": "ABP voi määrittää sovelluspalvelut automaattisesti API-ohjaimiksi sopimuksen mukaan.", - "DynamicClientProxiesExplanation": "Kuluta helposti API: si JavaScript- ja C # -asiakkailta.", + "DynamicClientProxiesExplanation": "Kuluta helposti API: si JavaScript- ja C# -asiakkailta.", "DistributedEventBusWithRabbitMQIntegrationExplanation": "Julkaise ja kuluta jaettuja tapahtumia helposti käyttämällä sisäänrakennettua hajautettua tapahtumaväylää, jossa on käytettävissä RabbitMQ-integraatio.", "TestInfrastructureExplanation": "Kehys on kehitetty yksikkö- ja integraatiotestaus ajatellen. Tarjoaa sinulle perusluokkia helpottamaan. Käynnistysmalleissa on valmiiksi konfiguroitu testaus.", "AuditLoggingEntityHistoriesExplanation": "Sisäänrakennettu tarkastuskirjaus yrityskriittisille sovelluksille. Pyyntö-, palvelu-, menetelmätason tarkastusloki ja entiteettihistoria omaisuuden tason yksityiskohdilla.", @@ -155,7 +155,7 @@ "Code": "Koodi", "Result": "Tulos", "SeeTheDocumentForMoreInformation": "Katso lisätietoja {0} asiakirjasta ", - "IndexPageHeroSection": " avoin lähdekoodi verkkosovellus
Framework
asp.net-ytimelle ", + "IndexPageHeroSection": " avoin lähdekoodi verkkosovellus
Framework
asp.net-ytimelle ", "UiFramework": "Käyttöliittymäkehys", "EmailAddress": "Sähköpostiosoite", "Mobile": "Matkapuhelin", @@ -169,13 +169,13 @@ "JoinTheABPCommunityInfo": "Ole mukana elävässä yhteisössä ja tule osallistujana sivutuotteiden kehykseen!", "AllArticles": "Kaikki artikkelit", "SubmitYourArticle": "Lähetä artikkelisi", - "DynamicClientProxyDocument": "Katso dynaamisen asiakkaan välityspalvelimen dokumentaatiot JavaScriptille ja C # .", + "DynamicClientProxyDocument": "Katso dynaamisen asiakkaan välityspalvelimen dokumentaatiot JavaScript ja C#.", "EmailSMSAbstractionsDocument": "Katso lisätietoja sähköpostitse ja tekstiviestien lähettäminen -asiakirjoista.", "CreateProjectWizard": "Tämä ohjattu toiminto luo uuden projektin käynnistysmallista, joka on määritetty oikein aloittamaan projekti.", "TieredOption": "Luo porrastetun ratkaisun, jossa Web- ja Http-API-kerrokset erotetaan fyysisesti. Jos sitä ei ole valittu, luodaan kerrostettu ratkaisu, joka on vähemmän monimutkainen ja sopii useimpiin tilanteisiin.", "SeparateIdentityServerOption": "Erottaa palvelinpuolen kahteen sovellukseen: Ensimmäinen on identiteettipalvelimelle ja toinen palvelinpuolen HTTP-sovellusliittymälle.", "UseslatestPreVersion": "Käyttää uusinta julkaisua edeltävää versiota", - "ReadTheDocumentation": " Lue Dokumentaatio ", + "ReadTheDocumentation": " Lue Dokumentaatio ", "Documentation": "Dokumentointi", "GettingStartedTutorial": "Aloitusopas", "ApplicationDevelopmentTutorial": "Sovelluskehitysopastus", @@ -195,7 +195,7 @@ "MultipleUIOptionsExplanation": "Ydinkehys on suunniteltu käyttöliittymästä riippumattomaksi ja se voi toimia minkä tahansa tyyppisen käyttöliittymäjärjestelmän kanssa, kun taas useita valmiiksi rakennettuja ja integroituja vaihtoehtoja toimitetaan heti.", "MultipleDBOptionsExplanation": "Kehys voi toimia minkä tahansa tietolähteen kanssa, kun taas seuraavat palveluntarjoajat on virallisesti kehitetty ja tuettu;", "SelectLanguage": "Valitse kieli", - "LatestArticleOnCommunity": "Viimeisin artikkeli ABP-yhteisöstä", + "LatestArticleOnCommunity": "Viimeisin artikkeli ABP Community", "Register": "Rekisteröidy", "IsDownloadable": "On ladattavissa" } diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/fr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/fr.json index 878af5e830..6b17234477 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/fr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/fr.json @@ -36,11 +36,11 @@ "BuiltInBundlingMinification": "Regroupement et minification intégrés", "BuiltInBundlingMinificationExplanation": "Pas besoin d'utiliser des outils externes pour le regroupement et la minimisation. ABP offre un moyen plus simple, dynamique, puissant, modulaire et intégré!", "VirtualFileSystem": "Système de fichiers virtuel", - "VirtualFileSystemExplanation": "Incorporez des vues, des scripts, des styles, des images ... dans des packages / bibliothèques et réutilisez-les dans différentes applications.", + "VirtualFileSystemExplanation": "Incorporez des vues, des scripts, des styles, des images... dans des packages / bibliothèques et réutilisez-les dans différentes applications.", "Theming": "Thème", "ThemingExplanationShort": "Utilisez et personnalisez le thème d'interface utilisateur standard basé sur le bootstrap ou créez le vôtre.", "BootstrapTagHelpersDynamicForms": "Aide aux balises Bootstrap et formulaires dynamiques", - "BootstrapTagHelpersDynamicFormsExplanation": "Au lieu d'écrire manuellement les détails répétitifs des composants d'amorçage, utilisez les assistants de balises d'ABP pour le simplifier et tirer parti d'intellisense. Créez rapidement des formulaires d'interface utilisateur basés sur un modèle C # à l'aide de l'assistant de balise de formulaire dynamique.", + "BootstrapTagHelpersDynamicFormsExplanation": "Au lieu d'écrire manuellement les détails répétitifs des composants d'amorçage, utilisez les assistants de balises d'ABP pour le simplifier et tirer parti d'intellisense. Créez rapidement des formulaires d'interface utilisateur basés sur un modèle C# à l'aide de l'assistant de balise de formulaire dynamique.", "HTTPAPIsDynamicProxies": "API HTTP et proxys dynamiques", "HTTPAPIsDynamicProxiesExplanation": "Exposez automatiquement les services d'application en tant qu'API HTTP de style REST et utilisez-les avec des proxys JavaScript et C# dynamiques.", "CompleteArchitectureInfo": "Architecture moderne pour créer des solutions logicielles maintenables.", @@ -127,7 +127,7 @@ "DatabaseConnectionTransactionManagement": "Connexion à la base de données et gestion des transactions", "CorrelationIdTracking": "Suivi des identifiants de corrélation", "BundlingMinificationExplanation": "ABP offre un système de regroupement et de minification simple, dynamique, puissant, modulaire et intégré.", - "VirtualFileSystemnExplanation": "Le système de fichiers virtuel permet de gérer des fichiers qui n'existent pas physiquement sur le système de fichiers (disque). Il est principalement utilisé pour incorporer des fichiers (js, css, image, cshtml ...) dans des assemblys et les utiliser comme des fichiers physiques lors de l'exécution.", + "VirtualFileSystemnExplanation": "Le système de fichiers virtuel permet de gérer des fichiers qui n'existent pas physiquement sur le système de fichiers (disque). Il est principalement utilisé pour incorporer des fichiers (js, css, image, cshtml...) dans des assemblys et les utiliser comme des fichiers physiques lors de l'exécution.", "ThemingExplanation": "Le système de thématisation permet de développer votre application et vos modules indépendamment du thème en définissant un ensemble de bibliothèques et de mises en page de base communes, basées sur le dernier framework Bootstrap.", "DomainDrivenDesignInfrastructureExplanation": "Une infrastructure complète pour créer des applications en couches basées sur les modèles et principes de conception pilotée par domaine;", "Specification": "spécification", @@ -138,7 +138,7 @@ "DataTransferObject": "Objet de transfert de données", "AggregateRootEntity": "Racine agrégée, entité", "AutoRESTAPIsExplanation": "ABP peut configurer automatiquement vos services d'application en tant que contrôleurs d'API par convention.", - "DynamicClientProxiesExplanation": "Utilisez facilement vos API à partir de clients JavaScript et C#.", + "DynamicClientProxiesExplanation": "Utilisez facilement vos API à partir de clients JavaScript et C# .", "DistributedEventBusWithRabbitMQIntegrationExplanation": "Publiez et consommez facilement des événements distribués à l'aide du bus d'événements distribués intégré avec l'intégration RabbitMQ disponible.", "TestInfrastructureExplanation": "Le framework a été développé en pensant aux tests unitaires et d'intégration. Vous fournit des classes de base pour vous faciliter la tâche. Les modèles de démarrage sont pré-configurés pour les tests.", "AuditLoggingEntityHistoriesExplanation": "Journalisation d'audit intégrée pour les applications stratégiques. Requête, service, journalisation d'audit au niveau de la méthode et historiques d'entités avec des détails au niveau de la propriété.", @@ -155,7 +155,7 @@ "Code": "Code", "Result": "Résultat", "SeeTheDocumentForMoreInformation": "Consultez le {0} document pour plus d'informations", - "IndexPageHeroSection": " open source Application Web
Framework
pour asp.net core ", + "IndexPageHeroSection": "open sourceWeb Application
Framework
for asp.net core", "UiFramework": "Framework de l'interface utilisateur", "EmailAddress": "Adresse e-mail", "Mobile": "Mobile", @@ -169,13 +169,13 @@ "JoinTheABPCommunityInfo": "Impliquez-vous dans une communauté dynamique et devenez un contributeur au cadre ABP!", "AllArticles": "Tous les articles", "SubmitYourArticle": "Soumettez votre article", - "DynamicClientProxyDocument": "Consultez les documentations sur le proxy client dynamique pour JavaScript et C # .", + "DynamicClientProxyDocument": "Consultez les documentations sur le proxy client dynamique pour JavaScript et C#.", "EmailSMSAbstractionsDocument": "Consultez les documents e-mail et Envoi de SMS pour plus d'informations.", "CreateProjectWizard": "Cet assistant crée un nouveau projet à partir du modèle de démarrage qui est correctement configuré pour démarrer rapidement votre projet.", "TieredOption": "Crée une solution à plusieurs niveaux dans laquelle les couches API Web et Http sont physiquement séparées. Si elle n'est pas cochée, crée une solution en couches qui est moins complexe et adaptée à la plupart des scénarios.", "SeparateIdentityServerOption": "Sépare le côté serveur en deux applications: la première est pour le serveur d'identité et la seconde pour votre API HTTP côté serveur.", "UseslatestPreVersion": "Utilise la dernière version préliminaire", - "ReadTheDocumentation": " Lire La documentation ", + "ReadTheDocumentation": " Lire La documentation ", "Documentation": "Documentation", "GettingStartedTutorial": "Tutoriel de mise en route", "ApplicationDevelopmentTutorial": "Tutoriel de développement d'applications", diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/hi.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/hi.json index a7974d8658..7902f2410e 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/hi.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/hi.json @@ -40,9 +40,9 @@ "Theming": "थीम", "ThemingExplanationShort": "बूटस्ट्रैप-आधारित मानक UI थीम का उपयोग करें और अनुकूलित करें या अपना स्वयं का बनाएं।", "BootstrapTagHelpersDynamicForms": "बूटस्ट्रैप टैग हेल्पर्स और डायनामिक फॉर्म", - "BootstrapTagHelpersDynamicFormsExplanation": "बूटस्ट्रैप घटकों के दोहराए गए विवरणों को मैन्युअल रूप से लिखने के बजाय, इसे सरल बनाने और अंतरंगता का लाभ उठाने के लिए एबीपी के टैग सहायकों का उपयोग करें। डायनामिक फॉर्म टैग हेल्पर का उपयोग करके C # मॉडल के आधार पर जल्दी से UI फॉर्म का निर्माण करें।", + "BootstrapTagHelpersDynamicFormsExplanation": "बूटस्ट्रैप घटकों के दोहराए गए विवरणों को मैन्युअल रूप से लिखने के बजाय, इसे सरल बनाने और अंतरंगता का लाभ उठाने के लिए एबीपी के टैग सहायकों का उपयोग करें। डायनामिक फॉर्म टैग हेल्पर का उपयोग करके C# मॉडल के आधार पर जल्दी से UI फॉर्म का निर्माण करें।", "HTTPAPIsDynamicProxies": "HTTP एपीआई और डायनेमिक प्रॉक्सी", - "HTTPAPIsDynamicProxiesExplanation": "REST शैली HTTP API के रूप में एप्लिकेशन सेवाओं को स्वचालित रूप से उजागर करें, और उन्हें गतिशील जावास्क्रिप्ट और C # प्रॉक्सी के साथ उपभोग करें।", + "HTTPAPIsDynamicProxiesExplanation": "REST शैली HTTP API के रूप में एप्लिकेशन सेवाओं को स्वचालित रूप से उजागर करें, और उन्हें गतिशील जावास्क्रिप्ट और C# प्रॉक्सी के साथ उपभोग करें।", "CompleteArchitectureInfo": "बनाए रखने योग्य सॉफ़्टवेयर समाधान बनाने के लिए आधुनिक वास्तुकला।", "DomainDrivenDesignBasedLayeringModelExplanation": "डीडीडी आधारित स्तरित वास्तुकला को लागू करने और बनाए रखने योग्य कोड आधार बनाने में आपकी सहायता करता है।", "DomainDrivenDesignBasedLayeringModelExplanationCont": "DDD पैटर्न और सिद्धांतों के आधार पर आपके एप्लिकेशन को विकसित करने में मदद करने के लिए स्टार्टअप टेम्प्लेट, सार, आधार कक्षाएं, सेवाएं, प्रलेखन और मार्गदर्शिका प्रदान करता है।", @@ -70,7 +70,7 @@ "ConventionOverConfigurationExplanation": "ABP न्यूनतम या शून्य कॉन्फ़िगरेशन के साथ डिफ़ॉल्ट रूप से सामान्य अनुप्रयोग सम्मेलनों को लागू करता है।", "ConventionOverConfigurationExplanationList1": "ऑटो निर्भरता इंजेक्शन के लिए सेवाओं को पंजीकृत करता है।", "ConventionOverConfigurationExplanationList2": "नामकरण सम्मेलनों द्वारा HTTP APIs के रूप में अनुप्रयोग सेवाओं का प्रस्ताव है।", - "ConventionOverConfigurationExplanationList3": "C # और JavaScript के लिए गतिशील HTTP क्लाइंट प्रॉक्सी बनाता है।", + "ConventionOverConfigurationExplanationList3": " C# और JavaScript के लिए गतिशील HTTP क्लाइंट प्रॉक्सी बनाता है।", "ConventionOverConfigurationExplanationList4": "आपकी संस्थाओं के लिए डिफ़ॉल्ट रिपॉजिटरी प्रदान करता है।", "ConventionOverConfigurationExplanationList5": "वेब अनुरोध या एप्लिकेशन सेवा पद्धति के अनुसार कार्य की इकाई का प्रबंधन।", "ConventionOverConfigurationExplanationList6": "आपकी संस्थाओं के लिए ईवेंट बनाएं, अपडेट करें और हटाएं।", @@ -94,7 +94,7 @@ "DynamicForms": "गतिशील रूप", "BundlingMinification": "बंडलिंग और न्यूनतमकरण", "BackgroundJobs": "बैकग्राउंड जॉब्स", - "BackgroundJobsExplanation": "सरल कक्षाओं को परिभाषित करें ताकि पृष्ठभूमि में नौकरियों को निष्पादित किया जा सके। अंतर्निहित नौकरी प्रबंधक का उपयोग करें या अपने स्वयं के एकीकृत करें। Hangfire और RabbitMQ एकीकरण पहले से ही उपलब्ध हैं।", + "BackgroundJobsExplanation": "कतारबद्ध के रूप में पृष्ठभूमि में कार्य निष्पादित करने के लिए सरल वर्गों को परिभाषित करें। बिल्ट-इन जॉब मैनेजर का उपयोग करें या अपना खुद का एकीकृत करें। Hangfire और RabbitMQ एकीकरण पहले से ही उपलब्ध हैं।", "DDDInfrastructure": "डीडीडी इन्फ्रास्ट्रक्चर", "DomainDrivenDesignInfrastructure": "डोमेन संचालित डिज़ाइन इन्फ्रास्ट्रक्चर", "AutoRESTAPIs": "ऑटो रीस्ट एपीआई", @@ -104,7 +104,7 @@ "TestInfrastructure": "टेस्ट इन्फ्रास्ट्रक्चर", "AuditLoggingEntityHistories": "ऑडिट लॉगिंग और इकाई इतिहास", "ObjectToObjectMapping": "ऑब्जेक्ट टू ऑब्जेक्ट मैपिंग", - "ObjectToObjectMappingExplanation": "AutoMapper एकीकरण के साथ अमूर्तता के लिए ऑब्जेक्ट मैपिंग पर ऑब्जेक्ट ।", + "ObjectToObjectMappingExplanation": "ऑटोमैपर एकीकरण के साथ ऑब्जेक्ट टू ऑब्जेक्ट मैपिंग एब्स्ट्रैक्शन।", "EmailSMSAbstractions": "ईमेल और एसएमएस अंश", "EmailSMSAbstractionsWithTemplatingSupport": "ईमेल और एसएमएस अंशांकन समर्थन के साथ", "Localization": "स्थानीयकरण", @@ -121,13 +121,13 @@ "MultiTenancyExplanation3": "एकल डेटाबेस, डेटाबेस प्रति किरायेदार और हाइब्रिड दृष्टिकोण का समर्थन करता है।", "MultiTenancyExplanation4": "आप अपने व्यवसाय कोड पर ध्यान केंद्रित करते हैं और आपकी ओर से मल्टी-टेनेंसी को संभालने की रूपरेखा तैयार करते हैं।", "BootstrapTagHelpersExplanation": "बूटस्ट्रैप घटकों के दोहराए गए विवरणों को मैन्युअल रूप से लिखने के बजाय, इसे सरल बनाने और अंतरंगता का लाभ उठाने के लिए एबीपी के टैग सहायकों का उपयोग करें। जब भी आपको जरूरत हो आप बूटस्ट्रैप का इस्तेमाल जरूर कर सकते हैं।", - "DynamicFormsExplanation": "डायनामिक फॉर्म और इनपुट टैग हेल्पर्स मॉडल के रूप में C # क्लास से पूरा फॉर्म बना सकते हैं।", + "DynamicFormsExplanation": "डायनामिक फॉर्म और इनपुट टैग हेल्पर्स मॉडल के रूप में C# क्लास से पूरा फॉर्म बना सकते हैं।", "AuthenticationAuthorizationExplanation": "रिच प्रमाणीकरण और प्राधिकरण विकल्प ASP.NET कोर आइडेंटिटी और आइडेंटिटी सेवर 4 को एकीकृत करता है। एक एक्स्टेंसिबल और विस्तृत अनुमति प्रणाली प्रदान करता है।", "CrossCuttingConcernsExplanation": "इन सभी सामान्य चीज़ों को बार-बार लागू करने के लिए अपने आप को दोहराएं नहीं। अपने व्यवसाय कोड पर ध्यान दें और एबीपी को उन्हें सम्मेलनों द्वारा स्वचालित करें।", "DatabaseConnectionTransactionManagement": "डेटाबेस कनेक्शन और लेनदेन प्रबंधन", "CorrelationIdTracking": "सहसंबंध-आईडी ट्रैकिंग", "BundlingMinificationExplanation": "ABP एक सरल, गतिशील, शक्तिशाली, मॉड्यूलर और बिल्ट-इन बंडलिंग और मिनिफिकेशन सिस्टम प्रदान करता है।", - "VirtualFileSystemnExplanation": "वर्चुअल फ़ाइल सिस्टम उन फ़ाइलों को प्रबंधित करना संभव बनाता है जो फ़ाइल सिस्टम (डिस्क) पर भौतिक रूप से मौजूद नहीं हैं। यह मुख्य रूप से विधानसभाओं में (js, css, image, cshtml ...) फ़ाइलों को एम्बेड करने और रनटाइम के दौरान भौतिक फ़ाइलों की तरह उपयोग करने के लिए उपयोग किया जाता है।", + "VirtualFileSystemnExplanation": "वर्चुअल फ़ाइल सिस्टम उन फ़ाइलों को प्रबंधित करना संभव बनाता है जो फ़ाइल सिस्टम (डिस्क) पर भौतिक रूप से मौजूद नहीं हैं। यह मुख्य रूप से विधानसभाओं में (js, css, image, cshtml...) फ़ाइलों को एम्बेड करने और रनटाइम के दौरान भौतिक फ़ाइलों की तरह उपयोग करने के लिए उपयोग किया जाता है।", "ThemingExplanation": "थिमिंग सिस्टम नवीनतम बूटस्ट्रैप ढांचे के आधार पर, सामान्य आधार पुस्तकालयों और लेआउट के एक सेट को परिभाषित करके आपके एप्लिकेशन और मॉड्यूल विषय को विकसित करने की अनुमति देता है।", "DomainDrivenDesignInfrastructureExplanation": "डोमेन संचालित डिज़ाइन पैटर्न और सिद्धांतों के आधार पर स्तरित अनुप्रयोगों के निर्माण के लिए एक पूर्ण बुनियादी ढांचा;", "Specification": "विनिर्देश", @@ -154,8 +154,8 @@ "AndMore": "और अधिक...", "Code": "कोड", "Result": "परिणाम", - "SeeTheDocumentForMoreInformation": "अधिक जानकारी के लिए {0} दस्तावेज़ देखें", - "IndexPageHeroSection": "<स्पैन क्लास = \"फर्स्ट-लाइन शाइन\"> ओपन सोर्स <स्पैन क्लास = \"सेकंड-लाइन टेक्स्ट-अपरकेस\"> वेब एप्लिकेशन
फ्रेमवर्क asp.net core के लिए ", + "SeeTheDocumentForMoreInformation": "अधिक जानकारी के लिए {0} दस्तावेज़ देखें", + "IndexPageHeroSection": "open sourceWeb Application
Framework
for asp.net core", "UiFramework": "यूआई फ्रेमवर्क", "EmailAddress": "ईमेल पता", "Mobile": "मोबाइल", @@ -169,13 +169,13 @@ "JoinTheABPCommunityInfo": "एक जीवंत समुदाय के साथ शामिल हों और ABP फ्रेमवर्क में योगदानकर्ता बनें!", "AllArticles": "सभी चीज़ें", "SubmitYourArticle": "अपना लेख प्रस्तुत करें", - "DynamicClientProxyDocument": " JavaScript और C # के लिए डायनेमिक क्लाइंट प्रॉक्सी दस्तावेज़ देखें।", - "EmailSMSAbstractionsDocument": "अधिक जानकारी के लिए ईमेल करना और एसएमएस भेजने के दस्तावेज़ देखें।", + "DynamicClientProxyDocument": "JavaScript और C# के लिए गतिशील क्लाइंट प्रॉक्सी दस्तावेज़ देखें।", + "EmailSMSAbstractionsDocument": "अधिक जानकारी के लिए ईमेलिंग और एसएमएस भेजना दस्तावेज़ देखें।", "CreateProjectWizard": "यह विज़ार्ड स्टार्टअप टेम्प्लेट से एक नई परियोजना बनाता है जो आपकी परियोजना को शुरू करने के लिए ठीक से कॉन्फ़िगर किया गया है।", "TieredOption": "एक ऐसा समाधान तैयार करता है जहाँ वेब और Http API परतें शारीरिक रूप से अलग हो जाती हैं। यदि जांच नहीं की जाती है, तो एक स्तरित समाधान बनाता है जो कम जटिल है और अधिकांश परिदृश्यों के लिए उपयुक्त है।", "SeparateIdentityServerOption": "सर्वर एप्लिकेशन को दो अनुप्रयोगों में अलग करता है: पहला पहचान सर्वर के लिए है और दूसरा आपके सर्वर साइड HTTP एपीआई के लिए है।", "UseslatestPreVersion": "नवीनतम पूर्व-रिलीज़ संस्करण का उपयोग करता है", - "ReadTheDocumentation": "<स्पैन क्लास = \"टेक्स्ट-प्राइमरी\"> पढ़ें <स्पैन क्लास = \"टेक्स्ट-सक्सेस\"> डॉक्यूमेंटेशन ", + "ReadTheDocumentation": "पढ़ेंदस्तावेज़ीकरण", "Documentation": "प्रलेखन", "GettingStartedTutorial": "स्टार्टिंग ट्यूटोरियल", "ApplicationDevelopmentTutorial": "अनुप्रयोग विकास ट्यूटोरियल", @@ -183,7 +183,7 @@ "InstallABPCLIInfo": "एबीपी सीएलआई एबीपी ढांचे के साथ एक नया समाधान शुरू करने का सबसे तेज़ तरीका है। कमांड लाइन विंडो का उपयोग करके ABP CLI स्थापित करें:", "DifferentLevelOfNamespaces": "आप विभिन्न स्तरों के नामस्थानों का उपयोग कर सकते हैं; जैसे BookStore, Acme.BookStore या Acme.Retail.BookStore।", "ABPCLIExamplesInfo": " नया कमांड डेटाबेस प्रदाता के रूप में एंटिटी फ्रेमवर्क कोर के साथ स्तरित MVC एप्लिकेशन बनाता है। हालांकि, इसके पास अतिरिक्त विकल्प हैं। उदाहरण:", - "SeeCliDocumentForMoreInformation": "अधिक विकल्पों के लिए ABP CLI दस्तावेज़ देखें या ऊपर \"डायरेक्ट डाउनलोड\" टैब चुनें।", + "SeeCliDocumentForMoreInformation": "अधिक विकल्पों के लिए ABP CLI दस्तावेज़ देखें या ऊपर \"डायरेक्ट डाउनलोड\" टैब चुनें।", "Optional": "ऐच्छिक", "LocalFrameworkRef": "फ्रेमवर्क पैकेज के लिए स्थानीय प्रोजेक्ट संदर्भ रखें।", "BlobStoring": "ब्लॉब भंडारण", diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/it.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/it.json index e4cdc73bf2..6b56b785ca 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/it.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/it.json @@ -2,7 +2,7 @@ "culture": "it", "texts": { "GetStarted": "Inizia - Modelli di avvio", - "Create": "Creare", + "Create": "Crea", "NewProject": "Nuovo progetto", "DirectDownload": "Download diretto", "ProjectName": "Nome del progetto", @@ -18,35 +18,35 @@ "SeeDetails": "Vedi i dettagli", "AbpShortDescription": "ABP Framework è un'infrastruttura completa per creare applicazioni Web moderne seguendo le migliori pratiche e convenzioni di sviluppo software.", "SourceCodeUpper": "CODICE SORGENTE", - "LatestReleaseLogs": "Ultimi registri di rilascio", + "LatestReleaseLogs": "Ultimi note di rilascio", "Infrastructure": "Infrastruttura", "Architecture": "Architettura", "Modular": "Modulare", "DontRepeatYourself": "Non ripetere te stesso", - "DeveloperFocused": "Concentrato sullo sviluppatore", + "DeveloperFocused": "Incentrato sullo sviluppatore", "FullStackApplicationInfrastructure": "Infrastruttura applicativa full stack.", - "DomainDrivenDesign": "Progettazione basata sul dominio", + "DomainDrivenDesign": "Domain Driven Design", "DomainDrivenDesignExplanation": "Progettato e sviluppato sulla base di modelli e principi DDD. Fornisce un modello a più livelli per la tua applicazione.", "Authorization": "Autorizzazione", "AuthorizationExplanation": "Autorizzazione avanzata con utente, ruolo e sistema di autorizzazioni granulare. Basato sulla libreria Microsoft Identity.", "MultiTenancy": "Multi-Tenancy", "MultiTenancyExplanationShort": "Applicazioni SaaS rese facili! Multi-tenancy integrato dal database all'interfaccia utente.", - "CrossCuttingConcerns": "Preoccupazioni trasversali", + "CrossCuttingConcerns": "Questioni trasversali", "CrossCuttingConcernsExplanationShort": "Infrastruttura completa per autorizzazione, convalida, gestione delle eccezioni, memorizzazione nella cache, registrazione di audit, gestione delle transazioni e altro ancora.", "BuiltInBundlingMinification": "Raggruppamento e minimizzazione incorporati", "BuiltInBundlingMinificationExplanation": "Non è necessario utilizzare strumenti esterni per raggruppare e minimizzare. ABP offre un modo più semplice, dinamico, potente, modulare e integrato!", "VirtualFileSystem": "File system virtuale", - "VirtualFileSystemExplanation": "Incorpora viste, script, stili, immagini ... in pacchetti / librerie e riutilizzali in diverse applicazioni.", - "Theming": "Theming", + "VirtualFileSystemExplanation": "Incorpora viste, script, stili, immagini... in pacchetti / librerie e riutilizzali in diverse applicazioni.", + "Theming": "Personalizzazione tema", "ThemingExplanationShort": "Usa e personalizza il tema dell'interfaccia utente standard basato su bootstrap o creane uno tuo.", "BootstrapTagHelpersDynamicForms": "Bootstrap Tag Helpers e moduli dinamici", - "BootstrapTagHelpersDynamicFormsExplanation": "Invece di scrivere manualmente i dettagli ripetuti dei componenti bootstrap, usa gli helper tag di ABP per semplificarlo e sfruttare intellisense. Crea rapidamente moduli dell'interfaccia utente basati su un modello C # utilizzando l'helper tag del modulo dinamico.", + "BootstrapTagHelpersDynamicFormsExplanation": "Invece di scrivere manualmente i dettagli ripetuti dei componenti bootstrap, usa i tag helper di ABP per semplificarlo e sfruttare intellisense. Crea rapidamente moduli dell'interfaccia utente basati su un modello C# utilizzando l'helper tag del modulo dinamico.", "HTTPAPIsDynamicProxies": "API HTTP e proxy dinamici", - "HTTPAPIsDynamicProxiesExplanation": "Esponi automaticamente i servizi dell'applicazione come API HTTP in stile REST e utilizzali con JavaScript dinamici e proxy C #.", + "HTTPAPIsDynamicProxiesExplanation": "Esponi automaticamente i servizi dell'applicazione come API HTTP in stile REST e utilizzali con JavaScript dinamici e proxy C# .", "CompleteArchitectureInfo": "Architettura moderna per creare soluzioni software manutenibili.", "DomainDrivenDesignBasedLayeringModelExplanation": "Aiuta a implementare un'architettura a strati basata su DDD e a costruire una base di codice gestibile.", "DomainDrivenDesignBasedLayeringModelExplanationCont": "Fornisce modelli di avvio, astrazioni, classi di base, servizi, documentazione e guide per aiutarti a sviluppare la tua applicazione in base a modelli e principi DDD.", - "MicroserviceCompatibleModelExplanation": "Il framework principale ei moduli di pre-compilazione sono progettati pensando all'architettura dei microservizi.", + "MicroserviceCompatibleModelExplanation": "Il framework principale ed i moduli predefiniti sono progettati pensando all'architettura dei microservizi.", "MicroserviceCompatibleModelExplanationCont": "Fornisce infrastruttura, integrazioni, campioni e documentazione per implementare più facilmente soluzioni di microservizi, mentre non apporta ulteriore complessità se si desidera un'applicazione monolitica.", "ModularInfo": "ABP fornisce un sistema di moduli che consente di sviluppare moduli applicativi riutilizzabili, collegarsi agli eventi del ciclo di vita dell'applicazione ed esprimere le dipendenze tra le parti principali del sistema.", "PreBuiltModulesThemes": "Moduli e temi predefiniti", @@ -62,18 +62,18 @@ "Validation": "Validazione", "DatabaseConnection": "Connessione al database", "TransactionManagement": "Gestione delle transazioni", - "AuditLogging": "Registrazione di audit", + "AuditLogging": "Registri di controllo", "Caching": "Caching", "Multitenancy": "Multitenancy", "DataFiltering": "Filtraggio dei dati", - "ConventionOverConfiguration": "Convenzione sulla configurazione", + "ConventionOverConfiguration": "Configurazione su convenzioni", "ConventionOverConfigurationExplanation": "ABP implementa convenzioni di applicazioni comuni per impostazione predefinita con una configurazione minima o zero.", "ConventionOverConfigurationExplanationList1": "Registra automaticamente i servizi noti per l'inserimento delle dipendenze.", "ConventionOverConfigurationExplanationList2": "Espone i servizi dell'applicazione come API HTTP mediante convenzioni di denominazione.", - "ConventionOverConfigurationExplanationList3": "Crea proxy client HTTP dinamici per C # e JavaScript.", + "ConventionOverConfigurationExplanationList3": "Crea proxy client HTTP dinamici per C# e JavaScript.", "ConventionOverConfigurationExplanationList4": "Fornisce archivi predefiniti per le tue entità.", - "ConventionOverConfigurationExplanationList5": "Gestisce l'unità di lavoro per richiesta Web o metodo di servizio dell'applicazione.", - "ConventionOverConfigurationExplanationList6": "Pubblica creare, aggiornare ed eliminare eventi per le tue entità.", + "ConventionOverConfigurationExplanationList5": "Gestisce l'UnitofWork per richieste Web o per i metodi dei servizi dell'applicazione.", + "ConventionOverConfigurationExplanationList6": "Pubblica gli eventi creazione, aggiornamento ed eliminazione per le tue entità.", "BaseClasses": "Classi di base", "BaseClassesExplanation": "Classi di base predefinite per modelli di applicazioni comuni.", "DeveloperFocusedExplanation": "ABP è per gli sviluppatori.", @@ -91,16 +91,16 @@ "ABPCLI": "ABP CLI", "Modularity": "Modularità", "BootstrapTagHelpers": "Bootstrap Tag Helpers", - "DynamicForms": "Forme dinamiche", + "DynamicForms": "Moduli dinamici", "BundlingMinification": "Raggruppamento e minimizzazione", - "BackgroundJobs": "Lavori in background", - "BackgroundJobsExplanation": "Definire classi semplici per eseguire lavori in background come in coda. Usa il gestore dei lavori integrato o integra il tuo. Le integrazioni Hangfire e RabbitMQ sono già disponibili.", + "BackgroundJobs": "Operazioni in background", + "BackgroundJobsExplanation": "Definire classi semplici per eseguire Operazioni in background come in coda. Usa il gestore dei lavori integrato o integra il tuo. Le integrazioni Hangfire e RabbitMQ sono già disponibili.", "DDDInfrastructure": "Infrastruttura DDD", - "DomainDrivenDesignInfrastructure": "Infrastruttura di progettazione basata sul dominio", + "DomainDrivenDesignInfrastructure": "Infrastruttura Domain Driven Design", "AutoRESTAPIs": "API REST automatiche", "DynamicClientProxies": "Proxy client dinamici", - "DistributedEventBus": "Bus di eventi distribuito", - "DistributedEventBusWithRabbitMQIntegration": "Bus di eventi distribuito con integrazione RabbitMQ", + "DistributedEventBus": "Bus Eventi Distribuito", + "DistributedEventBusWithRabbitMQIntegration": "Bus Eventi Distribuito con integrazione RabbitMQ", "TestInfrastructure": "Infrastruttura di test", "AuditLoggingEntityHistories": "Registrazione di audit e cronologie delle entità", "ObjectToObjectMapping": "Mappatura da oggetto a oggetto", @@ -109,36 +109,36 @@ "EmailSMSAbstractionsWithTemplatingSupport": "Astrazioni tramite posta elettronica e SMS con supporto per i modelli", "Localization": "Localizzazione", "SettingManagement": "Gestione delle impostazioni", - "ExtensionMethods": "Metodi di estensione", - "ExtensionMethodsHelpers": "Metodi di estensione e aiutanti", + "ExtensionMethods": "Metodi estensione", + "ExtensionMethodsHelpers": "Metodi estensione e assistenti", "AspectOrientedProgramming": "Programmazione orientata agli aspetti", - "DependencyInjection": "Iniezione di dipendenza", + "DependencyInjection": "Iniezione delle dipendenze", "DependencyInjectionByConventions": "Inserimento delle dipendenze mediante convenzioni", "ABPCLIExplanation": "ABP CLI (Command Line Interface) è uno strumento a riga di comando per eseguire alcune operazioni comuni per soluzioni basate su ABP.", "ModularityExplanation": "ABP fornisce un'infrastruttura completa per creare i propri moduli applicativi che possono avere entità, servizi, integrazione di database, API, componenti dell'interfaccia utente e così via.", "MultiTenancyExplanation": "Il framework ABP non solo supporta lo sviluppo di applicazioni multi-tenant, ma rende anche il tuo codice per lo più inconsapevole del multi-tenancy.", - "MultiTenancyExplanation2": "Può determinare automaticamente l'inquilino corrente, isolare i dati di diversi inquilini l'uno dall'altro.", + "MultiTenancyExplanation2": "Può determinare automaticamente il tenant corrente, isolare i dati dei diversi tenant l'uno dall'altro.", "MultiTenancyExplanation3": "Supporta database singolo, database per tenant e approcci ibridi.", "MultiTenancyExplanation4": "Ti concentri sul tuo codice aziendale e lasci che il framework gestisca il multi-tenancy per tuo conto.", "BootstrapTagHelpersExplanation": "Invece di scrivere manualmente i dettagli ripetuti dei componenti bootstrap, usa gli helper tag di ABP per semplificarlo e sfruttare intellisense. Puoi sicuramente usare Bootstrap ogni volta che ne hai bisogno.", - "DynamicFormsExplanation": "Gli helper dei moduli dinamici e dei tag di input possono creare il modulo completo da una classe C # come modello.", + "DynamicFormsExplanation": "Gli helper dei moduli dinamici e dei tag di input possono creare il modulo completo da una classe C# come modello.", "AuthenticationAuthorizationExplanation": "Ricche opzioni di autenticazione e autorizzazione integrate in ASP.NET Core Identity & IdentityServer4. Fornisce un sistema di autorizzazione estensibile e dettagliato.", "CrossCuttingConcernsExplanation": "Non ripetere te stesso per implementare tutte queste cose comuni ancora e ancora. Concentrati sul tuo codice aziendale e lascia che ABP li automatizzi secondo le convenzioni.", "DatabaseConnectionTransactionManagement": "Connessione al database e gestione delle transazioni", "CorrelationIdTracking": "Monitoraggio dell'ID di correlazione", "BundlingMinificationExplanation": "ABP offre un sistema di raggruppamento e minimizzazione semplice, dinamico, potente, modulare e integrato.", - "VirtualFileSystemnExplanation": "Il Virtual File System consente di gestire file che non esistono fisicamente nel file system (disco). Viene utilizzato principalmente per incorporare file (js, css, immagine, cshtml ...) negli assembly e utilizzarli come file fisici in runtime.", + "VirtualFileSystemnExplanation": "Il Virtual File System consente di gestire file che non esistono fisicamente nel file system (disco). Viene utilizzato principalmente per incorporare file (js, css, immagine, cshtml...) negli assembly e utilizzarli come file fisici in runtime.", "ThemingExplanation": "Il sistema di temi consente di sviluppare applicazioni e moduli indipendentemente dal tema definendo un set di librerie e layout di base comuni, basati sul più recente framework Bootstrap.", "DomainDrivenDesignInfrastructureExplanation": "Un'infrastruttura completa per creare applicazioni a più livelli basate sui modelli e sui principi di Domain Driven Design;", - "Specification": "Specifica", + "Specification": "Specificazione", "Repository": "Repository", "DomainService": "Servizio di dominio", "ValueObject": "Oggetto valore", - "ApplicationService": "Servizio applicazioni", + "ApplicationService": "Servizio applicazione", "DataTransferObject": "Oggetto trasferimento dati", - "AggregateRootEntity": "Radice aggregata, entità", + "AggregateRootEntity": "Aggregate Root, entità", "AutoRESTAPIsExplanation": "ABP può configurare automaticamente i servizi dell'applicazione come controller API per convenzione.", - "DynamicClientProxiesExplanation": "Utilizza facilmente le tue API da client JavaScript e C #.", + "DynamicClientProxiesExplanation": "Utilizza facilmente le tue API da client JavaScript e C# .", "DistributedEventBusWithRabbitMQIntegrationExplanation": "Pubblica e utilizza facilmente eventi distribuiti utilizzando Distributed Event Bus integrato con integrazione RabbitMQ disponibile.", "TestInfrastructureExplanation": "Il framework è stato sviluppato in mente per test di unità e integrazione. Fornisce classi di base per renderlo più facile. I modelli di avvio vengono forniti preconfigurati per i test.", "AuditLoggingEntityHistoriesExplanation": "Registrazione di audit integrata per applicazioni business-critical. Richiesta, servizio, registrazione di controllo a livello di metodo e cronologie di entità con dettagli a livello di proprietà.", @@ -151,17 +151,17 @@ "DataFilteringExplanation": "Definisci e utilizza i filtri di dati che vengono applicati automaticamente quando esegui query sulle entità dal database. Quando si implementano interfacce semplici, vengono forniti filtri Soft Delete e MultiTenant.", "PublishEvents": "Pubblica eventi", "HandleEvents": "Gestisci eventi", - "AndMore": "e altro ancora ...", + "AndMore": "e altro ancora...", "Code": "Codice", "Result": "Risultato", "SeeTheDocumentForMoreInformation": "Consulta il {0} documento per ulteriori informazioni", - "IndexPageHeroSection": " open source Framework
applicazione web
per asp.net core ", + "IndexPageHeroSection": "open sourceFramework
applicazione web
per asp.net core", "UiFramework": "Framework dell'interfaccia utente", "EmailAddress": "Indirizzo e-mail", "Mobile": "Mobile", "ReactNative": "React Native", "Strong": "Forte", - "Complete": "Completare", + "Complete": "Completa", "BasedLayeringModel": "Modello di stratificazione basato", "Microservice": "Microservizio", "Compatible": "Compatibile", @@ -169,21 +169,21 @@ "JoinTheABPCommunityInfo": "Fatti coinvolgere da una vivace comunità e diventa un collaboratore di ABP Framework!", "AllArticles": "Tutti gli articoli", "SubmitYourArticle": "Invia il tuo articolo", - "DynamicClientProxyDocument": "Consulta la documentazione relativa al proxy del client dinamico per JavaScript e C # .", - "EmailSMSAbstractionsDocument": "Per ulteriori informazioni, consulta i documenti di invio di email e invio di SMS .", + "DynamicClientProxyDocument": "Consulta la documentazione relativa al proxy del client dinamico per JavaScript e C#.", + "EmailSMSAbstractionsDocument": "Per ulteriori informazioni, consulta i documenti di invio di email e invio di SMS.", "CreateProjectWizard": "Questa procedura guidata crea un nuovo progetto dal modello di avvio che è configurato correttamente per avviare rapidamente il progetto.", "TieredOption": "Crea una soluzione a più livelli in cui i livelli API Web e Http sono fisicamente separati. Se non selezionata, crea una soluzione a più livelli meno complessa e adatta alla maggior parte degli scenari.", "SeparateIdentityServerOption": "Separa il lato server in due applicazioni: la prima è per il server di identità e la seconda è per l'API HTTP lato server.", "UseslatestPreVersion": "Utilizza l'ultima versione pre-rilascio", - "ReadTheDocumentation": " Leggi La documentazione ", + "ReadTheDocumentation": "LeggiLa documentazione", "Documentation": "Documentazione", "GettingStartedTutorial": "Tutorial introduttivo", "ApplicationDevelopmentTutorial": "Tutorial sullo sviluppo di applicazioni", "TheStartupTemplate": "Il modello di avvio", "InstallABPCLIInfo": "ABP CLI è il modo più veloce per avviare una nuova soluzione con il framework ABP. Installa ABP CLI utilizzando una finestra della riga di comando:", "DifferentLevelOfNamespaces": "È possibile utilizzare diversi livelli di spazi dei nomi; per esempio. BookStore, Acme.BookStore o Acme.Retail.BookStore.", - "ABPCLIExamplesInfo": "Il comando nuovo crea un ' applicazione MVC a più livelli con Entity Framework Core come provider di database. Tuttavia, ha opzioni aggiuntive. Esempi:", - "SeeCliDocumentForMoreInformation": "Consulta il documento ABP CLI per ulteriori opzioni o seleziona la scheda \"Download diretto\" in alto.", + "ABPCLIExamplesInfo": "Il comando nuovo crea un 'applicazione MVC a più livelli con Entity Framework Core come provider di database. Tuttavia, ha opzioni aggiuntive. Esempi:", + "SeeCliDocumentForMoreInformation": "Consulta la documentazione ABP CLI per ulteriori opzioni o seleziona la scheda \"Download diretto\" in alto.", "Optional": "Opzionale", "LocalFrameworkRef": "Mantieni il riferimento al progetto locale per i pacchetti del framework.", "BlobStoring": "Archiviazione BLOB", @@ -191,12 +191,88 @@ "TextTemplating": "Modelli di testo", "TextTemplatingExplanation": "La creazione di modelli di testo viene utilizzata per eseguire il rendering dinamico dei contenuti in base a un modello e un modello (un oggetto dati). Ad esempio, puoi utilizzarlo per creare contenuti di posta elettronica dinamici con un modello predefinito.", "MultipleUIOptions": "Molteplici opzioni di interfaccia utente", - "MultipleDBOptions": "Più fornitori di database", + "MultipleDBOptions": "Più provider per database", "MultipleUIOptionsExplanation": "Il framework principale è progettato come indipendente dall'interfaccia utente e può funzionare con qualsiasi tipo di sistema di interfaccia utente, mentre sono disponibili più opzioni predefinite e integrate.", "MultipleDBOptionsExplanation": "Il framework può funzionare con qualsiasi fonte di dati, mentre i seguenti provider sono ufficialmente sviluppati e supportati;", "SelectLanguage": "Seleziona la lingua", "LatestArticleOnCommunity": "Ultimo articolo sulla comunità ABP", "Register": "Registrati", - "IsDownloadable": "È scaricabile" + "IsDownloadable": "È scaricabile", + "DatabaseOptions": "Opzioni Database", + "BackToPackagesPage": "Torna alla Pagina Pacchetti", + "HowToInstall": "Come Installare", + "SeeOnNpm": "Vedi su NPM", + "SeeOnNuget": "Vedi su Nuget", + "MVCGulpCommandExplanation": "Se stai usando UI MVC (Razor Pages), esegui il comando \"gulp\" dopo l'installazione del pacchetto.", + "UsingABPCLI": "Usa Abp CLI", + "WithoutABPCLI": "Senza ABP CLI", + "ABPCLIModuleDependency": "ABP CLI aggiunge automaticamente le dipendenze del modulo.", + "AddModuleDependency": "Quindi aggiunge le dipendenze del modulo", + "Packages": "Pacchetti", + "NugetPackages": "Pacchetti Nuget", + "NPMPackages": "Pacchetti NPM", + "SeeDocs": "Vedi Documentazione", + "None": "Nessuno", + "Application": "Applicazione", + "Module": "Moduli", + "PackageName": "Nome Pacchetto", + "LicenseURL": "URL Licenza", + "License": "Licenza", + "ProjectCreationSuccessMessage": "Il tuo progetto è stato creato con successo", + "HowToRunSolution": "Come Eseguire la Tua Soluzione?", + "GettingStartedMessage": "Guarda la documentazione su come iniziare per imparare a configurare ed eseguire la tua soluzione.", + "WebAppDevTutorial": "Tutorial Sviluppo Web App", + "WebAppDevTutorialMessage": "Guarda tutorial sviluppo applicazione web per un esempio di sviluppo passo passo.", + "CommunityArticles": "Articoli Comunità", + "CommunityArticleMessage": "Verifica la piattaforma Comunità ABP per leggere articoli utili su ABP Framework.", + "InvestigateSolutionDetails": "Vedi Dettagli della Soluzione", + "StartupTemplateDocumentationMessage": "Guarda il modello di avvio dell'applicazione per imparare l'architettura e la struttura della tua soluzione.", + "ClientSideDevelopment": "Sviluppo Lato Client", + "ClientSideDevelopmentDocumentationMessage": "Guarda il documento {0} per imparare i punti chiave dello sviluppo dell'interfaccia utente (lato client).", + "DatabaseProviderDocumentationMessage": "Guarda il documento {0} per imparare i punti chiave dello sviluppo dello strato database", + "ABPCommercialExplanationMessage": "ABP Commercial fornisce moduli premium, temi, strumenti e supporto per ABP Framework.", + "ImplementingDDD": "Implementazione Domain Driven Design", + "DDDBookExplanation": "Una guida pratica per l'implementazione del Domain Driven Design con ABP Framework.", + "Overview": "Panoramica", + "DDDBookPracticalGuide": "Questa a una guida pratica per implementare Domain Driven Design (DDD). Mentre i dettagli per l'implementazione sono basati sull'infrastruttura di ABP Framework, i concetti base, principi e modelli possono essere applicati a qualsiasi soluzione, anche se non è una soluzione .NET.", + "TableOfContents": "Sommario", + "IntroductionToImplementingDDD": "Introduzione all'implementazione del Domain Driven Design", + "WhatIsDDD": "Cos'è il Domain Driven Design?", + "Implementation": "Implementazione", + "TheBigPicture": "Il Quadro Generale", + "TheBuildingBlock": "Le Fondamenta", + "ExampleUseCase": "Esempio d'uso", + "DomainAndApplicationLogic": "Logica di Dominio e Logica Applicazione", + "Author": "Autore", + "Pages": "Pagine", + "PublishedOn": "Pubblicato il", + "FreeEBook": "E-Book Gratuito", + "Download": "Scarica", + "EBookSignInForDownload": "Accedi per scaricare l'e-book", + "SignIn": "Accedi", + "Or": "O", + "TellUsAboutYourself": "Dicci qualcosa di te", + "Name": "Nome", + "Surname": "Cognome", + "CompanyName": "Nome Azienda", + "DoYouAgreePrivacyPolicy": "Acconsento ai Termini e Condizioni ed alla Politica Privacy.", + "Free": "Gratuito", + "DDDEBook": "DDD E-Book", + "PracticalGuideForImplementingDDD": "Questo libro è una guida pratica per l'implementazione del Domain Driven Design con ABP Framework.", + "IntroducingDDD": "Introduzione al Domain Driven Design", + "DDDLayersAndCleanArchitecture": "DDD Strati e Architettura Pulita", + "LayeringOfADotnetSolution": "Stratificazione di una Soluzione .NET", + "ImplementingDDDBuildingBlocks": "Implementazione delle Fondamenta del DDD", + "DomainVsApplicationLogic": "Logica di Dominio contro Logica Applicazione", + "SamplesAndDiscussions": "Esempi e Discussioni", + "EmailNotValid": "Per favore inserisci un indirizzo email valido.", + "WeWillSendYouADownloadLink": "Un link per scaricare il libro e-book è stato spedito a {0}.
Controlla nella posta in arrivo, indesiderata o spam!", + "GoHome": "Vai alla Home", + "InvalidFormInputs": "Per favore, digita le informazioni valide specificate nel modulo", + "DDDBookEmailBody": "Grazie.
Per scaricare il tuo libro, clicca qui.", + "SubscribeToNewsletter": "Sottiscrivi la nostra newsletter per ricevere informazione su cosa accade nella Piattaforma ABP.IO, come nuove versioni, articoli, offerte ed altro ancora.", + "FirstEdition": "Prima Edizione", + "ThankYou": "Grazie!", + "CheckboxMandatory": "Devi mettere la spunta su questo per procedere!" } } \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/sk.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/sk.json new file mode 100644 index 0000000000..77fd1113fd --- /dev/null +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/sk.json @@ -0,0 +1,202 @@ +{ + "culture": "sk", + "texts": { + "GetStarted": "Začíname - štartovacie šablóny", + "Create": "Vytvoriť", + "NewProject": "Nový projekt", + "DirectDownload": "Priame stiahnutie", + "ProjectName": "Názov projektu", + "ProjectType": "Typ projektu", + "DatabaseProvider": "Poskytovateľ databázy", + "DatabaseManagementSystem": "Systém správy databáz", + "NTier": "N-úroveň", + "IncludeUserInterface": "Zahrnúť používateľské rozhranie", + "CreateNow": "Vytvoriť teraz", + "TheStartupProject": "Štartovací projekt", + "Tutorial": "Návod", + "UsingCLI": "Pomocou CLI", + "SeeDetails": "Pozri podrobnosti", + "AbpShortDescription": "ABP Framework je kompletná infraštruktúra na vytváranie moderných webových aplikácií podľa najlepších postupov a konvencií vývoja softvéru.", + "SourceCodeUpper": "ZDROJOVÝ KÓD", + "LatestReleaseLogs": "Najnovšie release logy", + "Infrastructure": "Infraštruktúra", + "Architecture": "Architektúra", + "Modular": "Modulárne", + "DontRepeatYourself": "Neopakujte sa", + "DeveloperFocused": "Zameranie na vývojárov", + "FullStackApplicationInfrastructure": "Kompletná infraštruktúra aplikácie.", + "DomainDrivenDesign": "Domain Driven Design", + "DomainDrivenDesignExplanation": "Navrhnuté a vyvinuté na základe vzorov a princípov DDD. Poskytuje vrstvený model pre vašu aplikáciu.", + "Authorization": "Autorizácia", + "AuthorizationExplanation": "Pokročilá autorizácia so systémom používateľov, rolí a jemných oprávnení. Postavené na knižnici Microsoft Identity.", + "MultiTenancy": "Multitenantnosť", + "MultiTenancyExplanationShort": "Jednoduché aplikácie SaaS! Integrovaná multitenantnosť od databázy po používateľské rozhranie.", + "CrossCuttingConcerns": "Cross Cutting Concerns", + "CrossCuttingConcernsExplanationShort": "Kompletná infraštruktúra pre autorizáciu, validáciu, spracovanie výnimiek, ukladanie do vyrovnávacej pamäte, audit logovania, správu transakcií a ďalšie.", + "BuiltInBundlingMinification": "Zabudovaná Bundling & Minification", + "BuiltInBundlingMinificationExplanation": "Nie je potrebné používať externé nástroje na Bundling & Minification. ABP ponúka zabudovaný jednoduchší, dynamický, výkonný a modulárny spôsob!", + "VirtualFileSystem": "Virtuálny systém súborov", + "VirtualFileSystemExplanation": "Vkladanie pohľadov, skriptov, štýlov, obrázkov... do balíkov/knižníc a ich opakované použitie v rôznych aplikáciách.", + "Theming": "Prispôsobenie témy", + "ThemingExplanationShort": "Použite a prispôsobte si štandardnú tému používateľského rozhrania založenú na bootstrape alebo si vytvorte vlastnú.", + "BootstrapTagHelpersDynamicForms": "Tag Bootstrap pomocníci & dynamické formuláre", + "BootstrapTagHelpersDynamicFormsExplanation": "Namiesto ručného písania opakujúcich sa detailov bootstrap komponentov použite ABP tags pomocníkov pre zjednodušenie a výhody intellisense. Vytvárajte formuláre používateľského rozhrania na základe C# modelu použitím dynamického form tag pomocníka.", + "HTTPAPIsDynamicProxies": "HTTP APIs & Dynamic Proxies", + "HTTPAPIsDynamicProxiesExplanation": "Automaticky poskytuje applikačné rozhranie formou REST a HTTP APIs a spracuváva ich pomocou dynamických JavaScriptov and C# proxies.", + "CompleteArchitectureInfo": "Moderná architektúra na vytváranie udržateľných softvérových riešení.", + "DomainDrivenDesignBasedLayeringModelExplanation": "Pomôže vám implementovať DDD architektúru založenú na vrstvách a vytvoriť udržateľnú kódovú základňu.", + "DomainDrivenDesignBasedLayeringModelExplanationCont": "Poskytuje štartovacie šablóny, abstrakcie, základné triedy, servicies, dokumentáciu a príručky, ktoré vám pomôžu pri vývoji aplikácie založenej na DDD patteroch & princípoch.", + "MicroserviceCompatibleModelExplanation": "Core framework a predpripravené moduly sú navrhnuté s ohľadom na architektúru mikroslužieb.", + "MicroserviceCompatibleModelExplanationCont": "Poskytuje infraštruktúru, integrácie, vzorky a dokumentáciu na jednoduchšiu implementáciu mikroslužieb, pri monolitickej aplikácii žiadne zložité riešenia.", + "ModularInfo": "ABP poskytuje systém modulov, ktorý umožňuje vyvíjať opakovane použiteľné moduly aplikácie, prepojiť ich s udalosťami životného cyklu aplikácie a vyjadriť závislosti medzi základnými časťami systému.", + "PreBuiltModulesThemes": "Vopred vytvorené moduly a témy", + "PreBuiltModulesThemesExplanation": "Open source a komerčné moduly & témy sú pripravené na použitie vo vašej firemnej aplikácii.", + "NuGetNPMPackages": "Balíky NuGet a NPM balíky", + "NuGetNPMPackagesExplanation": "Distribuované ako balíky NuGet a NPM. Jednoduchá inštalácia a aktualizácia.", + "ExtensibleReplaceable": "Rozšíriteľné/zameniteľné", + "ExtensibleReplaceableExplanation": "Všetky služby a moduly sú navrhnuté s ohľadom na rozšíriteľnosť. Môžete nahradiť služby, stránky, štýly a komponenty.", + "CrossCuttingConcernsExplanation2": "Udržujte svoju kódovú základňu menšiu, aby ste sa mohli sústrediť na kód, ktorý je špecifický pre váš biznis.", + "CrossCuttingConcernsExplanation3": "Nemíňajte čas implementáciou spoločných aplikačných požiadaviek vo viacerých projektoch.", + "AuthenticationAuthorization": "Overovanie a autorizácia", + "ExceptionHandling": "Spracovanie výnimiek", + "Validation": "Overovanie", + "DatabaseConnection": "Pripojenie k databáze", + "TransactionManagement": "Správa transakcií", + "AuditLogging": "Audit logy", + "Caching": "Ukladanie do vyrovnávacej pamäte", + "Multitenancy": "Multitenancia", + "DataFiltering": "Filtrovanie údajov", + "ConventionOverConfiguration": "Konvencia nad konfiguráciou", + "ConventionOverConfigurationExplanation": "ABP implementuje bežné aplikačné konvencie v predvolenom nastavení s minimálnou alebo nulovou konfiguráciou.", + "ConventionOverConfigurationExplanationList1": "Automaticky registruje známe services do dependecy injection.", + "ConventionOverConfigurationExplanationList2": "Poskytuje aplikačné rozhranie formou HTTP API podľa naming conventions.", + "ConventionOverConfigurationExplanationList3": "Vytvára dynamické HTTP client proxies pre jazyky C# a JavaScript.", + "ConventionOverConfigurationExplanationList4": "Poskytuje predvolené úložiská pre vaše entity.", + "ConventionOverConfigurationExplanationList5": "Spravuje jednotku práce na webovú požiadavku alebo metódu aplikačnej služby.", + "ConventionOverConfigurationExplanationList6": "Publikuje vytváranie, aktualizovanie & vymazanie udalostí pre vaše entity.", + "BaseClasses": "Základné triedy", + "BaseClassesExplanation": "Predpripravené základné triedy pre bežné aplikačné patterny.", + "DeveloperFocusedExplanation": "ABP je pre vývojárov.", + "DeveloperFocusedExplanationCont": "Jeho cieľom je zjednodušiť každodenný vývoj softvéru a zároveň vás neobmedzovať pri písaní nízkoúrovňového kódu.", + "SeeAllFeatures": "Pozrite si všetky funkcie", + "CLI_CommandLineInterface": "CLI (rozhranie príkazového riadka)", + "CLI_CommandLineInterfaceExplanation": "Obsahuje CLI, ktoré vám pomôže automatizovať vytváranie nových projektov a pridávanie nových modulov.", + "StartupTemplates": "Štartovacie šablóny", + "StartupTemplatesExplanation": "Rôzne štartovacie šablóny poskytujú plne nakonfigurované riešenie na začatie vývoja.", + "BasedOnFamiliarTools": "Na základe známych nástrojov", + "BasedOnFamiliarToolsExplanation": "Integrované a postavené na obľúbených nástrojoch. Nízka krivka učenia, jednoduché prispôsobenie, pohodlný vývoj.", + "ORMIndependent": "Nezávislé ORM", + "ORMIndependentExplanation": "Core frameworku je ORM/databázovo nezávislý a môže pracovať s akýmkoľvek zdrojom údajov. Entity Framework Core a MongoDB poskytovatelia sú už k dispozícii.", + "Features": "Preskúmajte funkcie ABP frameworku", + "ABPCLI": "ABP CLI", + "Modularity": "Modularita", + "BootstrapTagHelpers": "Bootstrap Tag pomocník", + "DynamicForms": "Dynamické formuláre", + "BundlingMinification": "Bundling & Minification", + "BackgroundJobs": "Automatické úlohy na pozadí", + "BackgroundJobsExplanation": "Definujte jednoduché triedy na vykonávanie automatických úloh na pozadí. Použite vstavaného správcu úloh alebo integrujte vlastného. Hangfire & RabbitMQ integrácie sú už k dispozícii.", + "DDDInfrastructure": "DDD infraštruktúra", + "DomainDrivenDesignInfrastructure": "Domain Driven Design infraštruktúra", + "AutoRESTAPIs": "Auto Rest APIs", + "DynamicClientProxies": "Dynamic Client Proxies", + "DistributedEventBus": "Distribuovaná zbernica udalostí", + "DistributedEventBusWithRabbitMQIntegration": "Distribuovaná zbernica udalostí s integráciou RabbitMQ", + "TestInfrastructure": "Testovacia infraštruktúra", + "AuditLoggingEntityHistories": "Audit logovanie & história entít", + "ObjectToObjectMapping": "Mapovanie object to object", + "ObjectToObjectMappingExplanation": "Mapovanie objekt to object abstrakcia s integráciou AutoMapper.", + "EmailSMSAbstractions": "E-mailové a SMS abstrakcie", + "EmailSMSAbstractionsWithTemplatingSupport": "E-mailové a SMS abstrakcie s podporou šablón", + "Localization": "Lokalizácia", + "SettingManagement": "Manažment nastavenia", + "ExtensionMethods": "Metódy rozšírenia", + "ExtensionMethodsHelpers": "Rozširujúce metódy & pomocníci", + "AspectOrientedProgramming": "Aspektovo orientované programovanie", + "DependencyInjection": "Vstrebávanie závislostí", + "DependencyInjectionByConventions": "Vstrebávanie závislostí podľa konvencií", + "ABPCLIExplanation": "ABP CLI (Command Line Interface) je nástroj príkazového riadka na vykonávanie niektorých bežných operácií pre riešenia založené na ABP.", + "ModularityExplanation": "ABP poskytuje kompletnú infraštruktúru na vytváranie vlastných aplikačných modulov, ktoré môžu obsahovať entity, služby, integráciu databáz, API, komponenty používateľského rozhrania atď.", + "MultiTenancyExplanation": "ABP framework nielenže podporuje vývoj multitenantných aplikácií, ale aj váš kód robí nezávislým od multitenancie.", + "MultiTenancyExplanation2": "Dokáže automaticky určiť aktuálneho tenanta, osobitne izolovať údaje jednotlivých tenantov.", + "MultiTenancyExplanation3": "Podporuje jednu databázu, databázu podľa tentanta a hybridné prístupy.", + "MultiTenancyExplanation4": "Sústreďte sa na svoj biznis kód a nechajte framework, aby sa vo vašom mene postaral o multitenantnosť.", + "BootstrapTagHelpersExplanation": "Namiesto ručného písania opakujúcich sa bootstrap komponentov, použite ABP tag pomocníka na zjednodušenie a využitie výhod intellisense. Bootstrap určite využijete vždy, keď ho budete potrebovať.", + "DynamicFormsExplanation": "Dynamické formuláre & vstup Tag pomocníka môžu vytvoriť kompletný formulár z C# triedy ako modelu.", + "AuthenticationAuthorizationExplanation": "Bohaté možnosti overovania & autorizácie integrované do ASP.NET Core Identity & IdentityServer4. Poskytuje rozšíriteľný a podrobný systém oprávnení.", + "CrossCuttingConcernsExplanation": "Neopakujte sa pri implementácii všetkých týchto bežných vecí znova a znova. Zamerajte sa na svoj biznis kód a nechajte ich automatizovať pomocou konvencií.", + "DatabaseConnectionTransactionManagement": "Pripojenie k databáze & správa transakcií", + "CorrelationIdTracking": "Sledovanie korelačnej identifikácie", + "BundlingMinificationExplanation": "ABP ponúka jednoduchý, dynamický, výkonný, modulárny a zabudovaný bundling & minification system.", + "VirtualFileSystemnExplanation": "Virtuálny sytém súborov umožňuje spravovať súbory, ktoré fyzicky neexistujú v v systéme súborov(na disku). Používa sa najmä na vkladanie (js, css, image, cshtml...) súborov do zostáv a ich používanie ako fyzických súborov za behu.", + "ThemingExplanation": "Systém vytvárania tém umožňuje vyvíjať aplikácie a moduly nezávisle od témy definovaním sady spoločných základných knižníc a layoutov na základe najnovšieho Bootstrap frameworku.", + "DomainDrivenDesignInfrastructureExplanation": "Kompletná infraštruktúra na vytváranie viacvrstvových aplikácií na základe patternov & princípov Domain Driven Design;", + "Specification": "Špecifikácia", + "Repository": "Úložisko", + "DomainService": "Doménová služba", + "ValueObject": "Objektová hodnota", + "ApplicationService": "Aplikačná služba", + "DataTransferObject": "Objekt prenosu údajov", + "AggregateRootEntity": "Aggregate Root, Entity", + "AutoRESTAPIsExplanation": "ABP môže automaticky konfigurovať vaše aplikačné služby ako API Controllers podľa konvencie.", + "DynamicClientProxiesExplanation": "Jednoduché používanie API rozhraní v jazykoch JavaScript a C#.", + "DistributedEventBusWithRabbitMQIntegrationExplanation": "Jednoduché publikovanie a spracuvávanie distribuovaných udalostí pomocou integrovanej zbernice distribuovaných udalostí s dostupnou RabbitMQ integráciou.", + "TestInfrastructureExplanation": "Tento framework bol vyvinutý s ohľadom na unit & integračné testovanie. Na uľahčenie vám poskytuje základné triedy. Štartovacie šablóny sa dodávajú predkonfigurované na testovanie.", + "AuditLoggingEntityHistoriesExplanation": "Zabudované auditové logy pre kritické biznis aplikácie. Audit logovanie, služby, metód a história entít s podrobnosťami na úrovni vlastností.", + "EmailSMSAbstractionsWithTemplatingSupportExplanation": "Abstrakcie IEmailSender a ISmsSender oddeľujú logiku aplikácie od infraštruktúry. Pokročilý systém e-mailových šablón umožňuje vytvárať lokalizované e-mailové šablóny a použiť ich, kedykoľvek je to potrebné.", + "LocalizationExplanation": "Lokalizačný systém umožňuje vytvárať zdroje v jednoduchých JSON súboroch a používať ich na lokalizáciu používateľského rozhrania. Podporuje pokročilé scenáre, ako je dedenie, rozšírenia a integrácia JavaScriptu, pričom je plne kompatibilný s AspNet Core lokalizačným systémom.", + "SettingManagementExplanation": "Definujte nastavenia pre svoju aplikáciu a získajte hodnoty počas behu na základe aktuálnej konfigurácie, tenanta a používateľa.", + "ExtensionMethodsHelpersExplanation": "Neopakujte sa ani pri triviálnych častiach kódu. Vďaka rozšíreniam a pomocníkom pre štandardné typy je váš kód oveľa prehľadnejší a jednoduchší na písanie.", + "AspectOrientedProgrammingExplanation": "Poskytuje pohodlnú infraštruktúru na vytváranie dynamických proxy a implementáciu aspektovo orientovaného programovania. Zachyťte akúkoľvek triedu a vykonajte svoj kód pred a po každom vykonaní metódy.", + "DependencyInjectionByConventionsExplanation": "Nie je potrebné ručne registrovať triedy do dependency injection. Automaticky registruje bežné typy služieb podľa konvencie. Pre ostatné typy služieb môžete použiť rozhrania a atribúty, aby ste to mali jednoduchšie a na jednom mieste.", + "DataFilteringExplanation": "Definujte a používajte dátové filtre, ktoré sa automaticky aplikujú pri vyhľadávaní entít z databázy. Soft Delete & multitenantné filtre sú k dispozícii už v základnej výbave, keď implementujete jednoduché rozhrania.", + "PublishEvents": "Publikovať udalosti", + "HandleEvents": "Spracovanie udalostí", + "AndMore": "a ďalšie...", + "Code": "Kód", + "Result": "Výsledok", + "SeeTheDocumentForMoreInformation": "Viac informácií nájdete v dokumente {0}", + "IndexPageHeroSection": "open sourceWeb Application
Framework
pre asp.net core", + "UiFramework": "UI framework", + "EmailAddress": "E-mailová adresa", + "Mobile": "Mobilný", + "ReactNative": "React Native", + "Strong": "Silný", + "Complete": "Kompletné", + "BasedLayeringModel": "Základný model vrstvenia", + "Microservice": "Mikroslužba", + "Compatible": "Kompatibilné", + "MeeTTheABPCommunityInfo": "Naším poslaním je vytvoriť prostredie, v ktorom si vývojári môžu navzájom pomáhať článkami, návodmi, prípadovými štúdiami atď. a stretávať sa s podobne zmýšľajúcimi ľuďmi.", + "JoinTheABPCommunityInfo": "Zapojte sa do živej komunity a staňte sa prispievateľom do ABP frameworku!", + "AllArticles": "Všetky články", + "SubmitYourArticle": "Odoslať článok", + "DynamicClientProxyDocument": "Pozrite si dokumentáciu dynamického proxy klienta pre JavaScript a C#.", + "EmailSMSAbstractionsDocument": "Viac informácií nájdete v dokumentoch posielanie e-mailov a posielanie SMS.", + "CreateProjectWizard": "Tento sprievodca vytvorí nový projekt zo štartovacej šablóny, ktorá je správne nakonfigurovaná na začatie vášho projektu.", + "TieredOption": "Vytvára viacúrovňové riešenie, v ktorom sú Web a Http API vrstvy fyzicky oddelené. Ak nie je checkbox začiarknutý, vytvorí vrstvené riešenie, ktoré je menej zložité a vhodné pre väčšinu scenárov.", + "SeparateIdentityServerOption": "Rozdeľuje stranu servera na dve aplikácie: Prvá je určená pre identity server a druhá pre vaše HTTP API na strane servera.", + "UseslatestPreVersion": "Používa najnovšiu predprípravnú verziu", + "ReadTheDocumentation": "Prečítajte siDokumentáciu", + "Documentation": "Dokumentácia", + "GettingStartedTutorial": "Začíname s návodom", + "ApplicationDevelopmentTutorial": "Návod Vývoj aplikácií", + "TheStartupTemplate": "Štartovacia šablóna", + "InstallABPCLIInfo": "ABP CLI je najrýchlejší spôsob, ako začať s novým riešením v ABP frameworku. Nainštalujte ABP CLI pomocou okna príkazového riadka:", + "DifferentLevelOfNamespaces": "Môžete použiť rôzne úrovne menných priestorov, napr. BookStore, Acme.BookStore alebo Acme.Retail.BookStore.", + "ABPCLIExamplesInfo": "Príkaz new vytvorí vrstvovú aplikáciu MVC s Entity Framework Core ako poskytovateľom databázy. Má však ďalšie možnosti. Príklady:", + "SeeCliDocumentForMoreInformation": "Viac možností nájdete v dokumente CLIABP alebo vyberte vyššie uvedenú kartu \"Priame stiahnutie\".", + "Optional": "Voliteľné", + "LocalFrameworkRef": "V rámci frameworkových balíkov dodržiavajte lokálne projektové referencie.", + "BlobStoring": "BLOB Storing", + "BlobStoringExplanation": "BLOB storing systém poskytuje abstrakciu na prácu s BLOBmi. ABP poskytuje niekoľko predpripravených integrácií poskytovateľov úložísk (Azure, AWS, File System, Database atď.), ktoré môžete jednoducho použiť vo svojich aplikáciách.", + "TextTemplating": "Šablónovanie textu", + "TextTemplatingExplanation": "Šablónovanie textu sa používa na dynamické vykresľovanie obsahu na základe šablóny a modelu (dátového objektu). Môžete ho napríklad použiť na vytvorenie dynamického obsahu e-mailu s vopred vytvorenou šablónou.", + "MultipleUIOptions": "Viacero možností používateľského rozhrania", + "MultipleDBOptions": "Viacerí poskytovatelia databáz", + "MultipleUIOptionsExplanation": "Framework core je navrhnuté ako nezávislé od používateľského rozhrania a môže pracovať s akýmkoľvek typom systému používateľského rozhrania, pričom je k dispozícii viacero predpripravených a integrovaných možností.", + "MultipleDBOptionsExplanation": "Framework môže pracovať s ľubovoľným zdrojom údajov, pričom oficiálne sú vyvinuté a podporované nasledujúce poskytovatelia;", + "SelectLanguage": "Výber jazyka", + "LatestArticleOnCommunity": "Najnovší článok v ABP komunite", + "Register": "Registrácia", + "IsDownloadable": "Je možné ho stiahnuť" + } +} \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/tr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/tr.json index 84a0173273..0d02300061 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/tr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/tr.json @@ -218,8 +218,6 @@ "Surname": "Soyad", "CompanyName": "Şirket adı", "DoYouAgreePrivacyPolicy": "Şartlar & Koşulları ve Gizlilik Politikasını kabul ediyorum.", - "VolosoftMarketingInformationMessage": "İşletmeler ve Organizasyonlar için çözümler ve diğer Volosoft ürün ve hizmetleri hakkında bilgi, ipucu ve teklifler almak istiyorum.", - "VolosoftSharingInformationMessage": "Volosoft'un ürünlerimi seçkin ortaklarla paylaşmasını kabul ediyorum, böylece ilgili ortakların ürünleri ve hizmetleri hakkında ilgili bilgileri alabilirim.", "Free": "Ücretsiz", "DDDEBook": "DDD E-kitap", "PracticalGuideForImplementingDDD": "Bu kitap ABP Framework ile birlikte Domain Driven Design'ın uygulanması için pratik bir kılavuz görevi görür.", @@ -233,6 +231,8 @@ "WeWillSendYouADownloadLink": "{0} adresine email gönderilmiştir.", "GoHome": "Anasayfaya Dön", "InvalidFormInputs": "Lütfen formda belirtilen geçerli bilgileri yazınız.", - "DDDBookEmailBody": "Teşekkürler.
Kitabı indirmek için, buraya tıklayınız." + "DDDBookEmailBody": "Teşekkürler.
Kitabı indirmek için, buraya tıklayınız.", + "SubscribeToNewsletter": "ABP.IO Platform'u ile ilgili yeni haberler, makaleler, teklifler ve daha fazlası gibi gelişmeler hakkında bilgi almak için bültene abone olun.", + "FirstEdition": "İlk Baskı" } } \ No newline at end of file diff --git a/build/common.ps1 b/build/common.ps1 index 0afe55f120..172e22ad6a 100644 --- a/build/common.ps1 +++ b/build/common.ps1 @@ -7,6 +7,7 @@ $rootFolder = (Get-Item -Path "./" -Verbose).FullName # List of solutions used only in development mode $solutionPaths = @( "../framework", + "../modules/basic-theme", "../modules/users", "../modules/permission-management", "../modules/setting-management", diff --git a/common.props b/common.props index a62f5b67b2..6bad8e8068 100644 --- a/common.props +++ b/common.props @@ -1,7 +1,7 @@ latest - 4.4.0 + 5.0.0 $(NoWarn);CS1591;CS0436 https://abp.io/assets/abp_nupkg.png https://abp.io/ diff --git a/docs/en/Apps/VoloDocs.md b/docs/en/Apps/VoloDocs.md index dbbd3712d2..f85aa78bbd 100644 --- a/docs/en/Apps/VoloDocs.md +++ b/docs/en/Apps/VoloDocs.md @@ -49,7 +49,7 @@ When you extract the `VoloDocs.*.zip` file, you will see a `Web` folder and a `M - ### Configuring Website - Go to `Web` folder in your VoloDocs directory. Open `appsettings.json` in your text editor. Set your connection string (same as in the `Migrator`'s `appsettings.json`). Set `title` of your website. This will be written on the left-upper corner of your website. That's it! Now you can publish your website. + Go to `Web` folder in your VoloDocs directory. Open `appsettings.json` in your text editor. Set your connection string (same as in the `Migrator`'s `appsettings.json`). That's it! Now you can publish your website. If you want to run @@ -127,6 +127,8 @@ When you extract the `VoloDocs.*.zip` file, you will see a `Web` folder and a `M * **Navigation document name**: `docs-nav.json` ([see the sample navigation](https://github.com/abpframework/abp/blob/master/docs/en/docs-nav.json)) + * **Parameters Document Name**: `docs-params.json` ([see the sample parameters](https://github.com/abpframework/abp/blob/dev/docs/en/docs-params.json)) + * **Minimum version**: *leave empty* *(hides the previous versions)* * **Main web site URL**: `/` @@ -139,7 +141,11 @@ When you extract the `VoloDocs.*.zip` file, you will see a `Web` folder and a `M * **GitHub user agent**: [see how to learn your GitHub username](#learn-your-github-username) - ![Creating a new project](../images/docs-create-project.jpg) + * **GitHub version provider source**: `Releases` (other option is `Branches`) + + * **Version branch prefix**: leave empty () + + ![Creating a new project](../images/docs-create-project-v4.4.0.png) ##### Retrieving GitHub Access Token diff --git a/docs/en/Best-Practices/Domain-Services.md b/docs/en/Best-Practices/Domain-Services.md index 4ca2115283..f8b7c31797 100644 --- a/docs/en/Best-Practices/Domain-Services.md +++ b/docs/en/Best-Practices/Domain-Services.md @@ -1,3 +1,4 @@ ## Domain Services Best Practices & Conventions -TODO \ No newline at end of file +> **This document is not ready yet. Please see the [Domain Services](../Domain-Services.md) document.** + diff --git a/docs/en/Best-Practices/Index.md b/docs/en/Best-Practices/Index.md index f3f626ca63..5c21b5b9ec 100644 --- a/docs/en/Best-Practices/Index.md +++ b/docs/en/Best-Practices/Index.md @@ -25,3 +25,6 @@ Also, this guide is mostly usable for general **application development**. * [Entity Framework Core Integration](Entity-Framework-Core-Integration.md) * [MongoDB Integration](MongoDB-Integration.md) +## See Also + +* [E-Book: Implementing Domain Driven Design](https://abp.io/books/implementing-domain-driven-design) diff --git a/docs/en/Blog-Posts/2021-04-05 CmsKit/POST.md b/docs/en/Blog-Posts/2021-04-05 CmsKit/POST.md index 740adb96c7..59bd172f47 100644 --- a/docs/en/Blog-Posts/2021-04-05 CmsKit/POST.md +++ b/docs/en/Blog-Posts/2021-04-05 CmsKit/POST.md @@ -28,13 +28,13 @@ If you're using the open-source version or adding the module to an existing proj You can run the following command to add the module to an open-source solution. ```bash -abp add-module Volo.CmsKit +abp add-module Volo.CmsKit --skip-db-migrations true ``` If you're a commercial user, run the following command to install the pro version of the module. ```bash -abp add-module Volo.CmsKit.Pro +abp add-module Volo.CmsKit.Pro --skip-db-migrations true ``` After adding the module to the solution, you need to configure features. The CMS Kit module uses the [global feature](https://docs.abp.io/en/abp/latest/Global-Features) system for all features. To enable the features in the CMS Kit module, open the `GlobalFeatureConfigurator` class in the `Domain.Shared` project and place the following code to the `Configure` method to enable all features in the CMS kit module. diff --git a/docs/en/Blog-Posts/2021-05-24-LeptonX/POST.md b/docs/en/Blog-Posts/2021-05-24-LeptonX/POST.md new file mode 100644 index 0000000000..c74a2cb4bd --- /dev/null +++ b/docs/en/Blog-Posts/2021-05-24-LeptonX/POST.md @@ -0,0 +1,69 @@ +# Introducing the Lepton Theme Next Generation! + +We are delighted and proud to announce that the next generation of [Lepton Theme](https://leptontheme.com/) which will be referred to as LeptonX is on the way. + +At Volosoft, we care about developer experience as much as user experience. Thus, LeptonX is following the latest trends for a better user experience and provides highly customizable features that will be easy to use. Our APIs follow a strict guideline to be easy-to-use, intuitive, extendable, and replaceable. In short, we'd like LeptonX to grow into such a platform that developers (not just ABP developers) love to build their applications on top of, and end-users love to interact with. + +The initial version has been built upon Bootstrap v4. Bootstrap v5 has been recently published and the ecosystem has not caught up yet. In addition, ABP packages are already dependent on Bootstrap v4, it will be easy to switch from the [Basic Theme](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Basic-Theme) or from the [Lepton Theme](https://commercial.abp.io/themes) to the LeptonX. + +We believe that the bigger the community is, the better the framework turns out to be. As we have done in our other products, we will release a free-to-use version of LeptonX, a.k.a LeptonX-lite which will contain most of the basic features. With LeptonX-lite, free ABP [application startup template](https://docs.abp.io/en/abp/latest/Startup-Templates/Application)s will come with a production-ready theme, and existing applications will be able to switch from the Basic Theme to the LeptonX-lite with ease. + +## Highlights + +In this section, I want to highlight some of the LeptonX features. + +### Better Mobile UX + +When people talk about Responsive Web Applications, they usually refer to a side menu or top menu that shrinks down into a hamburger menu which does not provide a good user experience for mobile users. + +On the other hand, widely used native mobile applications usually employ a tab menu stick to the bottom of the screen. People have become acquainted with this type of experience. With this UX in mind, menus in the `LeptonX` theme switches to the tab menu on the mobile resolution as opposed to the hamburger menu. + +![Three iPhones showing LeptonX theme for mobile resolution in dark, dim, and light themes](./mobiles.png) +![Three iPads showing LeptonX theme for tablet resolution in dark, dim and light themes](./tablets.png) + +### Theme Build System + +Following the latest trends, LeptonX comes with three built-in theme styles; `dark`, `dim`, `light`. +A theme builder system (written in SASS) is provided with the LeptonX which you can run with your brand colors and create your own theme style as well. + +LeptonX is built on top of CSS variables that enable runtime theme building. In the future, we may release a color picker that can create custom themes. Each user can create their own color palettes. + +### Multiple Layouts + +LeptonX is being developed with such an architecture that it will be easy to replace the layout. You will be given many options to choose any layout you prefer. + +Take a look at the following layouts: + +![An image showing default layout for LeptonX lite and pro packages](./default-layout.png) +![An image showing top menu layout](./top-menu-layout.png) + +## The LeptonX Ecosystem + +The previous version of Lepton Theme has been only available to Commercial ABP customers and it has never been as a standalone (ABP independent) template. With LeptonX, we aim at a broader audience. With this goal in mind, LeptonX can be integrated into any tech stack you'd prefer as well as the ABP Framework. That's why you'll be offered a variety of packages as follows: + +- A template with HTML, VanillaJS, CSS (can be used as ABP independent) + - You can just download this like any other template and start developing your application. +- An angular library (can be used as ABP independent) + - An upgradeable angular package contains layouts, components, directives, and services for you to build any application you'd like. If you choose this option, you'll be able to stay up-to-date with LeptonX as we will continue to introduce new features. +- A standalone Angular template that contains code of the npm package above (can be used as ABP independent) + - You can also download an angular template that contains the library above. You can edit it as you see fit, however, it will be harder to integrate new features once they become available. +- Free ABP packages for all of the clients available, MVC, Blazor, and Angular that utilizes the lite packages + - Client-specific packages that employ LeptonX-lite for open-source ABP users. +- Commercial ABP packages for all of the clients available, MVC, Blazor, and Angular that utilizes Pro (full version) packages + - Client-specific packages that employ, provide and extend all of the features that LeptonX-Pro packages have. + +As is seen above, the LeptonX ecosystem contains multiple projects that are designed for different kinds of users. + +## The LeptonX-lite + +As mentioned above, there will be a free-to-use version of LeptonX. It will contain most of the basic features and components that the pro version has. It will be also a one-line change to upgrade from the lite version to the pro (full) version. Here, some of the differences between the lite and pro versions: + +- The mobile tab navigation bar shown above will be only available to pro. In the lite version, a hamburger menu will take its place. +- The lite version will be released just with the side menu layout although we may release more in the future as our infrastructure makes it easy to develop and replace layouts. +- Different color options and the ability to change colors during runtime are pro-only features. CSS of the lite version is built with the `dim` colors. + +## Alpha Version + +As we have mentioned above, both user and developer experience means a great deal to us. Therefore, we will release an alpha version of the LeptonX-lite package and the free ABP-based Angular package soon to start collecting feedback from our users. Once the packages are released, we would like you to try them out and give us feedback. It will help us improve the LeptonX greatly. We plan to release a stable version later this year. + +To stay up-to-date with LeptonX and be notified once the alpha version is ready, follow our [Twitter account](https://twitter.com/volosoftcompany). \ No newline at end of file diff --git a/docs/en/Blog-Posts/2021-05-24-LeptonX/default-layout.png b/docs/en/Blog-Posts/2021-05-24-LeptonX/default-layout.png new file mode 100644 index 0000000000..91e3ce214c Binary files /dev/null and b/docs/en/Blog-Posts/2021-05-24-LeptonX/default-layout.png differ diff --git a/docs/en/Blog-Posts/2021-05-24-LeptonX/leptonx-intro-cover-image.png b/docs/en/Blog-Posts/2021-05-24-LeptonX/leptonx-intro-cover-image.png new file mode 100644 index 0000000000..46faaf13e6 Binary files /dev/null and b/docs/en/Blog-Posts/2021-05-24-LeptonX/leptonx-intro-cover-image.png differ diff --git a/docs/en/Blog-Posts/2021-05-24-LeptonX/mobiles.png b/docs/en/Blog-Posts/2021-05-24-LeptonX/mobiles.png new file mode 100644 index 0000000000..5e25afb845 Binary files /dev/null and b/docs/en/Blog-Posts/2021-05-24-LeptonX/mobiles.png differ diff --git a/docs/en/Blog-Posts/2021-05-24-LeptonX/tablets.png b/docs/en/Blog-Posts/2021-05-24-LeptonX/tablets.png new file mode 100644 index 0000000000..e9944e15f7 Binary files /dev/null and b/docs/en/Blog-Posts/2021-05-24-LeptonX/tablets.png differ diff --git a/docs/en/Blog-Posts/2021-05-24-LeptonX/top-menu-layout.png b/docs/en/Blog-Posts/2021-05-24-LeptonX/top-menu-layout.png new file mode 100644 index 0000000000..b0edab8974 Binary files /dev/null and b/docs/en/Blog-Posts/2021-05-24-LeptonX/top-menu-layout.png differ diff --git a/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/POST.md b/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/POST.md new file mode 100644 index 0000000000..dff6d1af77 --- /dev/null +++ b/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/POST.md @@ -0,0 +1,174 @@ +# ABP Platform 4.4 RC Has Been Released + +Today, we have released the [ABP Framework](https://abp.io/) and the [ABP Commercial](https://commercial.abp.io/) version 4.4 RC (Release Candidate). This blog post introduces the new features and important changes in this new version. + +> **The planned release date for the [4.4.0 final](https://github.com/abpframework/abp/milestone/53) version is July 13, 2021**. + +## Get Started with the 4.4 RC + +If you want to try the version 4.4.0 today, follow the steps below; + +1) **Upgrade** the ABP CLI to the version `4.4.0-rc.1` using a command line terminal: + +````bash +dotnet tool update Volo.Abp.Cli -g --version 4.4.0-rc.1 +```` + +**or install** if you haven't installed before: + +````bash +dotnet tool install Volo.Abp.Cli -g --version 4.4.0-rc.1 +```` + +2) Create a **new application** with the `--preview` option: + +````bash +abp new BookStore --preview +```` + +See the [ABP CLI documentation](https://docs.abp.io/en/abp/latest/CLI) for all the available options. + +> You can also use the *Direct Download* tab on the [Get Started](https://abp.io/get-started) page by selecting the **Preview checkbox**. + +### Migration Notes + +There is **no breaking change** with this version. However, if you are using Entity Framework Core, you will need to run the `Add-Migration` command to add a new **database migration** since some changes done in the module database mappings. + +## What's new with the ABP Framework 4.4 + +### Removed EntityFrameworkCore.DbMigrations Project + +With this version, we are doing an important change in the application startup solution template. The startup solution was containing an `EntityFrameworkCore.DbMigrations` project that contains a separate `DbContext` class which was responsible to unify the module database mappings and maintain the code-first database migrations. With the v4.4, we've removed that project from the solution. In the new structure, the `EntityFrameworkCore` integration project will be used for database migrations as well as on runtime. + +We'd published [a community article](https://community.abp.io/articles/unifying-dbcontexts-for-ef-core-removing-the-ef-core-migrations-project-nsyhrtna) about that change. Please see the article to understand the motivation behind the change. + +Beside the `DbContext` unification, we've also used the new `ReplaceDbContext` attribute and [replaced](https://github.com/abpframework/abp/blob/ea2205f0855f52015152ae066a5c239af4b8511f/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/EntityFrameworkCore/MyProjectNameDbContext.cs#L18-L19) the `IIdentityDbContext` and `ITenantManagementDbContext` interfaces to make it possible to perform join queries over repositories for these modules easily. In the next days, we will publish another community article to explain the problem and the solution. However, most of times, you don't need to know these details. + +### Dynamic Menu Management for the CMS Kit Module + +CMS Kit is a set of reusable *Content Management System* features packaged as an ABP application module. We had published the first usable version with the previous release. With this release, we are adding another feature to the CMS Kit module: You can now dynamically arrange the main menu on the UI, which is an essential feature for any kind of content management system. In this way, you can add pages or any kind of arbitrary URLs to the main menu from the UI. + +A screenshot from the menu management page (from the CMS Kit admin side): + +![menu-items-admin](menu-items-admin.png) + +And the items rendered in a public website: + +![menu-items-public](menu-items-public.png) + +Note that this feature is also available with the open source [CMS Kit module](https://docs.abp.io/en/abp/4.4/Modules/Cms-Kit/Index) (while the screenshots have been taken from the ABP Commercial). + +### Razor Engine Support for Text Templating + +[Text Templating](https://docs.abp.io/en/abp/4.4/Text-Templating) is a system to generate content on runtime by using a model (data) and a template. It was running on the [Scriban](https://github.com/scriban/scriban) templating engine. Beginning from this version, we have a second option: We can use the familiar **razor syntax** to build and render the templates. See the text templating [razor integration document](https://docs.abp.io/en/abp/4.4/Text-Templating-Razor) to get started with the new engine! + +### New Customization Points for DbContext/Entities + +Two new extension methods are added to `ObjectExtensionManager.Instance` to override EF Core mappings of [pre-built application modules](https://docs.abp.io/en/abp/latest/Modules/Index). + +**Example: Change mappings for the `IdentityDbContext` to override mappings for the `IdentityUser` entity** + +````csharp +ObjectExtensionManager.Instance.MapEfCoreDbContext(modelBuilder => +{ + modelBuilder.Entity(b => + { + b.ToTable("MyUsers"); + b.Property(x => x.Email).HasMaxLength(300); + }); +}); +```` + +The startup template contains a class, like `YourProjectNameEfCoreEntityExtensionMappings`, that can be used to place that code. + +### New ABP CLI Commands + +There are new [ABP CLI](https://docs.abp.io/en/abp/4.4/CLI) commands introduced with the v4.4: + +* `abp install-libs` command is used for MVC / Razor Pages and Blazor Server applications to restore the `wwwroot/libs` folder. Previously we were running the `yarn` and `gulp` commands to restore that folder. While the `install-libs` command still uses yarn (if available), it is no longer needed to use `gulp`. +* `abp prompt` command can be used to open a prompt for the ABP CLI and run multiple commands without needing to specify the `abp` command every time. For example, if you run `abp prompt`, then you can directly run `install-libs` instead of `abp install-libs`. Use `exit` to quit from the ABP prompt. +* `abp batch` command can be used to run multiple ABP commands with one command. Prepare a text file, write each command as a line (without the `abp` command prefix), then execute `abp batch ` (ex: `abp batch your_commands.txt`) command to execute all the commands in that file. + +### appsettings.secrets.json + +Added `appsettings.secrets.json` to the startup template that can be used to set your sensitive/secret configuration values. You can ignore this file from source control (by adding to `.gitignore` if you're using git) and keep it only in developer/production machines. + +### Other ABP Framework Improvements + +* [#9350](https://github.com/abpframework/abp/pull/9350) Extracted `IRemoteServiceConfigurationProvider` to get remote service configurations. You can replace this service to get the configuration from any source. +* [#8829](https://github.com/abpframework/abp/pull/8829) Implemented error handler and retry for distributed event bus. +* [#9288](https://github.com/abpframework/abp/issues/9288) Use default CORS policy instead of a named one in the startup template. It is suggested to update your own solutions to make it simpler. +* Translated the framework and module localization strings to Hindi, Italian, Finnish and French languages. + +Beside these, there are a lot of enhancements and bug fixes. See the [4.4-preview milestone](https://github.com/abpframework/abp/milestone/52?closed=1) for all issues and pull requests closed with this version. + +## What's new with the ABP Commercial 4.4 + +### New Features for the SaaS Module + +We've implemented some important features to the [SaaS module](https://commercial.abp.io/modules/Volo.Saas): + +* Integrated to the [Payment module](https://commercial.abp.io/modules/Volo.Payment) and implemented **subscription system** for the SaaS module. +* Allow to make a **tenant active/passive**. In this way, you can take a tenant to passive to prevent the users of that tenant from using the system. In addition, you can set a date to automatically make a tenant passive when the date comes. +* Allow to **limit user count** for a tenant. +* Allow to set **different connection strings** for a tenant for each database/module, which makes possible to create different databases for a tenant for each microservice in a microservice solution. + +### New ABP Suite Code Generation Features + +There are many improvements done for for [ABP Suite](https://commercial.abp.io/tools/suite), including CRUD page generation for the **[microservice solution](https://docs.abp.io/en/commercial/latest/startup-templates/microservice/index) template**. + +### Angular UI: Two Factor Authentication for the Resource Owner Password Flow + +In the previous version, we had implemented the resource owner password authentication flow for the Angular UI, which makes the login process easier for simpler applications. With this release, we've implemented two-factor authentication for that flow. Authorization code flow already supports 2FA. + +### Other ABP Commercial Improvements + +* Added web layers to microservices in the microservice solution. You can use them to create modular UI or override existing pages/components of pre-built modules (e.g. Identity and SaaS). +* ABP Commercial license code has been moved to `appsettings.secrets.json` in the new startup templates. +* Added new language options: Hindi, Italian, Arabic, Finnish, French. + +Beside these, there are many minor improvements and fixes done in the modules and themes. + +## Other News + +In this section, I will share some news that you may be interested in. + +### New Article: Using Elsa Workflow with ABP Framework + +We have been frequently asked how to use [Elsa Workflows](https://elsa-workflows.github.io/elsa-core/) with the ABP Framework. Finally, we have [created an article](https://community.abp.io/articles/using-elsa-workflow-with-the-abp-framework-773siqi9) to demonstrate it. + +![elsa-overview](elsa-overview.gif) + +You can [check it](https://community.abp.io/articles/using-elsa-workflow-with-the-abp-framework-773siqi9) to see how to integrate Elsa into an ABP based solution easily. + +### Free E-Book: Implementing Domain Driven Design + +We've published a free e-book for the ABP Community in the beginning of June. This is a practical guide for implementing Domain Driven Design (DDD). While the implementation details are based on the ABP Framework infrastructure, the basic concepts, principles and models can be applied to any solution, even if it is not a .NET solution. + +![ddd-book](ddd-book.png) + +Thousands of copies are already downloaded. If you haven't seen it yet, [click here to get a free copy of that e-book](https://abp.io/books/implementing-domain-driven-design). + +### The LeptonX Theme + +We have been working on a new ABP theme, named the *LeptonX*, for a long time. The theme will be available for ABP Framework (free - lite version) and ABP Commercial (pro version). It is being finalized in the next weeks and we will release the first version in a short time. + +![leptonx](leptonx.png) + +See [this blog post](https://volosoft.com/blog/introducing-the-lepton-theme-next-generation) to learn more about that project. + +### Volosoft & .NET Foundation + +[Volosoft](https://volosoft.com/), the company leads the ABP Framework project, has been a corporate sponsor of the [.NET Foundation](https://dotnetfoundation.org/). We are happy by taking our place among other great sponsors! + +![dotnetfoundation-sponsor-volosoft](dotnetfoundation-sponsor-volosoft.png) + +We will continue to contribute to and support open source! See this [blog post for the announcement](https://volosoft.com/blog/Volosoft-Announces-the-NET-Foundation-Sponsorship). + +### Looking for Developer Advocate(s) + +We are actively looking for professional developer advocates for the ABP.IO platform. If you want to create content and touch to the ABP community, please check our [job post](https://github.com/volosoft/vs-home/issues/13). + +## About the Next Version + +The next version will be a major version: 5.0, which will be based on .NET 6.0. We are planning to release it in the end of 2021, short time after the .NET 6.0 release. We will release multiple preview/beta versions before the RC version. See the [road map](https://docs.abp.io/en/abp/latest/Road-Map) for details of the planned works for the version 5.0. diff --git a/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/abp-44-cover.png b/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/abp-44-cover.png new file mode 100644 index 0000000000..1f231aac00 Binary files /dev/null and b/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/abp-44-cover.png differ diff --git a/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/ddd-book.png b/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/ddd-book.png new file mode 100644 index 0000000000..747fcc1d13 Binary files /dev/null and b/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/ddd-book.png differ diff --git a/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/dotnetfoundation-sponsor-volosoft.png b/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/dotnetfoundation-sponsor-volosoft.png new file mode 100644 index 0000000000..e1f70929f4 Binary files /dev/null and b/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/dotnetfoundation-sponsor-volosoft.png differ diff --git a/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/elsa-overview.gif b/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/elsa-overview.gif new file mode 100644 index 0000000000..ba589cabfc Binary files /dev/null and b/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/elsa-overview.gif differ diff --git a/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/leptonx.png b/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/leptonx.png new file mode 100644 index 0000000000..46faaf13e6 Binary files /dev/null and b/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/leptonx.png differ diff --git a/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/menu-items-admin.png b/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/menu-items-admin.png new file mode 100644 index 0000000000..63075eed70 Binary files /dev/null and b/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/menu-items-admin.png differ diff --git a/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/menu-items-public.png b/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/menu-items-public.png new file mode 100644 index 0000000000..42c12c6801 Binary files /dev/null and b/docs/en/Blog-Posts/2021-06-28 v4_4 Preview/menu-items-public.png differ diff --git a/docs/en/Blog-Posts/2021-07-08 Introducing_the_eShopOnAbp/POST.md b/docs/en/Blog-Posts/2021-07-08 Introducing_the_eShopOnAbp/POST.md new file mode 100644 index 0000000000..efba693145 --- /dev/null +++ b/docs/en/Blog-Posts/2021-07-08 Introducing_the_eShopOnAbp/POST.md @@ -0,0 +1,52 @@ +# Introducing the eShopOnAbp Project + +We are happy to introduce the **eShopOnAbp** project as an example microservice solution built with the ABP Framework by the core ABP team. This solution demonstrates the strength of ABP Framework and using it in a real-life case. The goal of the project is to create a full-featured cloud-native microservices reference application. The project is inspired by the [eShopOnContainers](https://github.com/dotnet-architecture/eShopOnContainers) project and shows how it can be implemented with the ABP Framework. + +> **Project Status**: Currently, the project doesn't have any business logic. We've just brought ABP's pre-built modules (Identity, Tenant Management, IdentityServer, etc) together as a base solution. However, it is fully working and you can now take it as a base solution for your microservice project. From now on, we will build the example application functionalities / business logic on top of it. + +## Source Code + +The source code is available on [abpframework/eShopOnAbp](https://github.com/abpframework/eShopOnAbp) repository. + +## The Big Picture + +The project follows micro-service architecture and overall structure is presented below. + +![eShopOnAbp Overall Solution](overall-solution.png) + +## How to Run? + +You can either run in Visual Studio, or using [Microsoft Tye](https://github.com/dotnet/tye). Tye is a developer tool that makes developing, testing, and deploying micro-services and distributed applications easier. + + ### Requirements + +- .NET 5.0+ +- Docker +- Yarn + +### Instructions + +- Clone the repository ( [eShopOnAbp](https://github.com/abpframework/eShopOnAbp) ) + +- Install Tye (*follow [these steps](https://github.com/dotnet/tye/blob/main/docs/getting_started.md#installing-tye)*) + +- Execute `run-tye.ps1` + +- Wait until all applications are up! + + - You can check running application from tye dashboard ([localhost:8000](http://127.0.0.1:8000/)) + +- After all your backend services are up, start the angular application: + + ```bash + cd apps/angular + yarn start + ``` + +## What's Next? + +We'll work on deployment & CI-CD processes as a next step and build the business logic on. First goal is deploying the entire application on [Kubernetes](https://kubernetes.io/). + +## Feedback + +Your comments and suggestions is important for us. You can ask your questions or post your feedback under [this discussion entry](https://github.com/abpframework/abp/discussions/9536). diff --git a/docs/en/Blog-Posts/2021-07-08 Introducing_the_eShopOnAbp/overall-solution.png b/docs/en/Blog-Posts/2021-07-08 Introducing_the_eShopOnAbp/overall-solution.png new file mode 100644 index 0000000000..e3ef175316 Binary files /dev/null and b/docs/en/Blog-Posts/2021-07-08 Introducing_the_eShopOnAbp/overall-solution.png differ diff --git a/docs/en/CLI-New-Command-Samples.md b/docs/en/CLI-New-Command-Samples.md index 52ca533c81..9eae987925 100644 --- a/docs/en/CLI-New-Command-Samples.md +++ b/docs/en/CLI-New-Command-Samples.md @@ -1,6 +1,6 @@ # ABP CLI Create Solution Sample Commands -The `abp new` command creates an ABP solution or other artifacts based on an ABP template. ABP CLI has several parameters to create a new ABP solution. In this document we will show you some sample commands to create a new solution. All the project names are `Acme.BookStore`. Currently, the only available mobile project is a `React Native` mobile app. Available database providers are `Entity Framework Core` and `MongoDB`. All the commands starts with `abp new`. +The `abp new` command creates an ABP solution or other artifacts based on an ABP template. [ABP CLI](CLI.md) has several parameters to create a new ABP solution. In this document we will show you some sample commands to create a new solution. All the project names are `Acme.BookStore`. Currently, the only available mobile project is a `React Native` mobile app. Available database providers are `Entity Framework Core` and `MongoDB`. All the commands starts with `abp new`. ## Angular @@ -217,6 +217,10 @@ As seen below, ABP Framework libraries are local project references. - + ``` + +## See Also + +* [ABP CLI documentation](CLI.md) diff --git a/docs/en/CLI.md b/docs/en/CLI.md index f2e7df219b..4e895e1277 100644 --- a/docs/en/CLI.md +++ b/docs/en/CLI.md @@ -42,6 +42,7 @@ Here, the list of all available commands before explaining their details: * **`login`**: Authenticates on your computer with your [abp.io](https://abp.io/) username and password. * **`logout`**: Logouts from your computer if you've authenticated before. * **`bundle`**: Generates script and style references for an ABP Blazor project. +* **`install-libs`**: Install NPM Packages for MVC / Razor Pages and Blazor Server UI types. ### help @@ -118,6 +119,8 @@ For more samples, go to [ABP CLI Create Solution Samples](CLI-New-Command-Sample * `--local-framework-ref --abp-path`: Uses local projects references to the ABP framework instead of using the NuGet packages. This can be useful if you download the ABP Framework source code and have a local reference to the framework from your application. * `--no-random-port`: Uses template's default ports. +See some [examples for the new command](CLI-New-Command-Samples.md) here. + ### update Updating all ABP related packages can be tedious since there are many packages of the framework and modules. This command automatically updates all ABP related NuGet and NPM packages in a solution or project to the latest versions. @@ -433,3 +436,22 @@ abp bundle [options] `bundle` command reads the `appsettings.json` file inside the Blazor project for bundling options. For more details about managing style and script references in Blazor apps, see [Managing Global Scripts & Styles](UI/Blazor/Global-Scripts-Styles.md) +### install-libs + +This command install NPM Packages for MVC / Razor Pages and Blazor Server UI types. Its **executing directory** or passed ```--working-directory``` parameter's directory must contain a project file(*.csproj). + +`install-libs` command reads the `abp.resourcemapping.js` file to manage package. For more details see [Client Side Package Management](UI/AspNetCore/Client-Side-Package-Management.md). + +Usage: + +````bash +abp install-libs [options] +```` + +#### Options + +* ```--working-directory``` or ```-wd```: Specifies the working directory. This option is useful when executing directory doesn't contain a project file. + +## See Also + +* [Examples for the new command](CLI-New-Command-Samples.md) \ No newline at end of file diff --git a/docs/en/Community-Articles/2020-08-12-Patch-Chrome-Login-Issue-For-IdentityServer4/Block-Third-Party-Cookies.png b/docs/en/Community-Articles/2020-08-12-Patch-Chrome-Login-Issue-For-IdentityServer4/Block-Third-Party-Cookies.png new file mode 100644 index 0000000000..fe166211df Binary files /dev/null and b/docs/en/Community-Articles/2020-08-12-Patch-Chrome-Login-Issue-For-IdentityServer4/Block-Third-Party-Cookies.png differ 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 index d0d2b1ccee..f7483208f8 100644 --- 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 @@ -121,6 +121,10 @@ public override void OnApplicationInitialization(ApplicationInitializationContex It's all! You are ready to go! +> Attention: This problem can't be solved if the user/browser/operating system blocked third-party cookies. + +![Block-Third-Party-Cookies](Block-Third-Party-Cookies.png) + --- Referenced from https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/ diff --git a/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md b/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md index 80141a32fe..308358bb67 100644 --- a/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md +++ b/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/How-To-Add-Custom-Property-To-The-User-Entity.md @@ -101,7 +101,7 @@ Now we need to add migration to see what has changed in our database. This for, ![nuget-package-manager](./nuget-package-manager.png) -Select the **CustomizeUserDemo.EntityFramework.DbMigrations** as the **default project** and execute the following command: +Select the **CustomizeUserDemo.EntityFramework** as the **default project** and execute the following command: ```bash Add-Migration "Updated-User-Entity" @@ -109,7 +109,7 @@ Add-Migration "Updated-User-Entity" ![added-new-migration](./added-new-migration.png) -This will create a new migration class inside the `Migrations` folder of the **CustomizeUserDemo.EntityFrameworkCore.DbMigrations** project. +This will create a new migration class inside the `Migrations` folder of the **CustomizeUserDemo.EntityFrameworkCore** project. > If you are using another IDE than the Visual Studio, you can use `dotnet-ef` tool as [documented here](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli#create-a-migration). diff --git a/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/initial-project.png b/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/initial-project.png index 6e08833853..b64e93a99f 100755 Binary files a/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/initial-project.png and b/docs/en/Community-Articles/2020-10-08-How-To-Add-Custom-Property-To-The-User-Entity/initial-project.png differ diff --git a/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/POST.md b/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/POST.md new file mode 100644 index 0000000000..1a07d744e7 --- /dev/null +++ b/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/POST.md @@ -0,0 +1,457 @@ +# Using Elsa Workflow with ABP Framework + +**Elsa Core** is an open-source workflows library that can be used in any kind of .NET Core application. Using such a workflow library can be useful to implement business rules visually or programmatically. + +![elsa-overview](./elsa-overview.gif) + +This article shows how we can use this workflow library within our ABP-based application. We will start with a couple of examples and then we will integrate the **Elsa Dashboard** (you can see it in the above gif) into our application to be able to design our workflows visually. + +## Source Code + +You can find the source of the example solution used in this article [here](https://github.com/abpframework/abp-samples/tree/master/ElsaDemo). + +## Create the Project + +In this article, I will create a new startup template with EF Core as a database provider and MVC/Razor-Pages for the UI framework. + +> If you already have a project with MVC/Razor-Pages or Blazor UI, you don't need to create a new startup template, you can directly implement the following steps to your existing project (you can skip this section). + +* We will create a new solution named `ElsaDemo` (or whatever you want). We will create a new startup template with **EF Core** as a database provider and **MVC/Razor-Pages** for the UI framework by using the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI): + +```bash +abp new ElsaDemo +``` + +* Our project boilerplate will be ready after the download is finished. Then, we can open the solution in the Visual Studio (or any other IDE). + +* We can run the `ElsaDemo.DbMigrator` project to apply migration into our database and seed initial data. + +* After the database and initial data created, we can run the `ElsaDemo.Web` to see our UI working properly. + +> Default admin username is **admin** and password is **1q2w3E*** + +## Let's Create The First Workflow (Console Activity) + +We can start with creating our first workflow. Let's get started with creating a basic hello-world workflow by using console activity. In this example, we will **programmatically** define a workflow definition that displays the text **"Hello World from Elsa!"** to the console using Elsa's Workflow Builder API and run this workflow when the application initialized. + +### Install Packages + +We need to add two packages: `Elsa` and `Elsa.Activities.Console` into our `ElsaDemo.Web` project. We can add these two packages with the following command: + +```bash +dotnet add package Elsa +dotnet add package Elsa.Activities.Console +``` + +* After the packages installed, we can define our first workflow. To do this, create a folder named **Workflows** and in this folder create a class named `HelloWorldConsole`. + +```csharp +using Elsa.Activities.Console; +using Elsa.Builders; + +namespace ElsaDemo.Web.Workflows +{ + public class HelloWorldConsole : IWorkflow + { + public void Build(IWorkflowBuilder builder) => builder.WriteLine("Hello World from Elsa!"); + } +} +``` + +* In here we've basically implemented the `IWorkflow` interface which only has one method named **Build**. In this method, we can define our workflow's execution steps (activities). + +* As you can see in the example above, we've used an activity named **WriteLine**, which writes a line of text to the console. Elsa Core has many pre-defined activities like that. E.g **HttpEndpoint** and **WriteHttpResponse** (we will see them both in the next section). + +> "An activity is an atomic building block that represents a single executable step on the workflow." - [Elsa Core Activity Definition](https://elsa-workflows.github.io/elsa-core/docs/next/concepts/concepts-workflows#activity) + +* After defining our workflow, we need to define service registrations which required for the Elsa Core library to work properly. To do that, open your `ElsaDemoWebModule` class and update your `ElsaDemoWebModule` with the following lines. Most of the codes are abbreviated for simplicity. + +```csharp +using ElsaDemo.Web.Workflows; +using Elsa.Services; + +public override void ConfigureServices(ServiceConfigurationContext context) +{ + var hostingEnvironment = context.Services.GetHostingEnvironment(); + var configuration = context.Services.GetConfiguration(); + + //... + + ConfigureElsa(context); +} + +private void ConfigureElsa(ServiceConfigurationContext context) +{ + context.Services.AddElsa(options => + { + options + .AddConsoleActivities() + .AddWorkflow(); + }); +} + +public override void OnApplicationInitialization(ApplicationInitializationContext context) +{ + //... + + var workflowRunner = context.ServiceProvider.GetRequiredService(); + workflowRunner.BuildAndStartWorkflowAsync(); +} +``` + +* Here we basically, configured Elsa's services in our `ConfigureServices` method and after that in our `OnApplicationInitialization` method we started the `HelloWorldConsole` workflow. + +* If we run the application and examine the console outputs, we should see the message that we defined in our workflow. + +![hello-world-workflow](./hello-world-workflow.jpg) + +## Creating A Workflow By Using Http Activities + +In this example, we will create a workflow that uses **Http Activities**. It will basically listen the specified route for incoming HTTP Request and writes back a simple response. + +### Add Elsa.Activities.Http Package + +* To be able to use **HTTP Activities** we need to add `Elsa` (we've already added in the previous section) and `Elsa.Activities.Http` packages into our web application. + +```bash +dotnet add package Elsa.Activities.Http +``` + +* After the package installed, we can create our workflow. Let's started with creating a class named `HelloWorldHttp` under **Workflows** folder. + +```csharp +using System.Net; +using Elsa.Activities.Http; +using Elsa.Builders; + +namespace ElsaDemo.Web.Workflows +{ + public class HelloWorldHttp : IWorkflow + { + public void Build(IWorkflowBuilder builder) + { + builder + .HttpEndpoint("/hello-world") + .WriteHttpResponse(HttpStatusCode.OK, "

Hello World!

", "text/html"); + } + } +} +``` + +* The above workflow has two activities. The first activity `HttpEndpoint` represents an HTTP endpoint, which can be invoked using an HTTP client, including a web browser. The first activity is connected to the second activity `WriteHttpResponse`, which returns a simple response to us. + +* After defined the **HelloWorldHttp** workflow we need to define this class as workflow. So, open your `ElsaDemoWebModule` and update the `ConfigureElsa` method as below. + +```csharp +private void ConfigureElsa(ServiceConfigurationContext context) +{ + context.Services.AddElsa(options => + { + options + .AddConsoleActivities() + .AddHttpActivities() //add this line to be able to use the http activities + .AddWorkflow() + .AddWorkflow(); //workflow that we defined + }); +} +``` +* And add the **UseHttpActivities** middleware to `OnApplicationInitilization` method of your `ElsaDemoWebModule` class. + +```csharp +public override void OnApplicationInitialization(ApplicationInitializationContext context) +{ + // ... + app.UseAuditing(); + app.UseAbpSerilogEnrichers(); + app.UseHttpActivities(); //add this line + app.UseConfiguredEndpoints(); + + var workflowRunner = context.ServiceProvider.GetRequiredService(); + workflowRunner.BuildAndStartWorkflowAsync(); +} +``` + +* If we run the application and navigate to the "/hello-world" route we should see the response message that we've defined (by using **WriteHttpResponse** activity) in our `HelloWorldHttp` workflow. + +![hello-world-http](./hello-world-http.jpg) + +## Integrate Elsa Dashboard To Application + +* Until now we've created two workflows programmatically. But also we can create workflows visually by using Elsa's **HTML5 Workflow Designer**. + +* Being able to design our workflows easily and taking advantage of **HTML5 Workflow Designer** we will integrate the Elsa Dashboard to our application. + +### Install Packages + +* Following three packages required for Elsa Server. + +```bash +dotnet add package Elsa.Activities.Temporal.Quartz +dotnet add package Elsa.Persistence.EntityFramework.SqlServer +dotnet add package Elsa.Server.Api +``` + +> Also, we need to install the **Elsa** and **Elsa.Activities.Http** packages but we've already installed these packages in the previous sections. + +* We need to install one more package named `Elsa.Designer.Components.Web`. This package provides us the **Elsa Dashboard** component. + +```bash +dotnet add package Elsa.Designer.Components.Web +``` + +* After the package installations completed, we need to make the necessary configurations to be able to use the **Elsa Server** and **Elsa Dashboard**. Therefore, open your `ElsaDemoWebModule` class and make the necessary changes as below. + +```csharp +public override void ConfigureServices(ServiceConfigurationContext context) +{ + var configuration = context.Services.GetConfiguration(); + + //... + + ConfigureElsa(context, configuration); +} + +private void ConfigureElsa(ServiceConfigurationContext context, IConfiguration configuration) +{ + var elsaSection = configuration.GetSection("Elsa"); + + context.Services.AddElsa(elsa => + { + elsa + .UseEntityFrameworkPersistence(ef => + DbContextOptionsBuilderExtensions.UseSqlServer(ef, + configuration.GetConnectionString("Default"))) + .AddConsoleActivities() + .AddHttpActivities(elsaSection.GetSection("Server").Bind) + .AddQuartzTemporalActivities() + .AddJavaScriptActivities() + .AddWorkflowsFrom(); + }); + + context.Services.AddElsaApiEndpoints(); + context.Services.Configure(options => + { + options.UseApiBehavior = false; + }); + + context.Services.AddCors(cors => cors.AddDefaultPolicy(policy => policy + .AllowAnyHeader() + .AllowAnyMethod() + .AllowAnyOrigin() + .WithExposedHeaders("Content-Disposition")) + ); + + //register controllers inside elsa + context.Services.AddAssemblyOf(); + + //Disable antiforgery validation for elsa + Configure(options => + { + options.AutoValidateFilter = type => + type.Assembly != typeof(Elsa.Server.Api.Endpoints.WorkflowRegistry.Get).Assembly; + }); +} + +public override void OnApplicationInitialization(ApplicationInitializationContext context) +{ + app.UseCors(); + + //... + + app.UseHttpActivities(); + app.UseConfiguredEndpoints(endpoints => + { + endpoints.MapFallbackToPage("/_Host"); + }); + + var workflowRunner = context.ServiceProvider.GetRequiredService(); + workflowRunner.BuildAndStartWorkflowAsync(); +} +``` + +* In here we've specified the Elsa Server Api's assembly by using the `AddAssemblyOf<>` extension method to register the required services (controllers). These services required for the dashboard (if we create a workflow by using **Elsa Workflow Designer** it calls some services under the hook, therefore we need to be assured about these services get registered). + +* With [v4.4](https://github.com/abpframework/abp/pull/9299), we will no longer need to specify this line of code. + +> **Note:** `AddAssemblyOf<>` extension method can help you to register all your services by convention. You can check [here](https://docs.abp.io/en/abp/latest/Dependency-Injection#conventional-registration) for more information about conventional registration. + +* We don't need to register our workflows one by one anymore. Because now we use `.AddWorkflowsFrom()`, and this registers workflows on our behalf. + +* As you may notice here, we use a section named `Elsa` and its sub-sections from the configuration system but we didn't define them yet. To define them open your `appsettings.json` and add the following Elsa section into this file. + +```json +{ + //... + + "Elsa": { + "Http": { + "BaseUrl": "https://localhost:44336" + } + } +} +``` + +#### Define Permission For Elsa Dashboard + +* We can define a [permission](https://docs.abp.io/en/abp/latest/Authorization#permission-system) to be assured of only allowed users can see the Elsa Dashboard. + +* Open your `ElsaDemoPermissions` class under the **Permissions** folder (in the `ElsaDemo.Application.Contracts` layer) and add the following permission name. + +```csharp +namespace ElsaDemo.Permissions +{ + public static class ElsaDemoPermissions + { + public const string GroupName = "ElsaDemo"; + + public const string ElsaDashboard = GroupName + ".ElsaDashboard"; + } +} +``` + +* After that, open your `ElsaDemoPermissionDefinitionProvider` class and define the permission for Elsa Dashboard. + +```csharp +using ElsaDemo.Localization; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Localization; + +namespace ElsaDemo.Permissions +{ + public class ElsaDemoPermissionDefinitionProvider : PermissionDefinitionProvider + { + public override void Define(IPermissionDefinitionContext context) + { + var myGroup = context.AddGroup(ElsaDemoPermissions.GroupName); + + myGroup.AddPermission(ElsaDemoPermissions.ElsaDashboard, L("Permission:ElsaDashboard")); + } + + private static LocalizableString L(string name) + { + return LocalizableString.Create(name); + } + } +} +``` + +* As you can notice, we've used a localized value (**L("Permission:ElsaDashboard")**) but haven't added this localization key and value to the localization file, so let's add this localization key and value. To do this, open your `en.json` file under **Localization/ElsaDemo** folder (under the **DomainShared** layer) and add this localization key. + +```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.", + "Permission:ElsaDashboard": "Elsa Dashboard" + } +} +``` + +#### Add Elsa Dashboard Component To Application + +* After those configurations, now we can add Elsa Dashboard to our application with an authorization check. To do this, create a razor page named **_Host.cshtml** (under **Pages** folder) and update its content as below. + +```html +@page "/elsa" +@using ElsaDemo.Permissions +@using Microsoft.AspNetCore.Authorization +@attribute [Authorize(ElsaDemoPermissions.ElsaDashboard)] +@{ + var serverUrl = $"{Request.Scheme}://{Request.Host}"; + Layout = null; +} + + + + + + Elsa Workflows + + + + + + + + + + + +``` + +* We've defined an attribute for authorization check here. With this authorization check, only the user who has the **Elsa Dashboard** permission allowed to see this page. + +#### Add Elsa Dashboard Page To Main Menu + +* We can open the `ElsaDemoMenuContributor` class under the **Menus** folder and define the menu item for reaching the Elsa Dashboard easily. + +```csharp +using System.Threading.Tasks; +using ElsaDemo.Localization; +using ElsaDemo.MultiTenancy; +using ElsaDemo.Permissions; +using Volo.Abp.Identity.Web.Navigation; +using Volo.Abp.SettingManagement.Web.Navigation; +using Volo.Abp.TenantManagement.Web.Navigation; +using Volo.Abp.UI.Navigation; + +namespace ElsaDemo.Web.Menus +{ + public class ElsaDemoMenuContributor : IMenuContributor + { + public async Task ConfigureMenuAsync(MenuConfigurationContext context) + { + if (context.Menu.Name == StandardMenus.Main) + { + await ConfigureMainMenuAsync(context); + } + } + + private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) + { + var administration = context.Menu.GetAdministration(); + var l = context.GetLocalizer(); + + context.Menu.Items.Insert( + 0, + new ApplicationMenuItem( + ElsaDemoMenus.Home, + l["Menu:Home"], + "~/", + icon: "fas fa-home", + order: 0 + ) + ); + + //add Workflow menu-item + context.Menu.Items.Insert( + 1, + new ApplicationMenuItem( + ElsaDemoMenus.Home, + "Workflow", + "~/elsa", + icon: "fas fa-code-branch", + order: 1, + requiredPermissionName: ElsaDemoPermissions.ElsaDashboard + ) + ); + + //... + } + } +} +``` + +* With that menu item configuration, only the user who has **Elsa Dashboard** permission allowed to see the defined menu item. + +## Result + +* Let's run the application and see how it looks like. + +> If the account you are logged in has the **ElsaDemoPermissions.ElsaDashboard** permission, you should see the **Workflow** menu item. If you do not see this menu item, please be assured that your logged-in account has that permission. + +* Now we can click the "Workflow" menu item, display the Elsa Dashboard and designing workflows. + +![elsa-demo-result](./elsa-demo-result.gif) \ No newline at end of file diff --git a/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/elsa-demo-result.gif b/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/elsa-demo-result.gif new file mode 100644 index 0000000000..4019b6eabe Binary files /dev/null and b/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/elsa-demo-result.gif differ diff --git a/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/elsa-overview.gif b/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/elsa-overview.gif new file mode 100644 index 0000000000..ba589cabfc Binary files /dev/null and b/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/elsa-overview.gif differ diff --git a/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/hello-world-http.jpg b/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/hello-world-http.jpg new file mode 100644 index 0000000000..6254e1348c Binary files /dev/null and b/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/hello-world-http.jpg differ diff --git a/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/hello-world-workflow.jpg b/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/hello-world-workflow.jpg new file mode 100644 index 0000000000..35451c88f1 Binary files /dev/null and b/docs/en/Community-Articles/2021-06-17-Using-Elsa-Workflow-with-ABP-Framework/hello-world-workflow.jpg differ diff --git a/docs/en/Connection-Strings.md b/docs/en/Connection-Strings.md index 585d0657f2..c5a70174bf 100644 --- a/docs/en/Connection-Strings.md +++ b/docs/en/Connection-Strings.md @@ -70,7 +70,7 @@ For [Entity Framework Core](Entity-Framework-Core.md) and [MongoDB](MongoDB.md), Relational databases require to create the database and the database schema (tables, views... etc.) before using it. -The startup template (with EF Core ORM) comes with a single database and a `.EntityFrameworkCore.DbMigrations` project that contains the migration files for that database. This project mainly defines a *YourProjectName*MigrationsDbContext that calls the `Configure...()` methods of the used modules, like `builder.ConfigurePermissionManagement()`. +The startup template (with EF Core ORM) comes with a single database and a `.EntityFrameworkCore` project that contains related classes and the migration files for that database. This project mainly defines a *YourProjectName*DbContext that calls the `Configure...()` methods of the used modules, like `builder.ConfigurePermissionManagement()`. Once you want to separate a module's database, you typically will need to create a second migration path. See the [EF Core Migrations](Entity-Framework-Core-Migrations.md) document to learn how to create and use a different database for a desired module. diff --git a/docs/en/Customizing-Application-Modules-Extending-Entities.md b/docs/en/Customizing-Application-Modules-Extending-Entities.md index bafcc01da2..c2bcd314d4 100644 --- a/docs/en/Customizing-Application-Modules-Extending-Entities.md +++ b/docs/en/Customizing-Application-Modules-Extending-Entities.md @@ -152,14 +152,14 @@ public class MyDistributedIdentityUserChangeEventHandler : * It implements multiple `IDistributedEventHandler` interfaces: **Created**, **Updated** and **Deleted**. Because, the distributed event bus system publishes events individually. There is no "Changed" event like the local event bus. * It subscribes to `EntityEto`, which is a generic event class that is **automatically published** for all type of entities by the ABP framework. This is why it checks the **entity type** (checking the entity type as string since we assume that there is no type safe reference to the `IdentityUser` entity). -Pre-built application modules do not define specialized event types yet (like `IdentityUserEto` - "ETO" means "Event Transfer Object"). This feature is on the road map and will be available in a short term ([follow this issue](https://github.com/abpframework/abp/issues/3033)). Once it is implemented, you will be able to subscribe to individual entity types. Example: +Pre-built application modules do not define specialized event types yet (like `UserEto` - "ETO" means "Event Transfer Object"). This feature is on the road map and will be available in a short term ([follow this issue](https://github.com/abpframework/abp/issues/3033)). Once it is implemented, you will be able to subscribe to individual entity types. Example: ````csharp public class MyDistributedIdentityUserCreatedEventHandler : - IDistributedEventHandler>, + IDistributedEventHandler>, ITransientDependency { - public async Task HandleEventAsync(EntityCreatedEto eventData) + public async Task HandleEventAsync(EntityCreatedEto eventData) { var userId = eventData.Entity.Id; var userName = eventData.Entity.UserName; diff --git a/docs/en/Customizing-Application-Modules-Guide.md b/docs/en/Customizing-Application-Modules-Guide.md index 438c087e0c..b62d4e5bb7 100644 --- a/docs/en/Customizing-Application-Modules-Guide.md +++ b/docs/en/Customizing-Application-Modules-Guide.md @@ -31,7 +31,8 @@ If you don't think to make huge changes on the pre-built modules, re-using them If you want to make **huge changes** or add **major features** on a pre-built module, but the available extension points are not enough, you can consider to directly work the source code of the depended module. -In this case, you typically **add the source code** of the module to your solution and **replace package references** by local project references. **[ABP CLI](CLI.md)** automates this process for you. +In this case, you typically **add the source code** of the module to your solution and replace **every** package reference in the solution with its corresponding local project references. **[ABP CLI](CLI.md)** automates this process for you. + #### Separating the Module Solution diff --git a/docs/en/Dependency-Injection.md b/docs/en/Dependency-Injection.md index f94901ad84..f4398488e5 100644 --- a/docs/en/Dependency-Injection.md +++ b/docs/en/Dependency-Injection.md @@ -59,9 +59,9 @@ Some specific types are registered to dependency injection by default. Examples: * MVC controllers (inherit ``Controller`` or ``AbpController``) are registered as transient. * MVC page models (inherit ``PageModel`` or ``AbpPageModel``) are registered as transient. * MVC view components (inherit ``ViewComponent`` or ``AbpViewComponent``) are registered as transient. -* Application services (implement ``IApplicationService`` interface or inherit ``ApplicationService`` class) are registered as transient. -* Repositories (implement ``IRepository`` interface) are registered as transient. -* Domain services (implement ``IDomainService`` interface) are registered as transient. +* Application services (inherit ``ApplicationService`` class or its subclasses) are registered as transient. +* Repositories (implement ``BasicRepositoryBase`` class or its subclasses) are registered as transient. +* Domain services (implement ``IDomainService`` interface or inherit ``DomainService`` class) are registered as transient. Example: diff --git a/docs/en/Distributed-Event-Bus.md b/docs/en/Distributed-Event-Bus.md index 02216c7ac8..05ef16a59e 100644 --- a/docs/en/Distributed-Event-Bus.md +++ b/docs/en/Distributed-Event-Bus.md @@ -4,7 +4,7 @@ Distributed Event bus system allows to **publish** and **subscribe** to events t ## Providers -Distributed event bus system provides an **abstraction** that can be implemented by any vendor/provider. There are two providers implemented out of the box: +Distributed event bus system provides an **abstraction** that can be implemented by any vendor/provider. There are four providers implemented out of the box: * `LocalDistributedEventBus` is the default implementation that implements the distributed event bus to work as in-process. Yes! The **default implementation works just like the [local event bus](Local-Event-Bus.md)**, if you don't configure a real distributed provider. * `RabbitMqDistributedEventBus` implements the distributed event bus with the [RabbitMQ](https://www.rabbitmq.com/). See the [RabbitMQ integration document](Distributed-Event-Bus-RabbitMQ-Integration.md) to learn how to configure it. @@ -302,3 +302,54 @@ namespace AbpDemo ```` This example uses the `AutoMap` attribute of the AutoMapper to configure the mapping. You could create a profile class instead. Please refer to the AutoMapper document for more options. + +## Exception Handling + +ABP provides exception handling and retries when an exception occurs, it will move to the dead letter queue after the retry fails. + +Enable exception handling: + +```csharp +public override void PreConfigureServices(ServiceConfigurationContext context) +{ + PreConfigure(options => + { + options.EnabledErrorHandle = true; + options.UseRetryStrategy(); + }); +} +``` + +* `EnabledErrorHandle` is used to enable exception handing. +* `UseRetryStrategy` is used to enable retry. + +When an exception occurs, it will retry every three seconds up to the maximum number of retries(default is 3) and move to dead letter queue, you can change the number of retries, retry interval and dead letter queue name: + +```csharp +PreConfigure(options => +{ + options.DeadLetterName = "dead_queue"; + options.UseRetryStrategy(retryStrategyOptions => + { + retryStrategyOptions.IntervalMillisecond = 0; + retryStrategyOptions.MaxRetryAttempts = 1; + }); +}); +``` + +### Error Handle Selector + +By default all event types will be exception handling, you can use `ErrorHandleSelector` of `AbpEventBusOptions` to change it: + +```csharp +PreConfigure(options => +{ + options.ErrorHandleSelector = type => type == typeof(MyExceptionHandleEventData); +}); +``` + +`options.ErrorHandleSelector` actually a list of type predicate. You can write a lambda expression to define your filter. + +### Customize Exception Handling + +ABP defines the `IEventErrorHandler` interface and implemented by the provider, you can replace it via [dependency injection](Dependency-Injection.md) \ No newline at end of file diff --git a/docs/en/Domain-Driven-Design-Implementation-Guide.md b/docs/en/Domain-Driven-Design-Implementation-Guide.md index bf466e04c8..dc28845396 100644 --- a/docs/en/Domain-Driven-Design-Implementation-Guide.md +++ b/docs/en/Domain-Driven-Design-Implementation-Guide.md @@ -1,1982 +1,5 @@ # Implementing Domain Driven Design -## Introduction +This document has been packaged as a **free e-book** and can be downloaded from the following URL: -This is a **practical guide** for implementing the Domain Driven Design (DDD). While the implementation details rely on the ABP Framework infrastructure, core concepts, principles and patterns are applicable in any kind of solution, even if it is not a .NET solution. - -### Goals - -The goals of this document are to - -* **Introduce and explain** the DDD architecture, concepts, principles, patterns and building blocks. -* Explain the **layered architecture** & solution structure offered by the ABP Framework. -* Introduce **explicit rules** to implement DDD patterns and best practices by giving **concrete examples**. -* Show what **ABP Framework provides** you as the infrastructure for implementing DDD in a proper way. -* And finally, provide **suggestions** based on software development **best practices** and our experiences to create a **maintainable codebase**. - -### Simple Code! - -> **Playing football** is very **simple**, but **playing simple football** is the **hardest thing** there is. -> — Johan Cruyff - -If we take this famous quote for programming, we can say; - -> **Writing code** is very **simple**, but **writing simple code** is the **hardest thing** there is. -> — ??? - -In this document, we will introduce **simple rules**, those are **easy to implement**. - -Once your **application grows**, it will be **hard to follow** these rules. Sometimes you find **breaking rules** will save you time in a short term. However, the saved time in the short term will bring much **more time loss** in the middle and long term. Your code base becomes **complicated** and hard to maintain. Most of the business applications are **re-written** just because you **can't maintain** it anymore. - -If you **follow the rules and best practices**, your code base will be simpler and easier to maintain. Your application **reacts to changes** faster. - -## What is the Domain Driven Design? - -Domain-driven design (DDD) is an approach to software development for **complex** needs by connecting the implementation to an **evolving** model; - -DDD is suitable for **complex domains** and **large-scale** applications rather than simple CRUD applications. It focuses on the **core domain logic** rather than the infrastructure details. It helps to build a **flexible**, modular and **maintainable** code base. - -### OOP & SOLID - -Implementing DDD highly relies on the Object Oriented Programming (OOP) and [SOLID](https://en.wikipedia.org/wiki/SOLID) principles. Actually, it **implements** and **extends** these principles. So, a **good understanding** of OOP & SOLID helps you a lot while truly implementing the DDD. - -### DDD Layers & Clean Architecture - -There are four fundamental layers of a Domain Driven Based Solution; - -![domain-driven-design-layers](images/domain-driven-design-layers.png) - -**Business Logic** places into two layers, the *Domain layer* and the *Application Layer*, while they contain different kinds of business logic; - -* **Domain Layer** implements the core, use-case independent business logic of the domain/system. -* **Application Layer** implements the use cases of the application based on the domain. A use case can be thought as a user interaction on the User Interface (UI). -* **Presentation Layer** contains the UI elements (pages, components) of the application. -* **Infrastructure Layer** supports other layer by implementing the abstractions and integrations to 3rd-party library and systems. - -The same layering can be shown as the diagram below and known as the **Clean Architecture**, or sometimes the **Onion Architecture**: - -![domain-driven-design-clean-architecture](images/domain-driven-design-clean-architecture.png) - -In the Clean Architecture, each layer only **depends on the layer directly inside it**. The most independent layer is shown in the most inner circle and it is the Domain Layer. - -### Core Building Blocks - -DDD mostly **focuses on the Domain & Application Layers** and ignores the Presentation and Infrastructure. They are seen as *details* and the business layers should not depend on them. - -That doesn't mean the Presentation and Infrastructure layers are not important. They are very important. UI frameworks and database providers have their own rules and best practices that you need to know and apply. However these are not in the topics of DDD. - -This section introduces the essential building blocks of the Domain & Application Layers. - -#### Domain Layer Building Blocks - -* **Entity**: An [Entity](Entities.md) is an object with its own properties (state, data) and methods that implements the business logic that is executed on these properties. An entity is represented by its unique identifier (Id). Two entity object with different Ids are considered as different entities. -* **Value Object**: A [Value Object](Value-Objects.md) is another kind of domain object that is identified by its properties rather than a unique Id. That means two Value Objects with same properties are considered as the same object. Value objects are generally implemented as immutable and mostly are much simpler than the Entities. -* **Aggregate & Aggregate Root**: An [Aggregate](Entities.md) is a cluster of objects (entities and value objects) bound together by an **Aggregate Root** object. The Aggregate Root is a specific type of an entity with some additional responsibilities. -* **Repository** (interface): A [Repository](Repositories.md) is a collection-like interface that is used by the Domain and Application Layers to access to the data persistence system (the database). It hides the complexity of the DBMS from the business code. Domain Layer contains the `interface`s of the repositories. -* **Domain Service**: A [Domain Service](Domain-Services.md) is a stateless service that implements core business rules of the domain. It is useful to implement domain logic that depends on multiple aggregate (entity) type or some external services. -* **Specification**: A [Specification](Specifications.md) is used to define named, reusable and combinable filters for entities and other business objects. -* **Domain Event**: A [Domain Event](Event-Bus.md) is a way of informing other services in a loosely coupled manner, when a domain specific event occurs. - -#### Application Layer Building Blocks - -* **Application Service**: An [Application Service](Application-Services.md) is a stateless service that implements use cases of the application. An application service typically gets and returns DTOs. It is used by the Presentation Layer. It uses and coordinates the domain objects to implement the use cases. A use case is typically considered as a Unit Of Work. -* **Data Transfer Object (DTO)**: A [DTO](Data-Transfer-Objects.md) is a simple object without any business logic that is used to transfer state (data) between the Application and Presentation Layers. -* **Unit of Work (UOW)**: A [Unit of Work](Unit-Of-Work.md) is an atomic work that should be done as a transaction unit. All the operations inside a UOW should be committed on success or rolled back on a failure. - -## Implementation: The Big Picture - -### Layering of a .NET Solution - -The picture below shows a Visual Studio Solution created using the ABP's [application startup template](Startup-Templates/Application.md): - -![domain-driven-design-vs-solution](images/domain-driven-design-vs-solution.png) - -The solution name is `IssueTracking` and it consists of multiple projects. The solution is layered by considering **DDD principles** as well as **development** and **deployment** practicals. The sub sections below explains the projects in the solution; - -> Your solution structure may be slightly different if you choose a different UI or Database provider. However, the Domain and Application layers will be same and this is the essential point for the DDD perspective. See the [Application Startup Template](Startup-Templates/Application.md) document if you want to know more about the solution structure. - -#### The Domain Layer - -The Domain Layer is splitted into two projects; - -* `IssueTracking.Domain` is the **essential domain layer** that contains all the **building blocks** (entities, value objects, domain services, specifications, repository interfaces, etc.) introduced before. -* `IssueTracking.Domain.Shared` is a thin project that contains some types those belong to the Domain Layer, but shared with all other layers. For example, it may contain some constants and `enum`s related to the Domain Objects but need to be **reused by other layers**. - -#### The Application Layer - -The Application Layer is also splitted into two projects; - -* `IssueTracking.Application.Contracts` contains the application service **interfaces** and the **DTO**s used by these interfaces. This project can be shared by the client applications (including the UI). -* `IssueTracking.Application` is the **essential application layer** that **implements** the interfaces defined in the Contracts project. - -#### The Presentation Layer - -* `IssueTracking.Web` is an ASP.NET Core MVC / Razor Pages application for this example. This is the only executable application that serves the application and the APIs. - -> ABP Framework also supports different kind of UI frameworks including [Angular](UI/Angular/Quick-Start.md) and [Blazor](UI/Blazor/Overall.md). In these cases, the `IssueTracking.Web` doesn't exist in the solution. Instead, an `IssueTracking.HttpApi.Host` application will be in the solution to serve the HTTP APIs as a standalone endpoint to be consumed by the UI applications via HTTP API calls. - -#### The Remote Service Layer - -* `IssueTracking.HttpApi` project contains HTTP APIs defined by the solution. It typically contains MVC `Controller`s and related models, if available. So, you write your HTTP APIs in this project. - -> Most of the time, API Controllers are just wrappers around the Application Services to expose them to the remote clients. Since ABP Framework's [Automatic API Controller System](API/Auto-API-Controllers.md) **automatically configures and exposes your Application Services as API Controllers**, you typically don't create Controllers in this project. However, the startup solution includes it for the cases you need to manually create API controllers. - -* `IssueTracking.HttpApi.Client` project is useful when you have a C# application that needs to consume your HTTP APIs. Once the client application references this project, it can directly [inject](Dependency-Injection.md) & use the Application Services. This is possible by the help of the ABP Framework's [Dynamic C# Client API Proxies System](API/Dynamic-CSharp-API-Clients.md). - -> There is a Console Application in the `test` folder of the solution, named `IssueTracking.HttpApi.Client.ConsoleTestApp`. It simply uses the `IssueTracking.HttpApi.Client` project to consume the APIs exposed by the application. It is just a demo application and you can safely delete it. You can even delete the `IssueTracking.HttpApi.Client` project if you think that you don't need to them. - -#### The Infrastructure Layer - -In a DDD implementation, you may have a single Infrastructure project to implement all the abstractions and integrations, or you may have different projects for each dependency. - -We suggest a balanced approach; Create separate projects for main infrastructure dependencies (like Entity Framework Core) and a common infrastructure project for other infrastructure. - -ABP's startup solution has two projects for the Entity Framework Core integration; - -* `IssueTracking.EntityFrameworkCore` is the essential integration package for the EF Core. Your application's `DbContext`, database mappings, implementations of the repositories and other EF Core related stuff are located here. -* `IssueTracking.EntityFrameworkCore.DbMigrations` is a special project to manage the Code First database migrations. There is a separate `DbContext` in this project to track the migrations. You typically don't touch this project much except you need to create a new database migration or add an [application module](Modules/Index.md) that has some database tables and naturally requires to create a new database migration. - -> You may wonder why there are two projects for the EF Core. It is mostly related to [modularity](Module-Development-Basics.md). Each module has its own independent `DbContext` and your application has also one `DbContext`. `DbMigrations` project contains a **union** of the modules to track and apply a **single migration path**. While most of the time you don't need to know it, you can see the [EF Core migrations](Entity-Framework-Core-Migrations.md) document for more information. - -#### Other Projects - -There is one more project, `IssueTracking.DbMigrator`, that is a simple Console Application that **migrates** the database schema and **[seeds](Data-Seeding.md) the initial** data when you execute it. It is a useful **utility application** that you can use it in development as well as in production environment. - -### Dependencies of the Projects in the Solution - -The diagram below shows the essential dependencies (project references) between the projects in the solution (`IssueTracking.` part is not shown to be simple) - -![domain-driven-design-project-relations](images/domain-driven-design-project-relations.png) - -The projects have been explained before. Now, we can explain the reasons of the dependencies; - -* `Domain.Shared` is the project that all other projects directly or indirectly depend on. So, all the types in this project are available to all projects. -* `Domain` only depends on the `Domain.Shared` because it is already a (shared) part of the domain. For example, an `IssueType` enum in the `Domain.Shared` can be used by an `Issue` entity in the `Domain` project. -* `Application.Contracts` depends on the `Domain.Shared`. In this way, you can reuse these types in the DTOs. For example, the same `IssueType` enum in the `Domain.Shared` can be used by a `CreateIssueDto` as a property. -* `Application` depends on the `Application.Contracts` since it implements the Application Service interfaces and uses the DTOs inside it. It also depends on the `Domain` since the Application Services are implemented using the Domain Objects defined inside it. -* `EntityFrameworkCore` depends on the `Domain` since it maps the Domain Objects (entities and value types) to database tables (as it is an ORM) and implements the repository interfaces defined in the `Domain`. -* `HttpApi` depends on the `Application.Contracts` since the Controllers inside it inject and use the Application Service interfaces as explained before. -* `HttpApi.Client` depends on the `Application.Contracts` since it can consume the Application Services as explained before. -* `Web` depends on the `HttpApi` since it serves the HTTP APIs defined inside it. Also, in this way, it indirectly depends on the `Application.Contracts` project to consume the Application Services in the Pages/Components. - -#### Dashed Dependencies - -When you investigate the solution, you will see two more dependencies shown with the dashed lines in the figure above. `Web` project depends on the `Application` and `EntityFrameworkCore` projects which *theoretically* should not be like that but actually it is. - -This is because the `Web` is the final project that runs and hosts the application and the **application needs the implementations of the Application Services and the Repositories** while running. - -This design decision potentially allows you to use Entities and EF Core objects in the Presentation Layer which **should be strictly avoided**. However, we find the alternative designs over complicated. Here, two of the alternatives if you want to remove this dependency; - -* Convert `Web` project to a razor class library and create a new project, like `Web.Host`, that depends on the `Web`, `Application` and `EntityFrameworkCore` projects and hosts the application. You don't write any UI code here, but use **only for hosting**. -* Remove `Application` and `EntityFrameworkCore` dependencies from the `Web` project and load their assemblies on application initialization. You can use ABP's [Plug-In Modules](PlugIn-Modules.md) system for that purpose. - -### Execution Flow a DDD Based Application - -The figure below shows a typical request flow for a web application that has been developed based on DDD patterns. - -![](images/domain-driven-design-web-request-flow.png) - -* The request typically begins with a user interaction on the UI (a *use case*) that causes an HTTP request to the server. -* An MVC Controller or a Razor Page Handler in the Presentation Layer (or in the Distributed Services Layer) handles the request and can perform some cross cutting concerns in this stage ([Authorization](Authorization.md), [Validation](Validation.md), [Exception Handling](Exception-Handling.md), etc.). A Controller/Page injects the related Application Service interface and calls its method(s) by sending and receiving DTOs. -* The Application Service uses the Domain Objects (Entities, Repository interfaces, Domain Services, etc.) to implement the *use case*. Application Layer implements some cross cutting concerns (Authorization, Validation, etc.). An Application Service method should be a [Unit Of Work](Unit-Of-Work.md). That means it should be atomic. - -Most of the cross cutting concerns are **automatically and conventionally implemented by the ABP Framework** and you typically don't need to write code for them. - -### Common Principles - -Before going into details, let's see some overall DDD principles; - -#### Database Provider / ORM Independence - -The domain and the application layers should be ORM / Database Provider agnostic. They should only depend on the Repository interfaces and the Repository interfaces don't use any ORM specific objects. - -Here, the main reasons of this principle; - -1. To make your domain/application **infrastructure independent** since the infrastructure may change in the future or you may need to support a second database type later. -2. To make your domain/application **focus on the business code** by hiding the infrastructure details behind the repositories. -3. To make your **automated tests** easier since you can mock the repositories in this case. - -> As a respect to this principle, none of the projects in the solution has reference to the `EntityFrameworkCore` project, except the startup application. - -##### Discussion About the Database Independence Principle - -Especially, the **reason 1** deeply effects your domain **object design** (especially, the entity relations) and **application code**. Assume that you are using [Entity Framework Core](Entity-Framework-Core.md) with a relational database. If you are willing to make your application switchable to [MongoDB](MongoDB.md) later, you can't use some very **useful EF Core features**. Examples; - -* You can't assume [Change Tracking](https://docs.microsoft.com/en-us/ef/core/querying/tracking) since MongoDB provider can't do it. So, you always need to explicitly update the changed entities. -* You can't use [Navigation Properties](https://docs.microsoft.com/en-us/ef/core/modeling/relationships) (or Collections) to other Aggregates in your entities since this is not possible for a Document Database. See the "Rule: Reference Other Aggregates Only By Id" section for more info. - -If you think such features are **important** for you and you **will never stray** from the EF Core, we believe that it is worth **stretching this principle**. We still suggest to use the repository pattern to hide the infrastructure details. But you can assume that you are using EF Core while designing your entity relations and writing your application code. You can even reference to the EF Core NuGet Package from your application layer to be able to directly use the asynchronous LINQ extension methods, like `ToListAsync()` (see the *IQueryable & Async Operations* section in the [Repositories](Repositories.md) document for more info). - -#### Presentation Technology Agnostic - -The presentation technology (UI Framework) is one of the most changed parts of a real world application. It is very important to design the **Domain and Application Layers** to be completely **unaware** of the presentation technology/framework. This principle is relatively easy to implement and ABP's startup template makes it even easier. - -In some cases, you may need to have **duplicate logic** in the application and presentation layers. For example, you may need to duplicate the **validation** and **authorization** checks in both layers. The checks in the UI layer is mostly for **user experience** while checks in the application and domain layers are for **security and data integrity**. That's perfectly normal and necessary. - -#### Focus on the State Changes, Not Reporting - -DDD focuses on how the domain objects **changes and interactions**; How to create an entity and change its properties by preserving the data **integrity/validity** and implementing the **business rules**. - -DDD **ignores reporting** and mass querying. That doesn't mean they are not important. If your application doesn't have fancy dashboards and reports, who would use it? However, reporting is another topic. You typically want to use the full power of the SQL Server or even use a separate data source (like ElasticSearch) for reporting purpose. You will write optimized queries, create indexes and even stored procedures(!). You are free to do all these things as long as you don't infect them into your business logic. - -## Implementation: The Building Blocks - -This is the essential part of this guide. We will introduce and explain some **explicit rules** with examples. You can follow these rules and apply in your solution while implementing the Domain Driven Design. - -### The Example Domain - -The examples will use some concepts those are used by GitHub, like `Issue`, `Repository`, `Label` and `User`, you are already familiar with. The figure below shows some of the aggregates, aggregate roots, entities, value object and the relations between them: - -![domain driven design example schema](images/domain-driven-design-example-domain-schema.png) - -**Issue Aggregate** consists of an `Issue` Aggregate Root that contains `Comment` and `IssueLabel` collections. Other aggregates are shown as simple since we will focus on the Issue Aggregate: - -![domain-driven-design-issue-aggregate-diagram](images/domain-driven-design-issue-aggregate-diagram.png) - -### Aggregates - -As said before, an [Aggregate](Entities.md) is a cluster of objects (entities and value objects) bound together by an Aggregate Root object. This section will introduce the principles and rules related to the Aggregates. - -> We refer the term *Entity* both for *Aggregate Root* and *sub-collection entities* unless we explicitly write *Aggregate Root* or *sub-collection entity*. - -#### Aggregate / Aggregate Root Principles - -##### Business Rules - -Entities are responsible to implement the business rules related to the properties of their own. The *Aggregate Root Entities* are also responsible for their sub-collection entities. - -An aggregate should maintain its self **integrity** and **validity** by implementing domain rules and constraints. That means, unlike the DTOs, Entities have **methods to implement some business logic**. Actually, we should try to implement business rules in the entities wherever possible. - -##### Single Unit - -An aggregate is **retrieved and saved as a single unit**, with all the sub-collections and properties. For example, if you want to add a `Comment` to an `Issue`, you need to; - -* Get the `Issue` from database with including all the sub-collections (`Comment`s and `IssueLabel`s). -* Use methods on the `Issue` class to add a new comment, like `Issue.AddComment(...);`. -* Save the `Issue` (with all sub-collections) to the database as a single database operation (update). - -That may seem strange to the developers used to work with **EF Core & Relational Databases** before. Getting the `Issue` with all details seems **unnecessary and inefficient**. Why don't we just execute an SQL `Insert` command to database without querying any data? - -The answer is that we should **implement the business** rules and preserve the data **consistency** and **integrity** in the **code**. If we have a business rule like "*Users can not comment on the locked issues*", how can we check the `Issue`'s lock state without retrieving it from the database? So, we can execute the business rules only if the related objects available in the application code. - -On the other hand, **MongoDB** developers will find this rule very natural. In MongoDB, an aggregate object (with sub-collections) is saved in a **single collection** in the database (while it is distributed into several tables in a relational database). So, when you get an aggregate, all the sub-collections are already retrieved as a part of the query, without any additional configuration. - -ABP Framework helps to implement this principle in your applications. - -**Example: Add a comment to an issue** - -````csharp -public class IssueAppService : ApplicationService, IIssueAppService -{ - private readonly IRepository _issueRepository; - - public IssueAppService(IRepository issueRepository) - { - _issueRepository = issueRepository; - } - - [Authorize] - public async Task CreateCommentAsync(CreateCommentDto input) - { - var issue = await _issueRepository.GetAsync(input.IssueId); - issue.AddComment(CurrentUser.GetId(), input.Text); - await _issueRepository.UpdateAsync(issue); - } -} -```` - -`_issueRepository.GetAsync` method retrieves the `Issue` with all details (sub-collections) as a single unit by default. While this works out of the box for MongoDB, you need to configure your aggregate details for the EF Core. But, once you configure, repositories automatically handle it. `_issueRepository.GetAsync` method gets an optional parameter, `includeDetails`, that you can pass `false` to disable this behavior when you need it. - -> See the *Loading Related Entities* section of the [EF Core document](Entity-Framework-Core.md) for the configuration and alternative scenarios. - -`Issue.AddComment` gets a `userId` and comment `text`, implements the necessary business rules and adds the comment to the Comments collection of the `Issue`. - -Finally, we use `_issueRepository.UpdateAsync` to save changes to the database. - -> EF Core has a **change tracking** feature. So, you actually don't need to call `_issueRepository.UpdateAsync`. It will be automatically saved thanks to ABP's Unit Of Work system that automatically calls `DbContext.SaveChanges()` at the end of the method. However, for MongoDB, you need to explicitly update the changed entity. -> -> So, if you want to write your code Database Provider independent, you should always call the `UpdateAsync` method for the changed entities. - -##### Transaction Boundary - -An aggregate is generally considered as a transaction boundary. If a use case works with a single aggregate, reads and saves it as a single unit, all the changes made to the aggregate objects are saved together as an atomic operation and you don't need to an explicit database transaction. - -However, in real life, you may need to change **more than one aggregate instances** in a single use case and you need to use database transactions to ensure **atomic update** and **data consistency**. Because of that, ABP Framework uses an explicit database transaction for a use case (an application service method boundary). See the [Unit Of Work](Unit-Of-Work.md) documentation for more info. - -##### Serializability - -An aggregate (with the root entity and sub-collections) should be serializable and transferrable on the wire as a single unit. For example, MongoDB serializes the aggregate to JSON document while saving to the database and deserializes from JSON while reading from the database. - -> This requirement is not necessary when you use relational databases and ORMs. However, it is an important practice of Domain Driven Design. - -The following rules will already bring the serializability. - -#### Aggregate / Aggregate Root Rules & Best Practices - -The following rules ensures implementing the principles introduced above. - -##### Reference Other Aggregates Only By Id - -The first rule says an Aggregate should reference to other aggregates only by their Id. That means you can not add navigation properties to other aggregates. - -* This rule makes it possible to implement the serializability principle. -* It also prevents different aggregates manipulate each other and leaking business logic of an aggregate to one another. - -You see two aggregate roots, `GitRepository` and `Issue` in the example below; - -![domain-driven-design-reference-by-id-sample](images/domain-driven-design-reference-by-id-sample.png) - -* `GitRepository` should not have a collection of the `Issue`s since they are different aggregates. -* `Issue` should not have a navigation property for the related `GitRepository` since it is a different aggregate. -* `Issue` can have `RepositoryId` (as a `Guid`). - -So, when you have an `Issue` and need to have `GitRepository` related to this issue, you need to explicitly query it from database by the `RepositoryId`. - -###### For EF Core & Relational Databases - -In MongoDB, it is naturally not suitable to have such navigation properties/collections. If you do that, you find a copy of the destination aggregate object in the database collection of the source aggregate since it is being serialized to JSON on save. - -However, EF Core & relational database developers may find this restrictive rule unnecessary since EF Core can handle it on database read and write. We see this an important rule that helps to **reduce the complexity** of the domain prevents potential problems and we strongly suggest to implement this rule. However, if you think it is practical to ignore this rule, see the *Discussion About the Database Independence Principle* section above. - -##### Keep Aggregates Small - -One good practice is to keep an aggregate simple and small. This is because an aggregate will be loaded and saved as a single unit and reading/writing a big object has performance problems. See the example below: - -![domain-driven-design-aggregate-keep-small](images/domain-driven-design-aggregate-keep-small.png) - -Role aggregate has a collection of `UserRole` value objects to track the users assigned for this role. Notice that `UserRole` is not another aggregate and it is not a problem for the rule *Reference Other Aggregates Only By Id*. However, it is a problem in practical. A role may be assigned to thousands (even millions) of users in a real life scenario and it is a significant performance problem to load thousands of items whenever you query a `Role` from database (remember: Aggregates are loaded by their sub-collections as a single unit). - -On the other hand, `User` may have such a `Roles` collection since a user doesn't have much roles in practical and it can be useful to have a list of roles while you are working with a User Aggregate. - -If you think carefully, there is one more problem when Role and User both have the list of relation when use a **non-relational database, like MongoDB**. In this case, the same information is duplicated in different collections and it will be hard to maintain data consistency (whenever you add an item to `User.Roles`, you need to add it to `Role.Users` too). - -So, determine your aggregate boundaries and size based on the following considerations; - -* Objects used together. -* Query (load/save) performance and memory consumption. -* Data integrity, validity and consistency. - -In practical; - -* Most of the aggregate roots will **not have sub-collections**. -* A sub-collection should not have more than **100-150 items** inside it at the most case. If you think a collection potentially can have more items, don't define the collection as a part of the aggregate and consider to extract another aggregate root for the entity inside the collection. - -##### Primary Keys of the Aggregate Roots / Entities - -* An aggregate root typically has a single `Id` property for its identifier (Primark Key: PK). We prefer `Guid` as the PK of an aggregate root entity (see the [Guid Genertation document](Guid-Generation.md) to learn why). -* An entity (that's not the aggregate root) in an aggregate can use a composite primary key. - -For example, see the Aggregate root and the Entity below: - -![domain-driven-design-entity-primary-keys](images/domain-driven-design-entity-primary-keys.png) - -* `Organization` has a `Guid` identifier (`Id`). -* `OrganizationUser` is a sub-collection of an `Organization` and has a composite primary key consists of the `OrganizationId` and `UserId`. - -That doesn't mean sub-collection entities should always have composite PKs. They may have single `Id` properties when it's needed. - -> Composite PKs are actually a concept of relational databases since the sub-collection entities have their own tables and needs to a PK. On the other hand, for example, in MongoDB you don't need to define PK for the sub-collection entities at all since they are stored as a part of the aggregate root. - -##### Constructors of the Aggregate Roots / Entities - -The constructor is located where the lifecycle of an entity begins. There are a some responsibilities of a well designed constructor: - -* Gets the **required entity properties** as parameters to **create a valid entity**. Should force to pass only for the required parameters and may get non-required properties as optional parameters. -* **Checks validity** of the parameters. -* Initializes **sub-collections**. - -**Example: `Issue` (Aggregate Root) constructor** - -````csharp -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using Volo.Abp; -using Volo.Abp.Domain.Entities; - -namespace IssueTracking.Issues -{ - public class Issue : AggregateRoot - { - public Guid RepositoryId { get; set; } - public string Title { get; set; } - public string Text { get; set; } - public Guid? AssignedUserId { get; set; } - public bool IsClosed { get; set; } - public IssueCloseReason? CloseReason { get; set; } //enum - - public ICollection Labels { get; set; } - - public Issue( - Guid id, - Guid repositoryId, - string title, - string text = null, - Guid? assignedUserId = null - ) : base(id) - { - RepositoryId = repositoryId; - Title = Check.NotNullOrWhiteSpace(title, nameof(title)); - - Text = text; - AssignedUserId = assignedUserId; - - Labels = new Collection(); - } - - private Issue() { /* for deserialization & ORMs */ } - } -} -```` - -* `Issue` class properly **forces to create a valid entity** by getting minimum required properties in its constructor as parameters. -* The constructor **validates** the inputs (`Check.NotNullOrWhiteSpace(...)` throws `ArgumentException` if the given value is empty). -* It **initializes the sub-collections**, so you don't get a null reference exception when you try to use the `Labels` collection after creating the `Issue`. -* The constructor also **takes the `id`** and passes to the `base` class. We don't generate `Guid`s inside the constructor to be able to delegate this responsibility to another service (see [Guid Generation](Guid-Generation.md)). -* Private **empty constructor** is necessary for ORMs. We made it `private` to prevent accidently using it in our own code. - -> See the [Entities](Entities.md) document to learn more about creating entities with the ABP Framework. - -##### Entity Property Accessors & Methods - -The example above may seem strange to you! For example, we force to pass a non-null `Title` in the constructor. However, the developer may then set the `Title` property to `null` without any control. This is because the example code above just focuses on the constructor. - -If we declare all the properties with **public setters** (like the example `Issue` class above), we can't force **validity** and **integrity** of the entity in its lifecycle. So; - -* Use **private setter** for a property when you need to perform any **logic** while setting that property. -* Define public methods to manipulate such properties. - -**Example: Methods to change the properties in a controlled way** - -````csharp -using System; -using Volo.Abp; -using Volo.Abp.Domain.Entities; - -namespace IssueTracking.Issues -{ - public class Issue : AggregateRoot - { - public Guid RepositoryId { get; private set; } //Never changes - public string Title { get; private set; } //Needs validation - public string Text { get; set; } //No validation - public Guid? AssignedUserId { get; set; } //No validation - public bool IsClosed { get; private set; } //Should be changed with CloseReason - public IssueCloseReason? CloseReason { get; private set; } //Should be changed with IsClosed - - //... - - public void SetTitle(string title) - { - Title = Check.NotNullOrWhiteSpace(title, nameof(title)); - } - - public void Close(IssueCloseReason reason) - { - IsClosed = true; - CloseReason = reason; - } - - public void ReOpen() - { - IsClosed = false; - CloseReason = null; - } - } -} -```` - -* `RepositoryId` setter made private and there is no way to change it after creating an `Issue` because this is what we want in this domain: An issue can't be moved to another repository. -* `Title` setter made private and `SetTitle` method has been created if you want to change it later in a controlled way. -* `Text` and `AssignedUserId` has public setters since there is no restriction on them. They can be null or any other value. We think it is unnecessary to define separate methods to set them. If we need later, we can add methods and make the setters private. Breaking changes are not problem in the domain layer since the domain layer is an internal project, it is not exposed to clients. -* `IsClosed` and `IssueCloseReason` are pair properties. Defined `Close` and `ReOpen` methods to change them together. In this way, we prevent to close an issue without any reason. - -##### Business Logic & Exceptions in the Entities - -When you implement validation and business logic in the entities, you frequently need to manage the exceptional cases. In these cases; - -* Create **domain specific exceptions**. -* **Throw these exceptions** in the entity methods when necessary. - -**Example** - -````csharp -public class Issue : AggregateRoot -{ - //... - - public bool IsLocked { get; private set; } - public bool IsClosed { get; private set; } - public IssueCloseReason? CloseReason { get; private set; } - - public void Close(IssueCloseReason reason) - { - IsClosed = true; - CloseReason = reason; - } - - public void ReOpen() - { - if (IsLocked) - { - throw new IssueStateException( - "Can not open a locked issue! Unlock it first." - ); - } - - IsClosed = false; - CloseReason = null; - } - - public void Lock() - { - if (!IsClosed) - { - throw new IssueStateException( - "Can not open a locked issue! Unlock it first." - ); - } - - IsLocked = true; - } - - public void Unlock() - { - IsLocked = false; - } -} -```` - -There are two business rules here; - -* A locked issue can not be re-opened. -* You can not lock an open issue. - -`Issue` class throws an `IssueStateException` in these cases to force the business rules: - -````csharp -using System; - -namespace IssueTracking.Issues -{ - public class IssueStateException : Exception - { - public IssueStateException(string message) - : base(message) - { - - } - } -} -```` - -There are two potential problems of throwing such exceptions; - -1. In case of such an exception, should the **end user** see the exception (error) message? If so, how do you **localize** the exception message? You can not use the [localization](Localization.md) system, because you can't inject and use `IStringLocalizer` in the entities. -2. For a web application or HTTP API, what **HTTP Status Code** should return to the client? - -ABP's [Exception Handling](Exception-Handling.md) system solves these and similar problems. - -**Example: Throwing a business exception with code** - -````csharp -using Volo.Abp; - -namespace IssueTracking.Issues -{ - public class IssueStateException : BusinessException - { - public IssueStateException(string code) - : base(code) - { - - } - } -} -```` - -* `IssueStateException` class inherits the `BusinessException` class. ABP returns 403 (forbidden) HTTP Status code by default (instead of 500 - Internal Server Error) for the exceptions derived from the `BusinessException`. -* The `code` is used as a key in the localization resource file to find the localized message. - -Now, we can change the `ReOpen` method as shown below: - -````csharp -public void ReOpen() -{ - if (IsLocked) - { - throw new IssueStateException("IssueTracking:CanNotOpenLockedIssue"); - } - - IsClosed = false; - CloseReason = null; -} -```` - -> Use constants instead of magic strings. - -And add an entry to the localization resource like below: - -````json -"IssueTracking:CanNotOpenLockedIssue": "Can not open a locked issue! Unlock it first." -```` - -* When you throw the exception, ABP automatically uses this localized message (based on the current language) to show to the end user. -* The exception code (`IssueTracking:CanNotOpenLockedIssue` here) is also sent to the client, so it may handle the error case programmatically. - -> For this example, you could directly throw `BusinessException` instead of defining a specialized `IssueStateException`. The result will be same. See the [exception handling document](Exception-Handling.md) for all the details. - -##### Business Logic in Entities Requiring External Services - -It is simple to implement a business rule in an entity method when the business logic only uses the properties of that entity. What if the business logic requires to **query database** or **use any external services** that should be resolved from the [dependency injection](Dependency-Injection.md) system. Remember; **Entities can not inject services!** - -There are two common ways of implementing such a business logic: - -* Implement the business logic on an entity method and **get external dependencies as parameters** of the method. -* Create a **Domain Service**. - -Domain Services will be explained later. But, now let's see how it can be implemented in the entity class. - -**Example: Business Rule: Can not assign more than 3 open issues to a user concurrently** - -````csharp -public class Issue : AggregateRoot -{ - //... - public Guid? AssignedUserId { get; private set; } - - public async Task AssignToAsync(AppUser user, IUserIssueService userIssueService) - { - var openIssueCount = await userIssueService.GetOpenIssueCountAsync(user.Id); - - if (openIssueCount >= 3) - { - throw new BusinessException("IssueTracking:ConcurrentOpenIssueLimit"); - } - - AssignedUserId = user.Id; - } - - public void CleanAssignment() - { - AssignedUserId = null; - } -} -```` - -* `AssignedUserId` property setter made private. So, the only way to change it to use the `AssignToAsync` and `CleanAssignment` methods. -* `AssignToAsync` gets an `AppUser` entity. Actually, it only uses the `user.Id`, so you could get a `Guid` value, like `userId`. However, this way ensures that the `Guid` value is `Id` of an existing user and not a random `Guid` value. -* `IUserIssueService` is an arbitrary service that is used to get open issue count for a user. It's the responsibility of the code part (that calls the `AssignToAsync`) to resolve the `IUserIssueService` and pass here. -* `AssignToAsync` throws exception if the business rule doesn't meet. -* Finally, if everything is correct, `AssignedUserId` property is set. - -This method perfectly guarantees to apply the business logic when you want to assign an issue to a user. However, it has some problems; - -* It makes the entity class **depending on an external service** which makes the entity **complicated**. -* It makes **hard to use** the entity. The code that uses the entity now needs to inject `IUserIssueService` and pass to the `AssignToAsync` method. - -An alternative way of implementing this business logic is to introduce a **Domain Service**, which will be explained later. - -### Repositories - -A [Repository](Repositories.md) is a collection-like interface that is used by the Domain and Application Layers to access to the data persistence system (the database) to read and write the Business Objects, generally the Aggregates. - -Common Repository principles are; - -* Define a repository **interface in the Domain Layer** (because it is used in the Domain and Application Layers), **implement in the Infrastructure Layer** (*EntityFrameworkCore* project in the startup template). -* **Do not include business logic** inside the repositories. -* Repository interface should be **database provider / ORM independent**. For example, do not return a `DbSet` from a repository method. `DbSet` is an object provided by the EF Core. -* **Create repositories for aggregate roots**, not for all entities. Because, sub-collection entities (of an aggregate) should be accessed over the aggregate root. - -#### Do Not Include Domain Logic in Repositories - -While this rule seems obvious at the beginning, it is easy to leak business logic into repositories. - -**Example: Get inactive issues from a repository** - -````csharp -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Volo.Abp.Domain.Repositories; - -namespace IssueTracking.Issues -{ - public interface IIssueRepository : IRepository - { - Task> GetInActiveIssuesAsync(); - } -} -```` - -`IIssueRepository` extends the standard `IRepository<...>` interface by adding a `GetInActiveIssuesAsync` method. This repository works with such an `Issue` class: - -````csharp -public class Issue : AggregateRoot, IHasCreationTime -{ - public bool IsClosed { get; private set; } - public Guid? AssignedUserId { get; private set; } - public DateTime CreationTime { get; private set; } - public DateTime? LastCommentTime { get; private set; } - //... -} -```` - -(the code shows only the properties we need for this example) - -The rule says the repository shouldn't know the business rules. The question here is "**What is an inactive issue**? Is it a business rule definition?" - -Let's see the implementation to understand it: - -````csharp -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using IssueTracking.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Volo.Abp.Domain.Repositories.EntityFrameworkCore; -using Volo.Abp.EntityFrameworkCore; - -namespace IssueTracking.Issues -{ - public class EfCoreIssueRepository : - EfCoreRepository, - IIssueRepository - { - public EfCoreIssueRepository( - IDbContextProvider dbContextProvider) - : base(dbContextProvider) - { - } - - public async Task> GetInActiveIssuesAsync() - { - var daysAgo30 = DateTime.Now.Subtract(TimeSpan.FromDays(30)); - - var dbSet = await GetDbSetAsync(); - return await dbSet.Where(i => - - //Open - !i.IsClosed && - - //Assigned to nobody - i.AssignedUserId == null && - - //Created 30+ days ago - i.CreationTime < daysAgo30 && - - //No comment or the last comment was 30+ days ago - (i.LastCommentTime == null || i.LastCommentTime < daysAgo30) - - ).ToListAsync(); - } - } -} -```` - -(Used EF Core for the implementation. See the [EF Core integration document](Entity-Framework-Core.md) to learn how to create custom repositories with the EF Core.) - -When we check the `GetInActiveIssuesAsync` implementation, we see a **business rule that defines an in-active issue**: The issue should be **open**, **assigned to nobody**, **created 30+ days ago** and has **no comment in the last 30 days**. - -This is an implicit definition of a business rule that is hidden inside a repository method. The problem occurs when we need to reuse this business logic. - -For example, let's say that we want to add an `bool IsInActive()` method on the `Issue` entity. In this way, we can check activeness when we have an issue entity. - -Let's see the implementation: - -````csharp -public class Issue : AggregateRoot, IHasCreationTime -{ - public bool IsClosed { get; private set; } - public Guid? AssignedUserId { get; private set; } - public DateTime CreationTime { get; private set; } - public DateTime? LastCommentTime { get; private set; } - //... - - public bool IsInActive() - { - var daysAgo30 = DateTime.Now.Subtract(TimeSpan.FromDays(30)); - return - //Open - !IsClosed && - - //Assigned to nobody - AssignedUserId == null && - - //Created 30+ days ago - CreationTime < daysAgo30 && - - //No comment or the last comment was 30+ days ago - (LastCommentTime == null || LastCommentTime < daysAgo30); - } -} -```` - -We had to copy/paste/modify the code. What if the definition of the activeness changes? We should not forget to update both places. This is a duplication of a business logic, which is pretty dangerous. - -A good solution to this problem is the *Specification Pattern*! - -### Specifications - -A [specification](Specifications.md) is a **named**, **reusable**, **combinable** and **testable** class to filter the Domain Objects based on the business rules. - -ABP Framework provides necessary infrastructure to easily create specification classes and use them inside your application code. Let's implement the in-active issue filter as a specification class: - -````csharp -using System; -using System.Linq.Expressions; -using Volo.Abp.Specifications; - -namespace IssueTracking.Issues -{ - public class InActiveIssueSpecification : Specification - { - public override Expression> ToExpression() - { - var daysAgo30 = DateTime.Now.Subtract(TimeSpan.FromDays(30)); - return i => - - //Open - !i.IsClosed && - - //Assigned to nobody - i.AssignedUserId == null && - - //Created 30+ days ago - i.CreationTime < daysAgo30 && - - //No comment or the last comment was 30+ days ago - (i.LastCommentTime == null || i.LastCommentTime < daysAgo30); - } - } -} -```` - -`Specification` base class simplifies to create a specification class by defining an expression. Just moved the expression here, from the repository. - -Now, we can re-use the `InActiveIssueSpecification` in the `Issue` entity and `EfCoreIssueRepository` classes. - -#### Using within the Entity - -`Specification` class provides an `IsSatisfiedBy` method that returns `true` if the given object (entity) satisfies the specification. We can re-write the `Issue.IsInActive` method as shown below: - -````csharp -public class Issue : AggregateRoot, IHasCreationTime -{ - public bool IsClosed { get; private set; } - public Guid? AssignedUserId { get; private set; } - public DateTime CreationTime { get; private set; } - public DateTime? LastCommentTime { get; private set; } - //... - - public bool IsInActive() - { - return new InActiveIssueSpecification().IsSatisfiedBy(this); - } -} -```` - -Just created a new instance of the `InActiveIssueSpecification` and used its `IsSatisfiedBy` method to re-use the expression defined by the specification. - -#### Using with the Repositories - -First, starting from the repository interface: - -````csharp -public interface IIssueRepository : IRepository -{ - Task> GetIssuesAsync(ISpecification spec); -} -```` - -Renamed `GetInActiveIssuesAsync` to simple `GetIssuesAsync` by taking a specification object. Since the **specification (the filter) has been moved out of the repository**, we no longer need to create different methods to get issues with different conditions (like `GetAssignedIssues(...)`, `GetLockedIssues(...)`, etc.) - -Updated implementation of the repository can be like that: - -````csharp -public class EfCoreIssueRepository : - EfCoreRepository, - IIssueRepository -{ - public EfCoreIssueRepository( - IDbContextProvider dbContextProvider) - : base(dbContextProvider) - { - } - - public async Task> GetIssuesAsync(ISpecification spec) - { - var dbSet = await GetDbSetAsync(); - return await dbSet - .Where(spec.ToExpression()) - .ToListAsync(); - } -} -```` - -Since `ToExpression()` method returns an expression, it can be directly passed to the `Where` method to filter the entities. - -Finally, we can pass any Specification instance to the `GetIssuesAsync` method: - -````csharp -public class IssueAppService : ApplicationService, IIssueAppService -{ - private readonly IIssueRepository _issueRepository; - - public IssueAppService(IIssueRepository issueRepository) - { - _issueRepository = issueRepository; - } - - public async Task DoItAsync() - { - var issues = await _issueRepository.GetIssuesAsync( - new InActiveIssueSpecification() - ); - } -} -```` - -##### With Default Repositories - -Actually, you don't have to create custom repositories to be able to use specifications. The standard `IRepository` already extends the `IQueryable`, so you can use the standard LINQ extension methods over it: - -````csharp -public class IssueAppService : ApplicationService, IIssueAppService -{ - private readonly IRepository _issueRepository; - - public IssueAppService(IRepository issueRepository) - { - _issueRepository = issueRepository; - } - - public async Task DoItAsync() - { - var queryable = await _issueRepository.GetQueryableAsync(); - var issues = AsyncExecuter.ToListAsync( - queryable.Where(new InActiveIssueSpecification()) - ); - } -} -```` - -`AsyncExecuter` is a utility provided by the ABP Framework to use asynchronous LINQ extension methods (like `ToListAsync` here) without depending on the EF Core NuGet package. See the [Repositories document](Repositories.md) for more information. - -#### Combining the Specifications - -One powerful side of the Specifications is they are combinable. Assume that we have another specification that returns `true` only if the `Issue` is in a Milestone: - -````csharp -public class MilestoneSpecification : Specification -{ - public Guid MilestoneId { get; } - - public MilestoneSpecification(Guid milestoneId) - { - MilestoneId = milestoneId; - } - - public override Expression> ToExpression() - { - return i => i.MilestoneId == MilestoneId; - } -} -```` - -This Specification is *parametric* as a difference from the `InActiveIssueSpecification`. We can combine both specifications to get a list of inactive issues in a specific milestone: - -````csharp -public class IssueAppService : ApplicationService, IIssueAppService -{ - private readonly IRepository _issueRepository; - - public IssueAppService(IRepository issueRepository) - { - _issueRepository = issueRepository; - } - - public async Task DoItAsync(Guid milestoneId) - { - var queryable = await _issueRepository.GetQueryableAsync(); - var issues = AsyncExecuter.ToListAsync( - queryable - .Where( - new InActiveIssueSpecification() - .And(new MilestoneSpecification(milestoneId)) - .ToExpression() - ) - ); - } -} -```` - -The example above uses the `And` extension method to combine the specifications. There are more combining methods are available, like `Or(...)` and `AndNot(...)`. - -> See the [Specifications document](Specifications.md) for more details about the specification infrastructure provided by the ABP Framework. - -### Domain Services - -Domain Services implement domain logic which; - -* Depends on **services and repositories**. -* Needs to work with **multiple aggregates**, so the logic doesn't properly fit in any of the aggregates. - -Domain Services work with Domain Objects. Their methods can **get and return entities, value objects, primitive types**... etc. However, **they don't get/return DTOs**. DTOs is a part of the Application Layer. - -**Example: Assigning an issue to a user** - -Remember how an issue assignment has been implemented in the `Issue` entity: - -````csharp -public class Issue : AggregateRoot -{ - //... - public Guid? AssignedUserId { get; private set; } - - public async Task AssignToAsync(AppUser user, IUserIssueService userIssueService) - { - var openIssueCount = await userIssueService.GetOpenIssueCountAsync(user.Id); - - if (openIssueCount >= 3) - { - throw new BusinessException("IssueTracking:ConcurrentOpenIssueLimit"); - } - - AssignedUserId = user.Id; - } - - public void CleanAssignment() - { - AssignedUserId = null; - } -} -```` - -Here, we will move this logic into a Domain Service. - -First, changing the `Issue` class: - -````csharp -public class Issue : AggregateRoot -{ - //... - public Guid? AssignedUserId { get; internal set; } -} -```` - -* Removed the assign-related methods. -* Changed `AssignedUserId` property's setter from `private` to `internal`, to allow to set it from the Domain Service. - -The next step is to create a domain service, named `IssueManager`, that has `AssignToAsync` to assign the given issue to the given user. - -````csharp -public class IssueManager : DomainService -{ - private readonly IRepository _issueRepository; - - public IssueManager(IRepository issueRepository) - { - _issueRepository = issueRepository; - } - - public async Task AssignToAsync(Issue issue, AppUser user) - { - var openIssueCount = await _issueRepository.CountAsync( - i => i.AssignedUserId == user.Id && !i.IsClosed - ); - - if (openIssueCount >= 3) - { - throw new BusinessException("IssueTracking:ConcurrentOpenIssueLimit"); - } - - issue.AssignedUserId = user.Id; - } -} -```` - -`IssueManager` can inject any service dependency and use to query open issue count on the user. - -> We prefer and suggest to use the `Manager` suffix for the Domain Services. - -The only problem of this design is that `Issue.AssignedUserId` is now open to set out of the class. However, it is not `public`. It is `internal` and changing it is possible only inside the same Assembly, the `IssueTracking.Domain` project for this example solution. We think this is reasonable; - -* Domain Layer developers are already aware of domain rules and they use the `IssueManager`. -* Application Layer developers are already forces to use the `IssueManager` since they don't directly set it. - -While there is a tradeoff between two approaches, we prefer to create Domain Services when the business logic requires to work with external services. - -> If you don't have a good reason, we think **there is no need to create interfaces** (like `IIssueManager` for the `IssueManager`) for Domain Services. - -### Application Services - -An [Application Service](Application-Services.md) is a stateless service that implements **use cases** of the application. An application service typically **gets and returns DTOs**. It is used by the Presentation Layer. It **uses and coordinates the domain objects** (entities, repositories, etc.) to implement the use cases. - -Common principles of an application service are; - -* Implement the **application logic** that is specific to the current use-case. Do not implement the core domain logic inside the application services. We will come back to differences between Application Domain logics. -* **Never get or return entities** for an application service method. This breaks the encapsulation of the Domain Layer. Always get and return DTOs. - -**Example: Assign an Issue to a User** - -````csharp -using System; -using System.Threading.Tasks; -using IssueTracking.Users; -using Microsoft.AspNetCore.Authorization; -using Volo.Abp.Application.Services; -using Volo.Abp.Domain.Repositories; - -namespace IssueTracking.Issues -{ - public class IssueAppService : ApplicationService, IIssueAppService - { - private readonly IssueManager _issueManager; - private readonly IRepository _issueRepository; - private readonly IRepository _userRepository; - - public IssueAppService( - IssueManager issueManager, - IRepository issueRepository, - IRepository userRepository) - { - _issueManager = issueManager; - _issueRepository = issueRepository; - _userRepository = userRepository; - } - - [Authorize] - public async Task AssignAsync(IssueAssignDto input) - { - var issue = await _issueRepository.GetAsync(input.IssueId); - var user = await _userRepository.GetAsync(input.UserId); - - await _issueManager.AssignToAsync(issue, user); - - await _issueRepository.UpdateAsync(issue); - } - } -} -```` - -An application service method typically has three steps those are implemented here; - -1. Get the related domain objects from database to implement the use case. -2. Use domain objects (domain services, entities, etc.) to perform the actual operation. -3. Update the changed entities in the database. - -> The last *Update* is not necessary if your are using EF Core since it has a Change Tracking system. If you want to take advantage of this EF Core feature, please see the *Discussion About the Database Independence Principle* section above. - -`IssueAssignDto` in this example is a simple DTO class: - -````csharp -using System; - -namespace IssueTracking.Issues -{ - public class IssueAssignDto - { - public Guid IssueId { get; set; } - public Guid UserId { get; set; } - } -} -```` - -### Data Transfer Objects - -A [DTO](Data-Transfer-Objects.md) is a simple object that is used to transfer state (data) between the Application and Presentation Layers. So, Application Service methods gets and returns DTOs. - -#### Common DTO Principles & Best Practices - -* A DTO **should be serializable**, by its nature. Because, most of the time it is transferred over network. So, it should have a **parameterless (empty) constructor**. -* Should not contain any **business logic**. -* **Never** inherit from or reference to **entities**. - -**Input DTOs** (those are passed to the Application Service methods) have different natures than **Output DTOs** (those are returned from the Application Service methods). So, they will be treated differently. - -#### Input DTO Best Practices - -##### Do not Define Unused Properties for Input DTOs - -Define **only the properties needed** for the use case! Otherwise, it will be **confusing for the clients** to use the Application Service method. You can surely define **optional properties**, but they should effect how the use case is working, when the client provides them. - -This rule seems unnecessary first. Who would define unused parameters (input DTO properties) for a method? But it happens, especially when you try to reuse input DTOs. - -##### Do not Re-Use Input DTOs - -Define a **specialized input DTO for each use case** (Application Service method). Otherwise, some properties are not used in some cases and this violates the rule defined above: *Do not Define Unused Properties for Input DTOs*. - -Sometimes, it seems appealing to reuse the same DTO class for two use cases, because they are almost same. Even if they are same now, they will probably become different by the time and you will come to the same problem. **Code duplication is a better practice than coupling use cases**. - -Another way of reusing input DTOs is **inheriting** DTOs from each other. While this can be useful in some rare cases, most of the time it brings you to the same point. - -**Example: User Application Service** - -````csharp -public interface IUserAppService : IApplicationService -{ - Task CreateAsync(UserDto input); - Task UpdateAsync(UserDto input); - Task ChangePasswordAsync(UserDto input); -} -```` - -`IUserAppService` uses `UserDto` as the input DTO in all methods (use cases). `UserDto` is defined below: - -````csharp -public class UserDto -{ - public Guid Id { get; set; } - public string UserName { get; set; } - public string Email { get; set; } - public string Password { get; set; } - public DateTime CreationTime { get; set; } -} -```` - -For this example; - -* `Id` is not used in *Create* since the server determines it. -* `Password` is not used in *Update* since we have another method for it. -* `CreationTime` is never used since we can't allow client to send the Creation Time. It should be set in the server. - -A true implementation can be like that: - -````csharp -public interface IUserAppService : IApplicationService -{ - Task CreateAsync(UserCreationDto input); - Task UpdateAsync(UserUpdateDto input); - Task ChangePasswordAsync(UserChangePasswordDto input); -} -```` - -With the given input DTO classes: - -````csharp -public class UserCreationDto -{ - public string UserName { get; set; } - public string Email { get; set; } - public string Password { get; set; } -} - -public class UserUpdateDto -{ - public Guid Id { get; set; } - public string UserName { get; set; } - public string Email { get; set; } -} - -public class UserChangePasswordDto -{ - public Guid Id { get; set; } - public string Password { get; set; } -} -```` - -This is more maintainable approach although more code is written. - -**Exceptional Case**: There can be some exceptions for this rule: If you always want to develop two methods **in parallel**, they may share the same input DTO (by inheritance or direct reuse). For example, if you have a reporting page that has some filters and you have multiple Application Service methods (like screen report, excel report and csv report methods) use the same filters but returns different results, you may want to reuse the same filter input DTO to **couple these use cases**. Because, in this example, whenever you change a filter, you have to make the necessary changes in all the methods to have a consistent reporting system. - -##### Input DTO Validation Logic - -* Implement only **formal validation** inside the DTO. Use Data Annotation Validation Attributes or implement `IValidatableObject` for formal validation. -* **Do not perform domain validation**. For example, don't try to check unique username constraint in the DTOs. - -**Example: Using Data Annotation Attributes** - -````csharp -using System.ComponentModel.DataAnnotations; - -namespace IssueTracking.Users -{ - public class UserCreationDto - { - [Required] - [StringLength(UserConsts.MaxUserNameLength)] - public string UserName { get; set; } - - [Required] - [EmailAddress] - [StringLength(UserConsts.MaxEmailLength)] - public string Email { get; set; } - - [Required] - [StringLength(UserConsts.MaxEmailLength, - MinimumLength = UserConsts.MinPasswordLength)] - public string Password { get; set; } - } -} -```` - -ABP Framework automatically validates input DTOs, throws `AbpValidationException` and returns HTTP Status `400` to the client in case of an invalid input. - -> Some developers think it is better to separate the validation rules and DTO classes. We think the declarative (Data Annotation) approach is practical and useful and doesn't cause any design problem. However, ABP also supports [FluentValidation integration](FluentValidation.md) if you prefer the other approach. - -> See the [Validation document](Validation.md) for all validation options. - -#### Output DTO Best Practices - -* Keep output **DTO count minimum**. **Reuse** where possible (exception: Do not reuse input DTOs as output DTOs). -* Output DTOs can contain **more properties** than used in the client code. -* Return entity DTO from **Create** and **Update** methods. - -The main goals of these suggestions are; - -* Make client code easy to develop and extend; - * Dealing with **similar, but not same** DTOs are problematic on the client side. - * It is common to **need to other properties** on the UI/client in the future. Returning all properties (by considering security and privileges) of an entity makes client code easy to improve without requiring to touch to the backend code. - * If you are opening your API to **3rd-party clients** that you don't know requirements of each client. -* Make the server side code easy to develop and extend; - * You have less class to **understand and maintain**. - * You can reuse the Entity->DTO **object mapping** code. - * Returning same types from different methods make it easy and clear to create **new methods**. - -**Example: Returning Different DTO types from different methods** - -````csharp -public interface IUserAppService : IApplicationService -{ - UserDto Get(Guid id); - List GetUserNameAndEmail(Guid id); - List GetRoles(Guid id); - List GetList(); - UserCreateResultDto Create(UserCreationDto input); - UserUpdateResultDto Update(UserUpdateDto input); -} -```` - -*(We didn't use async methods to make the example cleaner, but use async in your real world application!)* - -The example code above returns different DTO types for each method. As you can guess, there will be a lot of code duplications for querying data, mapping entities to DTOs. - -The `IUserAppService` service above can be simplified: - -````csharp -public interface IUserAppService : IApplicationService -{ - UserDto Get(Guid id); - List GetList(); - UserDto Create(UserCreationDto input); - UserDto Update(UserUpdateDto input); -} -```` - -With a single output DTO: - -````csharp -public class UserDto -{ - public Guid Id { get; set; } - public string UserName { get; set; } - public string Email { get; set; } - public DateTime CreationTime { get; set; } - public List Roles { get; set; } -} -```` - -* Removed `GetUserNameAndEmail` and `GetRoles` since `Get` method already returns the necessary information. -* `GetList` now returns the same with `Get`. -* `Create` and `Update` also returns the same `UserDto`. - -Using the same DTO has a lot of advantages as explained before. For example, think a scenario where you show a **data grid** of Users on the UI. After updating a user, you can get the return value and **update it on the UI**. So, you don't need to call `GetList` again. This is why we suggest to return the entity DTO (`UserDto` here) as return value from the `Create` and `Update` operations. - -##### Discussion - -Some of the output DTO suggestions may not fit in every scenario. These suggestions can be ignored for **performance** reasons, especially when **large data sets** returned or when you create services for your own UI and you have **too many concurrent requests**. - -In these cases, you may want to create **specialized output DTOs with minimal information**. The suggestions above are especially for applications where **maintaining the codebase** is more important than **negligible performance lost**. - -#### Object to Object Mapping - -Automatic [object to object mapping](Object-To-Object-Mapping.md) is a useful approach to copy values from one object to another when two objects have same or similar properties. - -DTO and Entity classes generally have same/similar properties and you typically need to create DTO objects from Entities. ABP's [object to object mapping system](Object-To-Object-Mapping.md) with [AutoMapper](http://automapper.org/) integration makes these operations much easier comparing to manual mapping. - -* **Use** auto object mapping only for **Entity to output DTO** mappings. -* **Do not use** auto object mapping for **input DTO to Entity** mappings. - -There are some reasons why you **should not use** input DTO to Entity auto mapping; - -1. An Entity class typically has a **constructor** that takes parameters and ensures valid object creation. Auto object mapping operation generally requires an empty constructor. -2. Most of the entity properties will have **private setters** and you should use methods to change these properties in a controlled way. -3. You typically need to **carefully validate and process** the user/client input rather than blindly mapping to the entity properties. - -While some of these problems can be solved through mapping configurations (For example, AutoMapper allows to define custom mapping rules), it makes your business code **implicit/hidden** and **tightly coupled** to the infrastructure. We think the business code should be explicit, clear and easy to understand. - -See the *Entity Creation* section below for an example implementation of the suggestions made in this section. - -## Example Use Cases - -This section will demonstrate some example use cases and discuss alternative scenarios. - -### Entity Creation - -Creating an object from an Entity / Aggregate Root class is the first step of the lifecycle of that entity. The *Aggregate / Aggregate Root Rules & Best Practices* section suggests to **create a primary constructor** for the Entity class that guarantees to **create a valid entity**. So, whenever we need to create an instance of that entity, we should always **use that constructor**. - -See the `Issue` Aggregate Root class below: - -````csharp -public class Issue : AggregateRoot -{ - public Guid RepositoryId { get; private set; } - public string Title { get; private set; } - public string Text { get; set; } - public Guid? AssignedUserId { get; internal set; } - - public Issue( - Guid id, - Guid repositoryId, - string title, - string text = null - ) : base(id) - { - RepositoryId = repositoryId; - Title = Check.NotNullOrWhiteSpace(title, nameof(title)); - Text = text; //Allow empty/null - } - - private Issue() { /* Empty constructor is for ORMs */ } - - public void SetTitle(string title) - { - Title = Check.NotNullOrWhiteSpace(title, nameof(title)); - } - - //... -} -```` - -* This class guarantees to create a valid entity by its constructor. -* If you need to change the `Title` later, you need to use the `SetTitle` method which continues to keep `Title` in a valid state. -* If you want to assign this issue to a user, you need to use `IssueManager` (it implements some business rules before the assignment - see the *Domain Services* section above to remember). -* The `Text` property has a public setter, because it also accepts null values and does not have any validation rules for this example. It is also optional in the constructor. - -Let's see an Application Service method that is used to create an issue: - -````csharp -public class IssueAppService : ApplicationService, IIssueAppService -{ - private readonly IssueManager _issueManager; - private readonly IRepository _issueRepository; - private readonly IRepository _userRepository; - - public IssueAppService( - IssueManager issueManager, - IRepository issueRepository, - IRepository userRepository) - { - _issueManager = issueManager; - _issueRepository = issueRepository; - _userRepository = userRepository; - } - - public async Task CreateAsync(IssueCreationDto input) - { - // Create a valid entity - var issue = new Issue( - GuidGenerator.Create(), - input.RepositoryId, - input.Title, - input.Text - ); - - // Apply additional domain actions - if (input.AssignedUserId.HasValue) - { - var user = await _userRepository.GetAsync(input.AssignedUserId.Value); - await _issueManager.AssignToAsync(issue, user); - } - - // Save - await _issueRepository.InsertAsync(issue); - - // Return a DTO represents the new Issue - return ObjectMapper.Map(issue); - } -} -```` - -`CreateAsync` method; - -* Uses the `Issue` **constructor** to create a valid issue. It passes the `Id` using the [IGuidGenerator](Guid-Generation.md) service. It doesn't use auto object mapping here. -* If the client wants to **assign this issue to a user** on object creation, it uses the `IssueManager` to do it by allowing the `IssueManager` to perform the necessary checks before this assignment. -* **Saves** the entity to the database. -* Finally uses the `IObjectMapper` to return an `IssueDto` that is automatically created by **mapping** from the new `Issue` entity. - -#### Applying Domain Rules on Entity Creation - -The example `Issue` entity has no business rule on entity creation, except some formal validations in the constructor. However, there maybe scenarios where entity creation should check some extra business rules. - -For example, assume that you **don't want** to allow to create an issue if there is already an issue with **exactly the same `Title`**. Where to implement this rule? It is **not proper** to implement this rule in the **Application Service**, because it is a **core business (domain) rule** that should always be checked. - -This rule should be implemented in a **Domain Service**, `IssueManager` in this case. So, we need to force the Application Layer always to use the `IssueManager` to create a new `Issue.` - -First, we can make the `Issue` constructor `internal`, instead of `public`: - -````csharp -public class Issue : AggregateRoot -{ - //... - - internal Issue( - Guid id, - Guid repositoryId, - string title, - string text = null - ) : base(id) - { - RepositoryId = repositoryId; - Title = Check.NotNullOrWhiteSpace(title, nameof(title)); - Text = text; //Allow empty/null - } - - //... -} -```` - -This prevents Application Services to directly use the constructor, so they will use the `IssueManager`. Then we can add a `CreateAsync` method to the `IssueManager`: - -````csharp -using System; -using System.Threading.Tasks; -using Volo.Abp; -using Volo.Abp.Domain.Repositories; -using Volo.Abp.Domain.Services; - -namespace IssueTracking.Issues -{ - public class IssueManager : DomainService - { - private readonly IRepository _issueRepository; - - public IssueManager(IRepository issueRepository) - { - _issueRepository = issueRepository; - } - - public async Task CreateAsync( - Guid repositoryId, - string title, - string text = null) - { - if (await _issueRepository.AnyAsync(i => i.Title == title)) - { - throw new BusinessException("IssueTracking:IssueWithSameTitleExists"); - } - - return new Issue( - GuidGenerator.Create(), - repositoryId, - title, - text - ); - } - } -} -```` - -* `CreateAsync` method checks if there is already an issue with the same title and throws a business exception in this case. -* If there is no duplication, it creates and returns a new `Issue`. - -The `IssueAppService` is changed as shown below in order to use the `IssueManager`'s `CreateAsync` method: - -````csharp -public class IssueAppService : ApplicationService, IIssueAppService -{ - private readonly IssueManager _issueManager; - private readonly IRepository _issueRepository; - private readonly IRepository _userRepository; - - public IssueAppService( - IssueManager issueManager, - IRepository issueRepository, - IRepository userRepository) - { - _issueManager = issueManager; - _issueRepository = issueRepository; - _userRepository = userRepository; - } - - public async Task CreateAsync(IssueCreationDto input) - { - // Create a valid entity using the IssueManager - var issue = await _issueManager.CreateAsync( - input.RepositoryId, - input.Title, - input.Text - ); - - // Apply additional domain actions - if (input.AssignedUserId.HasValue) - { - var user = await _userRepository.GetAsync(input.AssignedUserId.Value); - await _issueManager.AssignToAsync(issue, user); - } - - // Save - await _issueRepository.InsertAsync(issue); - - // Return a DTO represents the new Issue - return ObjectMapper.Map(issue); - } -} - -// *** IssueCreationDto class *** -public class IssueCreationDto -{ - public Guid RepositoryId { get; set; } - [Required] - public string Title { get; set; } - public Guid? AssignedUserId { get; set; } - public string Text { get; set; } -} -```` - -##### Discussion: Why is the Issue not saved to the database in `IssueManager`? - -You may ask "**Why didn't `IssueManager` save the `Issue` to the database?**". We think it is the responsibility of the Application Service. - -Because, the Application Service may require additional changes/operations on the `Issue` object before saving it. If Domain Service saves it, then the *Save* operation is duplicated; - -* It causes performance lost because of double database round trip. -* It requires explicit database transaction that covers both operations. -* If additional actions cancel the entity creation because of a business rule, the transaction should be rolled back in the database. - -When you check the `IssueAppService`, you will see the advantage of **not saving** `Issue` to the database in the `IssueManager.CreateAsync`. Otherwise, we would need to perform one *Insert* (in the `IssueManager`) and one *Update* (after the Assignment). - -##### Discussion: Why is the duplicate Title check not implemented in the Application Service? - -We could simple say "Because it is a **core domain logic** and should be implemented in the Domain Layer". However, it brings a new question "**How did you decide** that it is a core domain logic, but not an application logic?" (we will discuss the difference later with more details). - -For this example, a simple question can help us to make the decision: "If we have another way (use case) of creating an issue, should we still apply the same rule? Is that rule should *always* be implemented". You may think "Why do we have a second way of creating an issue?". However, in real life, you have; - -* **End users** of the application may create issues in your application's standard UI. -* You may have a second **back office** application that is used by your own employees and you may want to provide a way of creating issues (probably with different authorization rules in this case). -* You may have an HTTP API that is open to **3rd-party clients** and they create issues. -* You may have a **background worker** service that do something and creates issues if it detects some problems. In this way, it will create an issue without any user interaction (and probably without any standard authorization check). -* You may have a button on the UI that **converts** something (for example, a discussion) to an issue. - -We can give more examples. All of these are should be implemented by **different Application Service methods** (see the *Multiple Application Layers* section below), but they **always** follow the rule: Title of the new issue can not be same of any existing issue! That's why this logic is a **core domain logic**, should be located in the Domain Layer and **should not be duplicated** in all these application service methods. - -### Updating / Manipulating An Entity - -Once an entity is created, it is updated/manipulated by the use cases until it is deleted from the system. There can be different types of the use cases directly or indirectly changes an entity. - -In this section, we will discuss a typical update operation that changes multiple properties of an `Issue`. - -This time, beginning from the *Update* DTO: - -````csharp -public class UpdateIssueDto -{ - [Required] - public string Title { get; set; } - public string Text { get; set; } - public Guid? AssignedUserId { get; set; } -} -```` - -By comparing to `IssueCreationDto`, you see no `RepositoryId`. Because, our system doesn't allow to move issues across repositories (think as GitHub repositories). Only `Title` is required and the other properties are optional. - -Let's see the *Update* implementation in the `IssueAppService`: - -````csharp -public class IssueAppService : ApplicationService, IIssueAppService -{ - private readonly IssueManager _issueManager; - private readonly IRepository _issueRepository; - private readonly IRepository _userRepository; - - public IssueAppService( - IssueManager issueManager, - IRepository issueRepository, - IRepository userRepository) - { - _issueManager = issueManager; - _issueRepository = issueRepository; - _userRepository = userRepository; - } - - public async Task UpdateAsync(Guid id, UpdateIssueDto input) - { - // Get entity from database - var issue = await _issueRepository.GetAsync(id); - - // Change Title - await _issueManager.ChangeTitleAsync(issue, input.Title); - - // Change Assigned User - if (input.AssignedUserId.HasValue) - { - var user = await _userRepository.GetAsync(input.AssignedUserId.Value); - await _issueManager.AssignToAsync(issue, user); - } - - // Change Text (no business rule, all values accepted) - issue.Text = input.Text; - - // Update entity in the database - await _issueRepository.UpdateAsync(issue); - - // Return a DTO represents the new Issue - return ObjectMapper.Map(issue); - } -} -```` - -* `UpdateAsync` method gets `id` as a separate parameter. It is not included in the `UpdateIssueDto`. This is a design decision that helps ABP to properly define HTTP routes when you [auto expose](API/Auto-API-Controllers.md) this service as an HTTP API endpoint. So, that's not related to DDD. -* It starts by **getting** the `Issue` entity **from the database**. -* Uses `IssueManager`'s `ChangeTitleAsync` instead of directly calling `Issue.SetTitle(...)`. Because we need to implement the **duplicate Title check** as just done in the *Entity Creation*. This requires some changes in the `Issue` and `IssueManager` classes (will be explained below). -* Uses `IssueManager`'s `AssignToAsync` method if the **assigned user** is being changed with this request. -* Directly sets the `Issue.Text` since there is **no business rule** for that. If we need later, we can always refactor. -* **Saves changes** to the database. Again, saving changed entities is a responsibility of the Application Service method that coordinates the business objects and the transaction. If the `IssueManager` had saved internally in `ChangeTitleAsync` and `AssignToAsync` method, there would be double database operation (see the *Discussion: Why is the Issue not saved to the database in `IssueManager`?* above). -* Finally uses the `IObjectMapper` to return an `IssueDto` that is automatically created by **mapping** from the updated `Issue` entity. - -As said, we need some changes in the `Issue` and `IssueManager` classes. - -First, made `SetTitle` internal in the `Issue` class: - -````csharp -internal void SetTitle(string title) -{ - Title = Check.NotNullOrWhiteSpace(title, nameof(title)); -} -```` - -Then added a new method to the `IssueManager` to change the Title: - -````csharp -public async Task ChangeTitleAsync(Issue issue, string title) -{ - if (issue.Title == title) - { - return; - } - - if (await _issueRepository.AnyAsync(i => i.Title == title)) - { - throw new BusinessException("IssueTracking:IssueWithSameTitleExists"); - } - - issue.SetTitle(title); -} -```` - -## Domain Logic & Application Logic - -As mentioned before, *Business Logic* in the Domain Driven Design is spitted into two parts (layers): *Domain Logic* and *Application Logic*: - -![domain-driven-design-domain-vs-application-logic](images/domain-driven-design-domain-vs-application-logic.png) - -Domain Logic consists of the *Core Domain Rules* of the system while Application Logic implements application specific *Use Cases*. - -While the definition is clear, the implementation may not be easy. You may be undecided which code should stand in the Application Layer, which code should be in the Domain Layer. This section tries to explain the differences. - -### Multiple Application Layers - -DDD helps to **deal with complexity** when your system is large. Especially, if there are **multiple applications** are being developed in a **single domain,** then the **Domain Logic vs Application Logic separation** becomes much more important. - -Assume that you are building a system that has multiple applications; - -* A **Public Web Site Application**, built with ASP.NET Core MVC, to show your products to users. Such a web site doesn't require authentication to see the products. The users login to the web site, only if they are performing some actions (like adding a product to the basket). -* A **Back Office Application**, built with Angular UI (that uses REST APIs). This application used by office workers of the company to manage the system (like editing product descriptions). -* A **Mobile Application** that has much simpler UI compared to the Public Web Site. It may communicate to the server via REST APIs or another technology (like TCP sockets). - -![domain-driven-design-multiple-applications](images/domain-driven-design-multiple-applications.png) - -Every application will have different **requirements**, different **use cases** (Application Service methods), different **DTOs**, different **validation** and **authorization** rules... etc. - -Mixing all these logics into a single application layer makes your services contain too many `if` conditions with **complicated business logic** makes your code **harder to develop, maintain and test** and leads to potential bugs. - -If you've multiple applications with a single domain; - -* Create **separate application layers** for each application/client type and implement application specific business logic in these separate layers. -* Use a **single domain layer** to share the core domain logic. - -Such a design makes it even more important to distinguish between Domain logic and Application Logic. - -To be more clear about the implementation, you can create different projects (`.csproj`) for each application types. For example; - -* `IssueTracker.Admin.Application` & `IssueTracker.Admin.Application.Contracts` projects for the Back Office (admin) Application. -* `IssueTracker.Public.Application` & `IssueTracker.Public.Application.Contracts` projects for the Public Web Application. -* `IssueTracker.Mobile.Application` & `IssueTracker.Mobile.Application.Contracts` projects for the Mobile Application. - -### Examples - -This section contains some Application Service and Domain Service examples to discuss how to decide to place business logic inside these services. - -**Example: Creating a new `Organization` in a Domain Service** - -````csharp -public class OrganizationManager : DomainService -{ - private readonly IRepository _organizationRepository; - private readonly ICurrentUser _currentUser; - private readonly IAuthorizationService _authorizationService; - private readonly IEmailSender _emailSender; - - public OrganizationManager( - IRepository organizationRepository, - ICurrentUser currentUser, - IAuthorizationService authorizationService, - IEmailSender emailSender) - { - _organizationRepository = organizationRepository; - _currentUser = currentUser; - _authorizationService = authorizationService; - _emailSender = emailSender; - } - - public async Task CreateAsync(string name) - { - if (await _organizationRepository.AnyAsync(x => x.Name == name)) - { - throw new BusinessException("IssueTracking:DuplicateOrganizationName"); - } - - await _authorizationService.CheckAsync("OrganizationCreationPermission"); - - Logger.LogDebug($"Creating organization {name} by {_currentUser.UserName}"); - - var organization = new Organization(); - - await _emailSender.SendAsync( - "systemadmin@issuetracking.com", - "New Organization", - "A new organization created with name: " + name - ); - - return organization; - } -} -```` - -Let's see the `CreateAsync` method step by step to discuss if the code part should be in the Domain Service, or not; - -* **CORRECT**: It first checks for **duplicate organization name** and and throws exception in this case. This is something related to core domain rule and we never allow duplicated names. -* **WRONG**: Domain Services should not perform **authorization**. [Authorization](Authorization.md) should be done in the Application Layer. -* **WRONG**: It logs a message with including the [Current User](CurrentUser.md)'s `UserName`. Domain service should not be depend on the Current User. Domain Services should be usable even if there is no user in the system. Current User (Session) should be a Presentation/Application Layer related concept. -* **WRONG**: It sends an [email](Emailing.md) about this new organization creation. We think this is also a use case specific business logic. You may want to create different type of emails in different use cases or don't need to send emails in some cases. - -**Example: Creating a new `Organization` in an Application Service** - -````csharp -public class OrganizationAppService : ApplicationService -{ - private readonly OrganizationManager _organizationManager; - private readonly IPaymentService _paymentService; - private readonly IEmailSender _emailSender; - - public OrganizationAppService( - OrganizationManager organizationManager, - IPaymentService paymentService, - IEmailSender emailSender) - { - _organizationManager = organizationManager; - _paymentService = paymentService; - _emailSender = emailSender; - } - - [UnitOfWork] - [Authorize("OrganizationCreationPermission")] - public async Task CreateAsync(CreateOrganizationDto input) - { - await _paymentService.ChargeAsync( - CurrentUser.Id, - GetOrganizationPrice() - ); - - var organization = await _organizationManager.CreateAsync(input.Name); - - await _organizationManager.InsertAsync(organization); - - await _emailSender.SendAsync( - "systemadmin@issuetracking.com", - "New Organization", - "A new organization created with name: " + input.Name - ); - - return organization; // !!! - } - - private double GetOrganizationPrice() - { - return 42; //Gets from somewhere else... - } -} -```` - -Let's see the `CreateAsync` method step by step to discuss if the code part should be in the Application Service, or not; - -* **CORRECT**: Application Service methods should be unit of work (transactional). ABP's [Unit Of Work](Unit-Of-Work.md) system makes this automatic (even without need to add `[UnitOfWork]` attribute for the Application Services). -* **CORRECT**: [Authorization](Authorization.md) should be done in the application layer. Here, it is done by using the `[Authorize]` attribute. -* **CORRECT**: Payment (an infrastructure service) is called to charge money for this operation (Creating an Organization is a paid service in our business). -* **CORRECT**: Application Service method is responsible to save changes to the database. -* **CORRECT**: We can send [email](Emailing.md) as a notification to the system admin. -* **WRONG**: Do not return entities from the Application Services. Return a DTO instead. - -**Discussion: Why don't we move the payment logic into the domain service?** - -You may wonder why the payment code is not inside the `OrganizationManager`. It is an **important thing** and we never want to **miss the payment**. - -However, **being important is not sufficient** to consider a code as a Core Business Logic. We may have **other use cases** where we don't charge money to create a new Organization. Examples; - -* An admin user can use a Back Office Application to create a new organization without any payment. -* A background-working data import/integration/synchronization system may also need to create organizations without any payment operation. - -As you see, **payment is not a necessary operation to create a valid organization**. It is a use-case specific application logic. - -**Example: CRUD Operations** - -````csharp -public class IssueAppService -{ - private readonly IssueManager _issueManager; - - public IssueAppService(IssueManager issueManager) - { - _issueManager = issueManager; - } - - public async Task GetAsync(Guid id) - { - return await _issueManager.GetAsync(id); - } - - public async Task CreateAsync(IssueCreationDto input) - { - await _issueManager.CreateAsync(input); - } - - public async Task UpdateAsync(UpdateIssueDto input) - { - await _issueManager.UpdateAsync(input); - } - - public async Task DeleteAsync(Guid id) - { - await _issueManager.DeleteAsync(id); - } -} -```` - -This Application Service **does nothing** itself and **delegates all the work** to the *Domain Service*. It even passes the DTOs to the `IssueManager`. - -* **Do not** create Domain Service methods just for simple **CRUD** operations **without any domain logic**. -* **Never** pass **DTOs** to or return **DTOs** from the Domain Services. - -Application Services can directly work with repositories to query, create, update or delete data unless there are some domain logics should be performed during these operations. In such cases, create Domain Service methods, but only for those really necessary. - -> Do not create such CRUD domain service methods just by thinking that they may be needed in the future ([YAGNI](https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it))! Do it when you need and refactor the existing code. Since the Application Layer gracefully abstracts the Domain Layer, the refactoring process doesn't effect the UI Layer and other clients. - -## Reference Books - -If you are more interested in the Domain Driven Design and building large-scale enterprise systems, the following books are recommended as reference books; - -* "*Domain Driven Design*" by Eric Evans -* "*Implementing Domain Driven Design*" by Vaughn Vernon -* "*Clean Architecture*" by Robert C. Martin +https://abp.io/books/implementing-domain-driven-design diff --git a/docs/en/Domain-Driven-Design.md b/docs/en/Domain-Driven-Design.md index f18c1882ef..03a6232ba3 100644 --- a/docs/en/Domain-Driven-Design.md +++ b/docs/en/Domain-Driven-Design.md @@ -32,6 +32,6 @@ DDD mostly interest in the **Domain** and the **Application** layers, rather tha * [Data Transfer Objects (DTOs)](Data-Transfer-Objects.md) * [Unit of Work](Unit-Of-Work.md) -## The Ultimate DDD Implementation Guide +## Free E-Book: Implementing DDD -See the [Implementing Domain Driven Design](Domain-Driven-Design-Implementation-Guide.md) guide as a **complete reference**. The Guide explains the Domain Driven Design and introduces explicit **rules and examples** to give a deep understanding of the **implementation details**. \ No newline at end of file +See the [Implementing Domain Driven Design book](https://abp.io/books/implementing-domain-driven-design) as a **complete reference**. This book explains the Domain Driven Design and introduces explicit **rules and examples** to give a deep understanding of the **implementation details**. \ No newline at end of file diff --git a/docs/en/Entity-Framework-Core-Migrations.md b/docs/en/Entity-Framework-Core-Migrations.md index 4d42d8bb97..5d8ca01854 100644 --- a/docs/en/Entity-Framework-Core-Migrations.md +++ b/docs/en/Entity-Framework-Core-Migrations.md @@ -61,9 +61,9 @@ ABP Framework's [connection string](Connection-Strings.md) system allows you to } ```` -The example configuration about tells to the ABP Framework to use the second connection string for the [Audit Logging module](Modules/Audit-Logging.md). +The example configuration tells to the ABP Framework to use the second connection string for the [Audit Logging module](Modules/Audit-Logging.md) (if you don't specify connection string for a module, it uses the `Default` connection string). -**However, this is just the beginning**. You also need to create the second database, create audit log tables inside it and maintain the database tables using the code first migrations approach. One of the main purposes of this document is to guide you on such **database separation** scenarios. +**However, this can work only if the audit log database with the given connection string is available**. So, you need to create the second database, create audit log tables inside it and maintain the database tables. No problem if you manually do all these. However, the recommended approach is the code first migrations. One of the main purposes of this document is to guide you on such **database separation** scenarios. #### Module Tables @@ -85,23 +85,24 @@ This code changes the prefix of the [Identity Server](Modules/IdentityServer.md) > Every module also defines `DbSchema` property (near to `DbTablePrefix`), so you can set it for the databases support the schema usage. -### The Projects +### .EntityFrameworkCore Project -From the database point of view, there are three important projects those will be explained in the next sections. +The solution contains a project, which's name ends with `.EntityFrameworkCore`. This project has the `DbContext` class (`BookStoreDbContext` for this sample) of your application. -#### .EntityFrameworkCore Project - -This project has the `DbContext` class (`BookStoreDbContext` for this sample) of your application. - -**Every module uses its own `DbContext` class** to access to the database. Likewise, your application has its own `DbContext`. You typically use this `DbContext` in your application code (in your [repositories](Repositories.md) if you follow the best practices). It is almost an empty `DbContext` since your application don't have any entities at the beginning, except the pre-defined `AppUser` entity: +**Every module uses its own `DbContext` class** to access to the database. Likewise, your application has its own `DbContext`. You typically use this `DbContext` in your application code (in your [repositories](Repositories.md) if you follow the best practices and hide your data access code behind the repositories). It is almost an empty `DbContext` since your application don't have any entities at the beginning: ````csharp +[ReplaceDbContext(typeof(IIdentityDbContext))] +[ReplaceDbContext(typeof(ITenantManagementDbContext))] [ConnectionStringName("Default")] -public class BookStoreDbContext : AbpDbContext +public class BookStoreDbContext : + AbpDbContext, + IIdentityDbContext, + ITenantManagementDbContext { - public DbSet Users { get; set; } - /* Add DbSet properties for your Aggregate Roots / Entities here. */ + + /* DbSet for entities from the replaced DbContexts */ public BookStoreDbContext(DbContextOptions options) : base(options) @@ -112,72 +113,7 @@ public class BookStoreDbContext : AbpDbContext protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); - - /* Configure the shared tables (with included modules) here */ - - builder.Entity(b => - { - //Sharing the same Users table with the IdentityUser - b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "Users"); - - b.ConfigureByConvention(); - b.ConfigureAbpUser(); - - /* Configure mappings for your additional properties - * Also see the MyProjectNameEntityExtensions class - */ - }); - - /* Configure your own tables/entities inside the ConfigureBookStore method */ - builder.ConfigureBookStore(); - } -} -```` - -This simple `DbContext` class still needs some explanations: - -* It defines a `[ConnectionStringName]` attribute which tells ABP to always use the `Default` connection string for this `Dbcontext`. -* It inherits from the `AbpDbContext` instead of the standard `DbContext` class. You can see the [EF Core integration](Entity-Framework-Core.md) document for more. For now, know that the `AbpDbContext` base class implements some conventions of the ABP Framework to automate some common tasks for you. -* It declares a `DbSet` property for the `AppUser` entity. `AppUser` shares the same table (named `AbpUsers` by default) with the `IdentityUser` entity of the [Identity module](Modules/Identity.md). The startup template provides this entity inside the application since we think that the User entity is generally needs to be customized in your application. -* The constructor takes a `DbContextOptions` instance. -* It overrides the `OnModelCreating` method to define the EF Core mappings. - * It first calls the the `base.OnModelCreating` method to let the ABP Framework to implement the base mappings for us. - * It then configures the mapping for the `AppUser` entity. There is a special case for this entity (it shares a table with the Identity module), which will be explained in the next sections. - * It finally calls the `builder.ConfigureBookStore()` extension method to configure other entities of your application. - -This design will be explained in more details after introducing the other database related projects. - -#### .EntityFrameworkCore.DbMigrations Project - -As mentioned in the previous section, every module (and your application) have **their own** separate `DbContext` classes. Each `DbContext` class only defines the entity to table mappings related to its own module and each module (and your application) use the related `DbContext` class **on runtime**. - -As you know, EF Core Code First migration system relies on a `DbContext` class **to track and generate** the code first migrations. So, which `DbContext` we should use for the migrations? The answer is *none of them*. There is another `DbContext` defined in the `.EntityFrameworkCore.DbMigrations` project (which is the `BookStoreMigrationsDbContext` for this example solution). - -##### The MigrationsDbContext - -The `MigrationsDbContext` is only used to create and apply the database migrations. It is **not used on runtime**. It **merges** all the entity to table mappings of all the used modules plus the application's mappings. - -In this way, you create and maintain a **single database migration path**. However, there are some difficulties of this approach and the next sections explains how ABP Framework overcomes these difficulties. But first, see the `BookStoreMigrationsDbContext` class as an example: - -````csharp -/* This DbContext is only used for database migrations. - * It is not used on runtime. See BookStoreDbContext for the runtime DbContext. - * It is a unified model that includes configuration for - * all used modules and your application. - */ -public class BookStoreMigrationsDbContext : AbpDbContext -{ - public BookStoreMigrationsDbContext( - DbContextOptions options) - : base(options) - { - - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - + /* Include modules to your migration db context */ builder.ConfigurePermissionManagement(); builder.ConfigureSettingManagement(); @@ -188,391 +124,42 @@ public class BookStoreMigrationsDbContext : AbpDbContext optionsAction = null) - { - var options = new BackgroundJobsModelBuilderConfigurationOptions( - BackgroundJobsDbProperties.DbTablePrefix, - BackgroundJobsDbProperties.DbSchema - ); - - optionsAction?.Invoke(options); - - builder.Entity(b => - { - b.ToTable(options.TablePrefix + "BackgroundJobs", options.Schema); - - b.ConfigureCreationTime(); - b.ConfigureExtraProperties(); - - b.Property(x => x.JobName) - .IsRequired() - .HasMaxLength(BackgroundJobRecordConsts.MaxJobNameLength); - - //... - }); + /* Configure your own tables/entities here. Example: */ + //builder.Entity(b => + //{ + // b.ToTable("YourEntities"); + // b.ConfigureByConvention(); //auto configure for the base properties + // //... + //}); } } ```` -This extension method also gets options to change the database table prefix and schema for this module, but it is not important here. - -The final application calls the extension methods inside the `MigrationsDbContext` class, so it can decide which modules are included in the database maintained by this `MigrationsDbContext`. If you want to create a second database and move some module tables to the second database, then you need to have a second `MigrationsDbContext` class which only calls the extension methods of the related modules. This topic will be detailed in the next sections. - -The same `ConfigureBackgroundJobs` method is also called in the `DbContext` of the Background Jobs module: - -````csharp -[ConnectionStringName(BackgroundJobsDbProperties.ConnectionStringName)] -public class BackgroundJobsDbContext - : AbpDbContext, IBackgroundJobsDbContext -{ - public DbSet BackgroundJobs { get; set; } - - public BackgroundJobsDbContext(DbContextOptions options) - : base(options) - { - - } - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - - //Reuse the same extension method! - builder.ConfigureBackgroundJobs(); - } -} -```` - -In this way, the mapping configuration of a module can be shared between `DbContext` classes. The code above is inside the related module NuGet package, so you don't care about it. - -##### Reusing a Table of a Module - -You may want to **reuse a table** of a depended module in your application. In this case, you have two options: - -1. You can **directly use the entity** defined by the module (you can still [extend the entity](Customizing-Application-Modules-Extending-Entities.md) in some level). -2. You can **create a new entity** mapping to the same database table. - -###### Use the Entity Defined by a Module - -Using an entity defined a module is pretty easy and standard. For example, Identity module defines the `IdentityUser` entity. You can inject the [repository](Repositories.md) for the `IdentityUser` and perform the standard repository operations for this entity. Example: - -````csharp -using System; -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Repositories; -using Volo.Abp.Identity; - -namespace Acme.BookStore -{ - public class MyService : ITransientDependency - { - private readonly IRepository _identityUserRepository; - - public MyService(IRepository identityUserRepository) - { - _identityUserRepository = identityUserRepository; - } - - public async Task DoItAsync() - { - //Get all users - var users = await _identityUserRepository.GetListAsync(); - } - } -} -```` - -This example injects the `IRepository` (default repository) which defines the standard repository methods and implements the `IQueryable` interface. - -> In addition, Identity module defines the `IIdentityUserRepository` (custom repository) that can also be injected and used by your application. `IIdentityUserRepository` provides additional custom methods for the `IdentityUser` entity while it does not implement the `IQueryable` interface. - -###### Create a New Entity - -Working with an entity of a module is easy if you want to use the entity as is. However, you may want to define your own entity class and map to the same database table in the following cases; - -* You want to **add a new field** to the table and map it to a property in the entity. You can't use the module's entity since it doesn't have the related property. -* You want to **use a subset of the table fields**. You don't want to access to all properties of the entity and hide the unrelated properties (from a security perspective or just by design). -* You don't want to directly **depend on** a module entity class. - -In any case, the progress is same. Assume that you want to create an entity, named `AppRole`, mapped to the same table of the `IdentityRole` entity of the [Identity module](Modules/Identity.md). - -Here, we will show the implementation, then **will discuss the limitations** of this approach. - -First, create a new `AppRole` class in your `.Domain` project: - -````csharp -using System; -using Volo.Abp.Domain.Entities; -using Volo.Abp.MultiTenancy; - -namespace Acme.BookStore.Roles -{ - public class AppRole : AggregateRoot, IMultiTenant - { - // Properties shared with the IdentityRole class - - public Guid? TenantId { get; private set; } - public string Name { get; private set; } - - //Additional properties - - public string Title { get; set; } - - private AppRole() - { - - } - } -} -```` - -* It's inherited from [the `AggregateRoot` class](Entities.md) and implements [the `IMultiTenant` interface](Multi-Tenancy.md) because the `IdentityRole` also does the same. -* You can add any properties defined by the `IdentityRole` entity. This examples add only the `TenantId` and `Name` properties since we only need them here. You can make the setters private (like in this example) to prevent changing Identity module's properties accidently. -* You can add custom (additional) properties. This example adds the `Title` property. -* The **constructor is private**, so it is not allowed to directly create a new `AppRole` entity. Creating a role is a responsibility of the Identity module. You can query roles, set/update your custom properties, but you should not create or delete a role in your code, as a best practice (while there is nothing restricts you). - -Now, it is time to define the EF Core mappings. Open the `DbContext` of your application (`BookStoreDbContext` in this sample) and add the following property: - -````csharp -public DbSet Roles { get; set; } -```` - -Then configure the mapping inside the `OnModelCreating` method (after calling the `base.OnModelCreating(builder)`): - -````csharp -protected override void OnModelCreating(ModelBuilder builder) -{ - base.OnModelCreating(builder); - - /* Configure the shared tables (with included modules) here */ - - //CONFIGURE THE AppRole ENTITY - builder.Entity(b => - { - b.ToTable("AbpRoles"); - b.ConfigureByConvention(); - b.Property(x => x.Title).HasMaxLength(128); - }); - - ... - - /* Configure your own tables/entities inside the ConfigureBookStore method */ - - builder.ConfigureBookStore(); -} -```` - -We added the following lines: - -````csharp -builder.Entity(b => -{ - b.ToTable("AbpRoles"); - b.ConfigureByConvention(); - b.Property(x => x.Title).HasMaxLength(128); -}); -```` - -* It maps to the same `AbpRoles` table shared with the `IdentityRole` entity. -* `ConfigureByConvention()` configures the standard/base properties (like `TenantId`) and recommended to always call it. - -You've configured the custom property for your `DbContext` that is used by your application on the runtime. We also need to configure the `MigrationsDbContext`. - -Instead of directly changing the `MigrationsDbContext`, we **should** use the entity extension system of the ABP Framework. Find the `YourProjectNameEfCoreEntityExtensionMappings` class in the `.EntityFrameworkCore` project of your solution (`BookStoreEfCoreEntityExtensionMappings` for this example) and change it as shown below: - -````csharp -public static class MyProjectNameEntityExtensions -{ - private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); - - public static void Configure() - { - OneTimeRunner.Run(() => - { - ObjectExtensionManager.Instance - .MapEfCoreProperty( - "Title", - (entityBuilder, propertyBuilder) => - { - propertyBuilder.HasMaxLength(128); - } - ); - }); - } -} -```` +This `DbContext` class needs some explanations: -> Instead of hard-coded "Title" string, we suggest to use `nameof(AppRole.Title)` or use a constant string. - -`ObjectExtensionManager` is used to add properties to existing entities. Since `ObjectExtensionManager.Instance` is a static instance (singleton), we should call it once. `OneTimeRunner` is a simple utility class defined by the ABP Framework. - -See the [EF Core integration documentation](Entity-Framework-Core.md) for more about the entity extension system. - -> We've repeated a similar database mapping code, like `HasMaxLength(128)`, in both classes. - -Now, you can add a new EF Core database migration using the standard `Add-Migration` command in the Package Manager Console (remember to select `.EntityFrameworkCore.DbMigrations` as the Default Project in the PMC and make sure that the `.Web` project is still the startup project): - -![pmc-add-migration-role-title](images/pmc-add-migration-role-title.png) - -This command will create a new code first migration class as shown below: - -````csharp -public partial class Added_Title_To_Roles : Migration -{ - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "Title", - table: "AbpRoles", - maxLength: 128, - nullable: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "Title", - table: "AbpRoles"); - } -} -```` - -All done! Just run the `Update-Database` command in the PMC or run the `.DbMigrator` project in your solution to apply changes to database. - -Now, you can work with the `AppRole` entity just like any other entity of your application. An example [application service](Application-Services.md) that queries and updates roles: - -````csharp -public class AppRoleAppService : ApplicationService, IAppRoleAppService -{ - private readonly IRepository _appRoleRepository; - - public AppRoleAppService(IRepository appRoleRepository) - { - _appRoleRepository = appRoleRepository; - } - - public async Task> GetListAsync() - { - var roles = await _appRoleRepository.GetListAsync(); - - return roles - .Select(r => new AppRoleDto - { - Id = r.Id, - Name = r.Name, - Title = r.Title - }) - .ToList(); - } - - public async Task UpdateTitleAsync(Guid id, string title) - { - var role = await _appRoleRepository.GetAsync(id); - - role.Title = title; - - await _appRoleRepository.UpdateAsync(role); - } -} -```` - -There are some **limitations** of creating a new entity and mapping it to a table of a depended module: - -* Your **custom properties must be nullable**. For example, `AppRole.Title` was nullable here. Otherwise, Identity module throws exception because it doesn't know and can not fill the Title when it inserts a new role to the database. -* As a good practice, you should not update the **properties defined by the module**, especially if it requires a business logic. You typically want to manage your own properties. - -##### Alternative Approaches - -Instead of creating a new entity class to add a custom property, you can use the following approaches. - -###### Using the ExtraProperties - -All entities derived from the `AggregateRoot ` class can store name-value pairs in their `ExtraProperties` property (because they implement the `IHasExtraProperties` interface), which is a `Dictionary` serialized to JSON in the database table. So, you can add values to this dictionary and query again without changing the entity. - -For example, you can store query the title Property inside an `IdentityRole` instead of creating a new entity. Example: - -````csharp -public class IdentityRoleExtendingService : ITransientDependency -{ - private readonly IIdentityRoleRepository _identityRoleRepository; - - public IdentityRoleExtendingService(IIdentityRoleRepository identityRoleRepository) - { - _identityRoleRepository = identityRoleRepository; - } - - public async Task GetTitleAsync(Guid id) - { - var role = await _identityRoleRepository.GetAsync(id); - return role.GetProperty("Title"); - } - - public async Task SetTitleAsync(Guid id, string newTitle) - { - var role = await _identityRoleRepository.GetAsync(id); - role.SetProperty("Title", newTitle); - await _identityRoleRepository.UpdateAsync(role); - } -} -```` - -* `GetProperty` and `SetProperty` methods are shortcuts to get and set a value in the `role.ExtraProperties` dictionary and they are the recommended way to work with the extra properties. - -In this way, you can easily attach any type of value to an entity of a depended module. However, there are some drawbacks of this usage: - -* All the extra properties are stored as **a single JSON object** in the database. They are not stored as new table fields, as you may expect. Creating database table indexes and using SQL queries against these properties will be harder compared to simple table fields. -* Property names are strings, so they are **not type safe**. It is recommended to define constants for these kind of properties to prevent typo errors. - -###### Using the Entity Extensions System - -Entity extension system solves the main problem of the extra properties: It can store an extra property in a **standard table field** in the database. - -All you need to do is to use the `ObjectExtensionManager` to define the extra property as explained above, in the `AppRole` example. Then you can continue to use the same `GetProperty` and `SetProperty` methods defined above to get/set the related property on the entity, but this time stored as a separate field in the database. - -See the [entity extension system](Customizing-Application-Modules-Extending-Entities.md) for details. - -###### Creating a New Table - -Instead of creating a new entity and mapping to the same table, you can also create **your own table** to store your properties. You typically duplicate some values of the original entity. For example, you can add `Name` field to your own table which is a duplication of the `Name` field in the original table. - -In this case, you don't deal with migration problems, however you need to deal with the problems of data duplication. When the duplicated value changes, you should reflect the same change in your table. You can use local or distributed [event bus](Event-Bus.md) to subscribe to the change events for the original entity. This is the recommended way of depending on a microservice's data from another microservice, especially if they have separate physical databases (you can search on the web on data sharing on a microservice design, it is a wide topic to cover here). - -> See the "[extending entities](Customizing-Application-Modules-Extending-Entities.md)" guide for more details on extending entities, including data duplication and synchronization tips. +* It defines `[ReplaceDbContext]` attributes for `IIdentityDbContext` and `ITenantManagementDbContext` those replaces Identity and Tenant Management module's `DbContext`s by your `DbContext` on runtime. This allows us to easily perform LINQ queries by joining your entities with the entities (over the repositories) coming from those modules. +* It defines a `[ConnectionStringName]` attribute which tells ABP to always use the `Default` connection string for this `Dbcontext`. +* It inherits from the `AbpDbContext` instead of the standard `DbContext` class. You can see the [EF Core integration](Entity-Framework-Core.md) document for more. For now, know that the `AbpDbContext` base class implements some conventions of the ABP Framework to automate some common tasks for you. +* It declares `DbSet` properties for entities from the replaced `DbContext`s (by implementing the corresponding interfaces). These `DbSet` properties are not shown above (for the sake of brevity), but you can find in your application's code in a `region`. +* The constructor takes a `DbContextOptions` instance. +* It overrides the `OnModelCreating` method to define the EF Core mappings. + * It first calls the the `base.OnModelCreating` method to let the ABP Framework to implement the base mappings for us. + * It then calls some `builder.ConfigureXXX()` methods for the used modules. This makes possible to add database mappings for these modules to this `DbContext`, so it creates the database tables of the modules when we add a new EF Core database migration. + * You can configure the mappings for your own entities as commented in the example code. At this point, you can also change mappings for the modules you are using. -#### Discussion of an Alternative Scenario: Every Module Manages Its Own Migration Path +### Discussion of an Alternative Scenario: Every Module Manages Its Own Migration Path -As mentioned before, `.EntityFrameworkCore.DbMigrations` merges all the database mappings of all the modules (plus your application's mappings) to create a unified migration path. +As mentioned before, in the `.EntityFrameworkCore` project, we merge all the database mappings of all the modules (plus your application's mappings) to create a unified migration path. -An alternative approach would be to allow each module to have its own migrations to maintain its database tables. While it seems more module in the beginning, it has some important drawbacks: +An alternative approach would be to allow each module to have its own migrations to maintain its database tables. While it seems more modular in the beginning, it has some drawbacks in practical: -* **EF Core migration system depends on the DBMS provider**. For example, if a module has created migrations for SQL Server, then you can not use this migration code for MySQL. It is not practical for a module to maintain migrations for all available DBMS providers. Leaving the migration to the application code (as explained in this document) allows you to **choose the DBMS in the application** code. -* It would be harder or impossible to **share a table** between modules or **re-use a table** of a module in your application. Because EF Core migration system can not handle it and will throw exceptions like "Table XXX is already exists in the database". -* It would be harder to **customize/enhance** the mapping and the resulting migration code. +* **EF Core migration system depends on the DBMS provider**. For example, if a module has created migrations for SQL Server, then you can not use this migration code for MySQL. It is not practical for a module to maintain migrations for all available DBMS providers. Leaving the migration to the application code (as explained in this document) allows you to **choose the DBMS in the application** code. If you can depend on a specific DBMS in your module, that's not an issue for you, however all pre-built ABP modules are DBMS agnostic. +* It would be harder to **customize/enhance** the mapping and the resulting migration code, in the final application. * It would be harder to track and **apply changes** to database when you use multiple modules. ## Using Multiple Databases -The default startup template is organized to use a single database used by all the modules and by your application. However, the ABP Framework and all the pre-built modules are designed so that **they can use multiple databases**. Each module can use its own database or you can group modules into a few databases. +The default startup template is organized to use a **single database** used by all the modules and by your application. However, the ABP Framework and all the pre-built modules are designed so that **they can use multiple databases**. Each module can use its own database or you can group modules into a few databases. This section will explain how to move Audit Logging, Setting Management and Permission Management module tables to a **second database** while the remaining modules continue to use the main ("Default") database. @@ -586,7 +173,7 @@ First step is to change the connection string section inside all the `appsetting ````json "ConnectionStrings": { - "Default": "Server=localhost;Database=BookStore;Trusted_Connection=True" + "Default": "Server=(LocalDb)\MSSQLLocalDB;Database=BookStore;Trusted_Connection=True" } ```` @@ -594,128 +181,120 @@ Change it as shown below: ````json "ConnectionStrings": { - "Default": "Server=localhost;Database=BookStore;Trusted_Connection=True", - "AbpPermissionManagement": "Server=localhost;Database=BookStore_SecondDb;Trusted_Connection=True", - "AbpSettingManagement": "Server=localhost;Database=BookStore_SecondDb;Trusted_Connection=True", - "AbpAuditLogging": "Server=localhost;Database=BookStore_SecondDb;Trusted_Connection=True" + "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=BookStore;Trusted_Connection=True", + "AbpPermissionManagement": "Server=(LocalDb)\\MSSQLLocalDB;Database=BookStore_SecondDb;Trusted_Connection=True", + "AbpSettingManagement": "Server=(LocalDb)\\MSSQLLocalDB;Database=BookStore_SecondDb;Trusted_Connection=True", + "AbpAuditLogging": "Server=(LocalDb)\MSSQLLocalDB;Database=BookStore_SecondDb;Trusted_Connection=True" } ```` -Added **three more connection strings** for the related module to target the `BookStore_SecondDb` database (they are all same). For example, `AbpPermissionManagement` is the connection string for the permission management module. +Added **three more connection strings** for the related module to target the `BookStore_SecondDb` database (they are all the same). For example, `AbpPermissionManagement` is the connection string name used by the permission management module. The `AbpPermissionManagement` is a constant [defined](https://github.com/abpframework/abp/blob/97eaa6ff5a044f503465455c86332e5a277b077a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/AbpPermissionManagementDbProperties.cs#L11) by the permission management module. ABP Framework [connection string selection system](Connection-Strings.md) selects this connection string for the permission management module if you define. If you don't define, it fallbacks to the `Default` connection string. -### Create a Second Migration Project +### Create a Second DbContext Defining the connection strings as explained above is enough **on runtime**. However, `BookStore_SecondDb` database doesn't exist yet. You need to create the database and the tables for the related modules. -Just like the main database, we want to use the EF Core Code First migration system to create and maintain the second database. - -An easy way is to create a second project (`.csproj`) for the second migration `DbContext`. - -So, create a new **class library project** in your solution named `Acme.BookStore.EntityFrameworkCore.DbMigrationsForSecondDb` (or name it better if you didn't like it). - -The `.csproj` content should be something like that: - -````xml - - - - - - netcoreapp3.1 - Acme.BookStore.DbMigrationsForSecondDb - - - - - - - - - - - -```` - -You can just copy & modify the content of the original `.DbMigrations` project. This project references to the `.EntityFrameworkCore` project. **Only difference** is the `RootNamespace` value. - -**Add a reference** to this project from the `.Web` project (otherwise, EF Core tooling doesn't allow to use the `Add-Migration` command). - -### Create the Second DbMigrationDbContext - -Create a new `DbContext` for the migrations and call the **extension methods** of the modules to configure the database tables for the related modules: +Just like the main database, we want to use the EF Core Code First migration system to create and maintain the second database. So, create a new `DbContext` class inside the `.EntityFrameworkCore` project: ````csharp -[ConnectionStringName("AbpPermissionManagement")] -public class BookStoreSecondMigrationsDbContext : - AbpDbContext +using Microsoft.EntityFrameworkCore; +using Volo.Abp.AuditLogging.EntityFrameworkCore; +using Volo.Abp.Data; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.PermissionManagement.EntityFrameworkCore; +using Volo.Abp.SettingManagement.EntityFrameworkCore; + +namespace BookStore.EntityFrameworkCore { - public BookStoreSecondMigrationsDbContext( - DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder builder) + [ConnectionStringName("AbpPermissionManagement")] + public class BookStoreSecondDbContext : + AbpDbContext { - base.OnModelCreating(builder); + public BookStoreSecondDbContext( + DbContextOptions options) + : base(options) + { + } - /* Include modules to your migration db context */ + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); - builder.ConfigurePermissionManagement(); - builder.ConfigureSettingManagement(); - builder.ConfigureAuditLogging(); + /* Include modules to your migration db context */ + builder.ConfigurePermissionManagement(); + builder.ConfigureSettingManagement(); + builder.ConfigureAuditLogging(); + } } } ```` > `[ConnectionStringName(...)]` attribute is important here and tells to the ABP Framework which connection string should be used for this `DbContext`. We've used `AbpPermissionManagement`, but all are the same. -Create a **Design Time Db Factory** class, that is used by the EF Core tooling (by `Add-Migration` and `Update-Database` PCM commands for example): +We need to register this `BookStoreSecondDbContext` class to the dependency injection system. Open the `BookStoreEntityFrameworkCoreModule` class in the `BookStore.EntityFrameworkCore` project and add the following line into the `ConfigureServices` method: ````csharp -/* This class is needed for EF Core console commands - * (like Add-Migration and Update-Database commands) */ -public class BookStoreSecondMigrationsDbContextFactory - : IDesignTimeDbContextFactory -{ - public BookStoreSecondMigrationsDbContext CreateDbContext(string[] args) - { - var configuration = BuildConfiguration(); +context.Services.AddAbpDbContext(); +```` - var builder = new DbContextOptionsBuilder() - .UseSqlServer(configuration.GetConnectionString("AbpPermissionManagement")); +We should also create a **Design Time Db Factory** class, that is used by the EF Core tooling (by `Add-Migration` and `Update-Database` PCM commands for example): - return new BookStoreSecondMigrationsDbContext(builder.Options); - } +````csharp +using System.IO; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Extensions.Configuration; - private static IConfigurationRoot BuildConfiguration() +namespace BookStore.EntityFrameworkCore +{ + /* This class is needed for EF Core console commands + * (like Add-Migration and Update-Database commands) */ + public class BookStoreSecondDbContextFactory + : IDesignTimeDbContextFactory { - var builder = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", optional: false); + public BookStoreSecondDbContext CreateDbContext(string[] args) + { + var configuration = BuildConfiguration(); + var builder = new DbContextOptionsBuilder() + .UseSqlServer(configuration.GetConnectionString("AbpPermissionManagement")); + return new BookStoreSecondDbContext(builder.Options); + } - return builder.Build(); + private static IConfigurationRoot BuildConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Path.Combine(Directory.GetCurrentDirectory(), "../BookStore.DbMigrator/")) + .AddJsonFile("appsettings.json", optional: false); + + return builder.Build(); + } } } ```` -This is similar to the class inside the `.EntityFrameworCore.DbMigrations` project, except this one uses the `AbpPermissionManagement` connection string. +Now, you can open the Package Manager Console, select the `.EntityFrameworkCore` project as the default project (make sure the `.Web` project is still the startup project) and run the following command: -Now, you can open the Package Manager Console, select the `.EntityFrameworkCore.DbMigrationsForSecondDb` project as the default project (make sure the `.Web` project is still the startup project) and run the `Add-Migration "Initial"` and `Update-Database` commands as shown below: +````bash +Add-Migration "Initial" -OutputDir "SecondDbMigrations" -Context BookStoreSecondDbContext +```` -![pmc-add-migration-initial-update-database](images/pmc-add-migration-initial-update-database.png) +This will add a `SecondDbMigrations` folder in the `.EntityFrameworkCore` project and a migration class inside it. `OutputDir` and `Context` parameters are required since we currently have two `DbContext` class and two migrations folder in the same project. -Now, you should have a new database contains only the tables needed by the related modules: +You can now run the following command to create the database and the tables inside it: -![bookstore-second-database](images/bookstore-second-database.png) +````bash +Update-Database -Context BookStoreSecondDbContext +```` + +A new database, named `BookStore_SecondDb` should be created. ### Remove Modules from the Main Database -We've **created a second database** contains tables for the Audit Logging, Permission Management and Setting Management modules. So, we should **delete these tables from the main database**. It is pretty easy. +We've **created a second database** that contains tables for the Audit Logging, Permission Management and Setting Management modules. So, we should **delete these tables from the main database**. It is pretty easy. -First, remove the following lines from the `MigrationsDbContext` class (`BookStoreMigrationsDbContext` for this example): +First, remove the following lines from the `BookStoreDbContext` class: ````csharp builder.ConfigurePermissionManagement(); @@ -723,10 +302,10 @@ builder.ConfigureSettingManagement(); builder.ConfigureAuditLogging(); ```` -Open the Package Manager Console, select the `.EntityFrameworkCore.DbMigrations` as the Default project (make sure that the `.Web` project is still the startup project) and run the following command: +Open the Package Manager Console, select the `.EntityFrameworkCore` as the Default project (make sure that the `.Web` project is still the startup project) and run the following command: ```` -Add-Migration "Removed_Audit_Setting_Permission_Modules" +Add-Migration "Removed_Audit_Setting_Permission_Modules" -Context BookStoreDbContext ```` This command will create a new migration class as shown below: @@ -764,122 +343,148 @@ Be careful in this step: * If you have a **live system**, then you should care about the **data loss**. You need to move the table contents to the second database before deleting the tables. * If you **haven't started** your project yet, you can consider to **remove all the migrations** and re-create the initial one to have a cleaner migration history. -Run the `Update-Database` command to delete the tables from your main database. +Run the following command to delete the tables from your main database: + +````bash +Update-Database -Context BookStoreDbContext +```` Notice that you've also **deleted some initial seed data** (for example, permission grants for the admin role) if you haven't copied it to the new database. If you run the application, you may not login anymore. The solution is simple: **Re-run the `.DbMigrator` console application** in your solution, it will seed the new database. ### Automate the Second Database Schema Migration -`.DbMigrator` console application can run the database seed code across multiple databases, without any additional configuration. However, it can not run the EF Core Code First Migrations inside the second database migration project. Now, you will see how to configure the console migration application to handle both databases. - -#### Implementing the IBookStoreDbSchemaMigrator +`.DbMigrator` console application can run the database seed code across multiple databases, without any additional configuration. However, it can not apply the EF Core Code First Migrations for the database of the `BookStoreSecondDbContext`. Now, you will see how to configure the console migration application to handle both databases. -`EntityFrameworkCoreBookStoreDbSchemaMigrator` class inside the `Acme.BookStore.EntityFrameworkCore.DbMigrations` project is responsible to migrate the database schema for the `BookStoreMigrationsDbContext`. It should be like that: +`EntityFrameworkCoreBookStoreDbSchemaMigrator` class inside the `Acme.BookStore.EntityFrameworkCore` project is responsible to migrate the database schema for the `BookStoreMigrationsDbContext`. It should be like that: ````csharp -[Dependency(ReplaceServices = true)] -public class EntityFrameworkCoreBookStoreDbSchemaMigrator - : IBookStoreDbSchemaMigrator, ITransientDependency -{ - private readonly IServiceProvider _serviceProvider; +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using BookStore.Data; +using Volo.Abp.DependencyInjection; - public EntityFrameworkCoreBookStoreDbSchemaMigrator( - IServiceProvider serviceProvider) +namespace BookStore.EntityFrameworkCore +{ + public class EntityFrameworkCoreBookStoreDbSchemaMigrator + : IBookStoreDbSchemaMigrator, ITransientDependency { - _serviceProvider = serviceProvider; - } + private readonly IServiceProvider _serviceProvider; - public async Task MigrateAsync() - { - /* We are intentionally resolving the BookStoreMigrationsDbContext - * from IServiceProvider (instead of directly injecting it) - * to properly get the connection string of the current tenant in the - * current scope. - */ - - await _serviceProvider - .GetRequiredService() - .Database - .MigrateAsync(); + public EntityFrameworkCoreBookStoreDbSchemaMigrator( + IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public async Task MigrateAsync() + { + /* We intentionally resolving the BookStoreDbContext + * from IServiceProvider (instead of directly injecting it) + * to properly get the connection string of the current tenant in the + * current scope. + */ + + await _serviceProvider + .GetRequiredService() + .Database + .MigrateAsync(); + } } } ```` -It implements the `IBookStoreDbSchemaMigrator` and **replaces existing services** (see the first line). +Add the following code inside the `MigrateAsync` method: -Remove the `[Dependency(ReplaceServices = true)]` line, because we will have two implementations of this interface and we want to use both. We don't want to replace one of them. +````csharp +await _serviceProvider + .GetRequiredService() + .Database + .MigrateAsync(); +```` -Create a copy of this class inside the new migration project (`Acme.BookStore.EntityFrameworkCore.DbMigrationsForSecondDb`), but use the `BookStoreSecondMigrationsDbContext`. Example implementation: +So, the `MigrateAsync` method should look like the following: ````csharp -public class EntityFrameworkCoreSecondBookStoreDbSchemaMigrator - : IBookStoreDbSchemaMigrator, ITransientDependency +public async Task MigrateAsync() { - private readonly IServiceProvider _serviceProvider; - - public EntityFrameworkCoreSecondBookStoreDbSchemaMigrator( - IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider; - } - - public async Task MigrateAsync() - { - /* We are intentionally resolving the BookStoreSecondMigrationsDbContext - * from IServiceProvider (instead of directly injecting it) - * to properly get the connection string of the current tenant in the - * current scope. - */ - - await _serviceProvider - .GetRequiredService() - .Database - .MigrateAsync(); - } + /* We intentionally resolving the BookStoreDbContext + * from IServiceProvider (instead of directly injecting it) + * to properly get the connection string of the current tenant in the + * current scope. + */ + + await _serviceProvider + .GetRequiredService() + .Database + .MigrateAsync(); + + await _serviceProvider + .GetRequiredService() + .Database + .MigrateAsync(); } ```` -> Name of this class is important for [dependency injection](Dependency-Injection.md). It should end with `BookStoreDbSchemaMigrator` to be injectable by `IBookStoreDbSchemaMigrator` reference. - -We, now, have two implementations of the `IBookStoreDbSchemaMigrator` interface, each one responsible to migrate the related database schema. +That's all. You can now run the `.DbMigrator` application to migrate & seed the databases. To test, you can delete both databases and run the `.DbMigrator` application again to see if it creates both of the databases. -#### Define a Module Class for the Second Migration Project +### Fixing the Tests -It is time to define the [module](Module-Development-Basics.md) class for this second migrations (`Acme.BookStore.EntityFrameworkCore.DbMigrationsForSecondDb`) project: +Creating a new DbContext will break the integration tests. It is easy to fix. Open the `BookStoreEntityFrameworkCoreTestModule` class in the `BookStore.EntityFrameworkCore.Tests` project, find the `CreateDatabaseAndGetConnection` method. It should be like that: ````csharp -[DependsOn( - typeof(BookStoreEntityFrameworkCoreModule) - )] -public class BookStoreEntityFrameworkCoreSecondDbMigrationsModule : AbpModule +private static SqliteConnection CreateDatabaseAndGetConnection() { - public override void ConfigureServices(ServiceConfigurationContext context) + var connection = new SqliteConnection("Data Source=:memory:"); + connection.Open(); + + var options = new DbContextOptionsBuilder() + .UseSqlite(connection) + .Options; + + using (var context = new BookStoreDbContext(options)) { - context.Services.AddAbpDbContext(); + context.GetService().CreateTables(); } + + return connection; } ```` -Now, reference `Acme.BookStore.EntityFrameworkCore.DbMigrationsForSecondDb` project from the `Acme.BookStore.DbMigrator` project and `typeof(BookStoreEntityFrameworkCoreSecondDbMigrationsModule)` to the dependency list of the `BookStoreDbMigratorModule`. `BookStoreDbMigratorModule` class should be something like that: +Change it as the following: ````csharp -[DependsOn( - typeof(AbpAutofacModule), - typeof(BookStoreEntityFrameworkCoreDbMigrationsModule), - typeof(BookStoreEntityFrameworkCoreSecondDbMigrationsModule), // ADDED THIS! - typeof(BookStoreApplicationContractsModule) - )] -public class BookStoreDbMigratorModule : AbpModule +private static SqliteConnection CreateDatabaseAndGetConnection() { - ... -} -```` + var connection = new SqliteConnection("Data Source=:memory:"); + connection.Open(); -We had a reference to the `Acme.BookStore.EntityFrameworkCore.DbMigrationsForSecondDb` project from the `Acme.BookStore.Web` project, but hadn't added module dependency since we hadn't created it before. But, now we have it and we need to add `typeof(BookStoreEntityFrameworkCoreSecondDbMigrationsModule)` to the dependency list of the `BookStoreWebModule` class. + var options = new DbContextOptionsBuilder() + .UseSqlite(connection) + .Options; -#### Run the Database Migrator! + using (var context = new BookStoreDbContext(options)) + { + context.GetService().CreateTables(); + } -You can run the `.DbMigrator` application to migrate & seed the databases. To test, you can delete both databases and run the `.DbMigrator` application again to see if it creates both of the databases. + // Add the following code -------------- + var optionsForSecondDb = new DbContextOptionsBuilder() + .UseSqlite(connection) + .Options; + + using (var context = new BookStoreSecondDbContext(optionsForSecondDb)) + { + context.GetService().CreateTables(); + } + //-------------------------------------- + + return connection; +} +```` + +Integration tests now will work. I've used the same database in the tests to keep it simple. ## Separating Host & Tenant Database Schemas @@ -887,7 +492,7 @@ In a multi-tenant solution, you may want to separate your database schemas, so h Some pre-built ABP modules are related only with the host side, like the [Tenant Management](Modules/Tenant-Management.md) module. So, in the tenant `DbContext` class you don't call `modelBuilder.ConfigureTenantManagement()` and that's all. -However, some modules, like the [Identity](Modules/Identity.md) module, is both used in host and tenant sides. It stores tenant users in the tenant database and host users in the host database. However, it stores some entities, like `IdentityClaimType`, only in the host side. In this case, you don't want to add these tables in the tenant database, even if they are not used and will always be empty. +Some modules, like the [Identity](Modules/Identity.md) module, is both used in host and tenant sides. It stores tenant users in the tenant database and host users in the host database. However, it stores some entities, like `IdentityClaimType`, only in the host side. In this case, you don't want to add these tables in the tenant database, even if they are not used and will always be empty for tenants. ABP provides a simple way to set the multi-tenancy side for a `DbContext`, so the modules can check it and decide to map tables to the database, or not. @@ -899,6 +504,7 @@ public class MyTenantDbContext : AbpDbContext modelBuilder.SetMultiTenancySide(MultiTenancySides.Tenant); base.OnModelCreating(modelBuilder); + modelBuilder.ConfigureIdentity(); modelBuilder.ConfigureFeatureManagement(); modelBuilder.ConfigureAuditLogging(); @@ -948,4 +554,4 @@ This document explains how to split your databases and manage your database migr ## Source Code -You can find the source code of the example project referenced by this document [here](https://github.com/abpframework/abp-samples/tree/master/EfCoreMigrationDemo). However, you need to read and understand this document in order to understand the example project's source code. \ No newline at end of file +You can find the source code of the example project referenced by this document [here](https://github.com/abpframework/abp-samples/tree/master/EfCoreMigrationDemo). You can also find the changes explained in this document as a [single commit](https://github.com/abpframework/abp-samples/pull/95/commits/c2ffd76175e0a6fdfcf6477bbaea23dc2793fedd). \ No newline at end of file diff --git a/docs/en/Entity-Framework-Core-MySQL.md b/docs/en/Entity-Framework-Core-MySQL.md index 14f419557e..92a9cfb36e 100644 --- a/docs/en/Entity-Framework-Core-MySQL.md +++ b/docs/en/Entity-Framework-Core-MySQL.md @@ -15,7 +15,7 @@ Find ***YourProjectName*EntityFrameworkCoreModule** class inside the `.EntityFra Find `UseSqlServer()` calls in your solution. Check the following files: * *YourProjectName*EntityFrameworkCoreModule.cs inside the `.EntityFrameworkCore` project. Replace `UseSqlServer()` with `UseMySQL()`. -* *YourProjectName*MigrationsDbContextFactory.cs inside the `.EntityFrameworkCore.DbMigrations` project. Replace `UseSqlServer()` with `UseMySql()`. Then add a new parameter (`ServerVersion`) to `UseMySql()` method. Example: `.UseMySql(configuration.GetConnectionString("Default"), ServerVersion.FromString("8.0.21-mysql"))`. See [this issue](https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/pull/1233) for more information about `ServerVersion`) +* *YourProjectName*DbContextFactory.cs inside the `.EntityFrameworkCore` project. Replace `UseSqlServer()` with `UseMySql()`. Then add a new parameter (`ServerVersion`) to `UseMySql()` method. Example: `.UseMySql(configuration.GetConnectionString("Default"), ServerVersion.FromString("8.0.21-mysql"))`. See [this issue](https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/pull/1233) for more information about `ServerVersion`) > Depending on your solution structure, you may find more code files need to be changed. @@ -29,8 +29,8 @@ You typically will change the `appsettings.json` inside the `.DbMigrator` and `. The startup template uses [Entity Framework Core's Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). EF Core Migrations depend on the selected DBMS provider. So, changing the DBMS provider will cause the migration fails. -* Delete the Migrations folder under the `.EntityFrameworkCore.DbMigrations` project and re-build the solution. -* Run `Add-Migration "Initial"` on the Package Manager Console (select the `.DbMigrator` (or `.Web`) project as the startup project in the Solution Explorer and select the `.EntityFrameworkCore.DbMigrations` project as the default project in the Package Manager Console). +* Delete the Migrations folder under the `.EntityFrameworkCore` project and re-build the solution. +* Run `Add-Migration "Initial"` on the Package Manager Console (select the `.DbMigrator` (or `.Web`) project as the startup project in the Solution Explorer and select the `.EntityFrameworkCore` project as the default project in the Package Manager Console). This will create a database migration with all database objects (tables) configured. diff --git a/docs/en/Entity-Framework-Core-Oracle-Devart.md b/docs/en/Entity-Framework-Core-Oracle-Devart.md index 23e660378c..95e05db427 100644 --- a/docs/en/Entity-Framework-Core-Oracle-Devart.md +++ b/docs/en/Entity-Framework-Core-Oracle-Devart.md @@ -19,20 +19,20 @@ Also replace `using Volo.Abp.EntityFrameworkCore.SqlServer;` with `using Volo.Ab Find `UseSqlServer()` calls in your solution, replace with `UseOracle()`. Check the following files: * *YourProjectName*EntityFrameworkCoreModule.cs inside the `.EntityFrameworkCore` project. -* *YourProjectName*MigrationsDbContextFactory.cs inside the `.EntityFrameworkCore.DbMigrations` project. +* *YourProjectName*DbContextFactory.cs inside the `.EntityFrameworkCore` project. -In the `CreateDbContext()` method of the *YourProjectName*MigrationsDbContextFactory.cs, replace the following code block +In the `CreateDbContext()` method of the *YourProjectName*DbContextFactory.cs, replace the following code block ```csharp -var builder = new DbContextOptionsBuilder() +var builder = new DbContextOptionsBuilder() .UseSqlServer(configuration.GetConnectionString("Default")); ``` with this one ```csharp -var builder = (DbContextOptionsBuilder) - new DbContextOptionsBuilder().UseOracle +var builder = (DbContextOptionsBuilder) + new DbContextOptionsBuilder().UseOracle ( configuration.GetConnectionString("Default") ); @@ -51,8 +51,8 @@ You typically will change the `appsettings.json` inside the `.DbMigrator` and `. The startup template uses [Entity Framework Core's Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/) by default. EF Core Migrations depend on the selected DBMS provider. Changing the DBMS provider, may not work with the existing migrations. -* Delete the `Migrations` folder under the `.EntityFrameworkCore.DbMigrations` project and re-build the solution. -* Run `Add-Migration "Initial"` on the Package Manager Console window (select the `.DbMigrator` (or `.Web`) project as the startup project in the Solution Explorer and select the `.EntityFrameworkCore.DbMigrations` project as the default project in the Package Manager Console). +* Delete the `Migrations` folder under the `.EntityFrameworkCore` project and re-build the solution. +* Run `Add-Migration "Initial"` on the Package Manager Console window (select the `.DbMigrator` (or `.Web`) project as the startup project in the Solution Explorer and select the `.EntityFrameworkCore` project as the default project in the Package Manager Console). This will scaffold a new migration for Oracle. diff --git a/docs/en/Entity-Framework-Core-Oracle-Official.md b/docs/en/Entity-Framework-Core-Oracle-Official.md index 8ae1a1bac0..d983dc5e41 100644 --- a/docs/en/Entity-Framework-Core-Oracle-Official.md +++ b/docs/en/Entity-Framework-Core-Oracle-Official.md @@ -17,19 +17,19 @@ Also replace `using Volo.Abp.EntityFrameworkCore.SqlServer;` with `using Volo.Ab Find `UseSqlServer()` calls in your solution, replace with `UseOracle()`. Check the following files: * *YourProjectName*EntityFrameworkCoreModule.cs inside the `.EntityFrameworkCore` project. -* *YourProjectName*MigrationsDbContextFactory.cs inside the `.EntityFrameworkCore.DbMigrations` project. +* *YourProjectName*DbContextFactory.cs inside the `.EntityFrameworkCore` project. -In the `CreateDbContext()` method of the *YourProjectName*MigrationsDbContextFactory.cs, replace the following code block +In the `CreateDbContext()` method of the *YourProjectName*DbContextFactory.cs, replace the following code block ```csharp -var builder = new DbContextOptionsBuilder() +var builder = new DbContextOptionsBuilder() .UseSqlServer(configuration.GetConnectionString("Default")); ``` with this one (just changes `UseSqlServer(...)` to `UseOracle(...)`) ```csharp -var builder = new DbContextOptionsBuilder() +var builder = new DbContextOptionsBuilder() .UseOracle(configuration.GetConnectionString("Default")); ``` @@ -46,8 +46,8 @@ You typically will change the `appsettings.json` inside the `.DbMigrator` and `. The startup template uses [Entity Framework Core's Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/) by default. EF Core Migrations depend on the selected DBMS provider. Changing the DBMS provider, may not work with the existing migrations. -* Delete the `Migrations` folder under the `.EntityFrameworkCore.DbMigrations` project and re-build the solution. -* Run `Add-Migration "Initial"` on the Package Manager Console window (select the `.DbMigrator` (or `.Web`) project as the startup project in the Solution Explorer and select the `.EntityFrameworkCore.DbMigrations` project as the default project in the Package Manager Console). +* Delete the `Migrations` folder under the `.EntityFrameworkCore` project and re-build the solution. +* Run `Add-Migration "Initial"` on the Package Manager Console window (select the `.DbMigrator` (or `.Web`) project as the startup project in the Solution Explorer and select the `.EntityFrameworkCore` project as the default project in the Package Manager Console). This will scaffold a new migration for Oracle. diff --git a/docs/en/Entity-Framework-Core-Other-DBMS.md b/docs/en/Entity-Framework-Core-Other-DBMS.md index d9190df844..8c9078583d 100644 --- a/docs/en/Entity-Framework-Core-Other-DBMS.md +++ b/docs/en/Entity-Framework-Core-Other-DBMS.md @@ -62,20 +62,18 @@ MySQL connection strings are different than SQL Server connection strings. So, c You typically will change the `appsettings.json` inside the `.DbMigrator` and `.Web` projects, but it depends on your solution structure. -## Change the Migrations DbContext +## DBMS restrictions MySQL DBMS has some slight differences than the SQL Server. Some module database mapping configuration (especially the field lengths) causes problems with MySQL. For example, some of the the [IdentityServer module](Modules/IdentityServer.md) tables has such problems and it provides an option to configure the fields based on your DBMS. -The startup template contains a *YourProjectName*MigrationsDbContext which is responsible to maintain and migrate the database schema. This DbContext basically calls extension methods of the depended modules to configure their database tables. - -Open the *YourProjectName*MigrationsDbContext and change the `builder.ConfigureIdentityServer();` line as shown below: +The module may provide some built-in solutions. You can configure it via `ModelBuilder`. eg: `Identity Server` module. -````csharp +```csharp builder.ConfigureIdentityServer(options => { options.DatabaseProvider = EfCoreDatabaseProvider.MySql; }); -```` +``` Then `ConfigureIdentityServer()` method will set the field lengths to not exceed the MySQL limits. Refer to related module documentation if you have any problem while creating or executing the database migrations. @@ -83,8 +81,8 @@ Then `ConfigureIdentityServer()` method will set the field lengths to not exceed The startup template uses [Entity Framework Core's Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). EF Core Migrations depend on the selected DBMS provider. So, changing the DBMS provider will cause the migration fails. -* Delete the Migrations folder under the `.EntityFrameworkCore.DbMigrations` project and re-build the solution. -* Run `Add-Migration "Initial"` on the Package Manager Console (select the `.DbMigrator` (or `.Web`) project as the startup project in the Solution Explorer and select the `.EntityFrameworkCore.DbMigrations` project as the default project in the Package Manager Console). +* Delete the Migrations folder under the `.EntityFrameworkCore` project and re-build the solution. +* Run `Add-Migration "Initial"` on the Package Manager Console (select the `.DbMigrator` (or `.Web`) project as the startup project in the Solution Explorer and select the `.EntityFrameworkCore` project as the default project in the Package Manager Console). This will create a database migration with all database objects (tables) configured. @@ -94,16 +92,4 @@ Run the `.DbMigrator` project to create the database and seed the initial data. It is ready. Just run the application and enjoy coding. -## DBMS restrictions - -Different DBMS may have some restrictions, such as the maximum length of field names, index length, etc. -The module may provide some built-in solutions. You can configure it via `ModelBuilder`. eg: `Identity Server` module. - -```csharp -builder.ConfigureIdentityServer(options => -{ - options.DatabaseProvider = EfCoreDatabaseProvider.MySql; -}); -``` - Related discussions: https://github.com/abpframework/abp/issues/1920 \ No newline at end of file diff --git a/docs/en/Entity-Framework-Core-PostgreSQL.md b/docs/en/Entity-Framework-Core-PostgreSQL.md index 4c6099b809..f6a0c26806 100644 --- a/docs/en/Entity-Framework-Core-PostgreSQL.md +++ b/docs/en/Entity-Framework-Core-PostgreSQL.md @@ -15,7 +15,7 @@ Find ***YourProjectName*EntityFrameworkCoreModule** class inside the `.EntityFra Find `UseSqlServer()` call in *YourProjectName*EntityFrameworkCoreModule.cs inside the `.EntityFrameworkCore` project and replace with `UseNpgsql()`. -Find `UseSqlServer()` call in *YourProjectName*MigrationsDbContextFactory.cs inside the `.EntityFrameworkCore.DbMigrations` project and replace with `UseNpgsql()`. +Find `UseSqlServer()` call in *YourProjectName*DbContextFactory.cs inside the `.EntityFrameworkCore` project and replace with `UseNpgsql()`. > Depending on your solution structure, you may find more `UseSqlServer()` calls that needs to be changed. @@ -29,8 +29,8 @@ You typically will change the `appsettings.json` inside the `.DbMigrator` and `. The startup template uses [Entity Framework Core's Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). EF Core Migrations depend on the selected DBMS provider. So, changing the DBMS provider will cause the migration fails. -* Delete the Migrations folder under the `.EntityFrameworkCore.DbMigrations` project and re-build the solution. -* Run `Add-Migration "Initial"` on the Package Manager Console (select the `.DbMigrator` (or `.Web`) project as the startup project in the Solution Explorer and select the `.EntityFrameworkCore.DbMigrations` project as the default project in the Package Manager Console). +* Delete the Migrations folder under the `.EntityFrameworkCore` project and re-build the solution. +* Run `Add-Migration "Initial"` on the Package Manager Console (select the `.DbMigrator` (or `.Web`) project as the startup project in the Solution Explorer and select the `.EntityFrameworkCore` project as the default project in the Package Manager Console). This will create a database migration with all database objects (tables) configured. diff --git a/docs/en/Entity-Framework-Core-SQLite.md b/docs/en/Entity-Framework-Core-SQLite.md index 2a13bf530c..81d4b72de2 100644 --- a/docs/en/Entity-Framework-Core-SQLite.md +++ b/docs/en/Entity-Framework-Core-SQLite.md @@ -15,7 +15,7 @@ Find ***YourProjectName*EntityFrameworkCoreModule** class inside the `.EntityFra Find `UseSqlServer()` calls in your solution, replace with `UseSqlite()`. Check the following files: * *YourProjectName*EntityFrameworkCoreModule.cs inside the `.EntityFrameworkCore` project. -* *YourProjectName*MigrationsDbContextFactory.cs inside the `.EntityFrameworkCore.DbMigrations` project. +* *YourProjectName*DbContextFactory.cs inside the `.EntityFrameworkCore` project. > Depending on your solution structure, you may find more code files need to be changed. @@ -39,8 +39,8 @@ You typically will change the `appsettings.json` inside the `.DbMigrator` and `. The startup template uses [Entity Framework Core's Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). EF Core Migrations depend on the selected DBMS provider. So, changing the DBMS provider will cause the migration fails. -* Delete the Migrations folder under the `.EntityFrameworkCore.DbMigrations` project and re-build the solution. -* Run `Add-Migration "Initial"` on the Package Manager Console (select the `.DbMigrator` (or `.Web`) project as the startup project in the Solution Explorer and select the `.EntityFrameworkCore.DbMigrations` project as the default project in the Package Manager Console). +* Delete the Migrations folder under the `.EntityFrameworkCore` project and re-build the solution. +* Run `Add-Migration "Initial"` on the Package Manager Console (select the `.DbMigrator` (or `.Web`) project as the startup project in the Solution Explorer and select the `.EntityFrameworkCore` project as the default project in the Package Manager Console). This will create a database migration with all database objects (tables) configured. diff --git a/docs/en/Entity-Framework-Core.md b/docs/en/Entity-Framework-Core.md index fcbf5d8091..f60fcfc490 100644 --- a/docs/en/Entity-Framework-Core.md +++ b/docs/en/Entity-Framework-Core.md @@ -604,23 +604,77 @@ ObjectExtensionManager.Instance ); ```` -If the related module has implemented this feature (by using the `ConfigureEfCoreEntity` explained below), then the new property is added to the model. Then you need to run the standard `Add-Migration` and `Update-Database` commands to update your database to add the new field. +### MapEfCoreEntity ->`MapEfCoreProperty` method must be called before using the related `DbContext`. It is a static method. The best way is to use it in your application as earlier as possible. The application startup template has a `YourProjectNameEfCoreEntityExtensionMappings` class that is safe to use this method inside. +`MapEfCoreEntity` is a shortcut extension method to configure the `Entity`. -### ConfigureEfCoreEntity +**Example**: Set the max length of `Name` to the `IdentityRole` entity: -If you are building a reusable module and want to allow application developers to add properties to your entities, you can use the `ConfigureEfCoreEntity` extension method in your entity mapping. However, there is a shortcut extension method `ConfigureObjectExtensions` that can be used while configuring the entity mapping: +````csharp +ObjectExtensionManager.Instance + .MapEfCoreEntity(builder => + { + builder.As>().Property(x => x.Name).HasMaxLength(200); + }); +```` + +### MapEfCoreDbContext + +`MapEfCoreDbContext` is a shortcut extension method to configure the `DbContext`. + +**Example**: Set the max length of `Name` to the `IdentityRole` entity of `IdentityDbContext`: ````csharp -builder.Entity(b => +ObjectExtensionManager.Instance.MapEfCoreDbContext(b => { - b.ConfigureObjectExtensions(); - //... + b.Entity().Property(x => x.Name).HasMaxLength(200); }); ```` -> If you call `ConfigureByConvention()` extension method (like `b.ConfigureByConvention()` for this example), ABP Framework internally calls the `ConfigureObjectExtensions` method. It is a **best practice** to use the `ConfigureByConvention()` method since it also configures database mapping for base properties by convention. +If the related module has implemented this feature(explained below), then the new property is added to the model or the DbContext/Entity configure changed. Then you need to run the standard `Add-Migration` and `Update-Database` commands to update your database to add the new field. + +> The `MapEfCoreProperty`, `MapEfCoreEntity` and `MapEfCoreDbContext` methods must be called before using the related `DbContext`. It is a static method. The best way is to use it in your application as earlier as possible. The application startup template has a `YourProjectNameEfCoreEntityExtensionMappings` class that is safe to use this method inside. + +### ConfigureEfCoreEntity, ApplyObjectExtensionMappings and TryConfigureObjectExtensions + +If you are building a reusable module and want to allow application developers to add properties to your entities, you can use the `ConfigureEfCoreEntity`, `ApplyObjectExtensionMappings` and `TryConfigureObjectExtensions` extension methods in your entity mapping. + +**Example**: +````csharp +public static class QADbContextModelCreatingExtensions +{ + public static void ConfigureQA( + this ModelBuilder builder, + Action optionsAction = null) + { + Check.NotNull(builder, nameof(builder)); + + var options = new QAModelBuilderConfigurationOptions( + QADatabaseDbProperties.DbTablePrefix, + QADatabaseDbProperties.DbSchema + ); + + optionsAction?.Invoke(options); + + builder.Entity(b => + { + b.ToTable(options.TablePrefix + "Questions", options.Schema); + b.ConfigureByConvention(); + //... + + //Call this in the end of buildAction. + b.ApplyObjectExtensionMappings(); + }); + + //... + + //Call this in the end of ConfigureQA. + builder.TryConfigureObjectExtensions(); + } +} +```` + +> If you call `ConfigureByConvention()` extension method (like `b.ConfigureByConvention()` for this example), ABP Framework internally calls the `ConfigureObjectExtensions` and `ConfigureEfCoreEntity` methods. It is a **best practice** to use the `ConfigureByConvention()` method since it also configures database mapping for base properties by convention. See the "*ConfigureByConvention Method*" section above for more information. @@ -812,4 +866,4 @@ public class MyCustomEfCoreBulkOperationProvider ## See Also * [Entities](Entities.md) -* [Repositories](Repositories.md) \ No newline at end of file +* [Repositories](Repositories.md) diff --git a/docs/en/Global-Features.md b/docs/en/Global-Features.md index 3858e25aa1..0029127eb0 100644 --- a/docs/en/Global-Features.md +++ b/docs/en/Global-Features.md @@ -8,8 +8,6 @@ Global Features system is especially useful if you want to develop a reusable ap ## Installation > This package is already installed by default with the startup template. So, most of the time, you don't need to install it manually. -### Using the ABP CLI - Open a command line window in the folder of the project (.csproj file) and type the following command: ```bash diff --git a/docs/en/Index.md b/docs/en/Index.md index de88e2f2f8..8483d61612 100644 --- a/docs/en/Index.md +++ b/docs/en/Index.md @@ -29,8 +29,9 @@ ABP has a **comprehensive documentation** that not only explains the ABP Framewo ABP offers a complete, modular and layered software architecture based on [Domain Driven Design](Domain-Driven-Design.md) principles and patterns. It also provides the necessary infrastructure to implement this architecture. * See the [Modularity](Module-Development-Basics.md) document to understand the module system. -* [Implementing Domain Driven Design](Domain-Driven-Design-Implementation-Guide.md) document is an ultimate guide for who want to understand and implement the DDD. +* [Implementing Domain Driven Design book](https://abp.io/books/implementing-domain-driven-design?ref=doc) is an ultimate guide for who want to understand and implement the DDD with the ABP Framework. * [Microservice Architecture](Microservice-Architecture.md) document explains how ABP helps to create a microservice solution. +* [Multi-Tenancy](Multi-Tenancy.md) document introduces multi-tenancy and explores the ABP multi-tenancy infrastructure. ### Infrastructure @@ -55,6 +56,14 @@ See the [Application Modules](Modules/Index.md) document for all pre-built modul The [Startup templates](Startup-Templates/Index.md) are pre-built Visual Studio solution templates. You can create your own solution based on these templates to **immediately start your development**. +## Free E-Book: Implementing Domain Driven Design + +![Implementing Domain Driven Design](images/implementing-domain-driven-design-book.png) + +A **practical guide** for implementing Domain Driven Design (DDD). While the implementation details are **based on the ABP Framework** infrastructure, the basic concepts, principles and models can be applied to any solution, even if it is not a .NET solution. + +[Click here to get your free copy](https://abp.io/books/implementing-domain-driven-design?ref=doc). + ## ABP Community ### The Source Code diff --git a/docs/en/Local-Event-Bus.md b/docs/en/Local-Event-Bus.md index d937a1caff..82c839a996 100644 --- a/docs/en/Local-Event-Bus.md +++ b/docs/en/Local-Event-Bus.md @@ -158,7 +158,53 @@ If you perform **database operations** and use the [repositories](Repositories.m > 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 +## Exception Handling + +ABP provides exception handling and retries when an exception occurs. + +Enable exception handling: + +```csharp +public override void PreConfigureServices(ServiceConfigurationContext context) +{ + PreConfigure(options => + { + options.UseRetryStrategy(); + }); +} +``` + +When an exception occurs, it will retry every three seconds up to the maximum number of retries(default is 3) and throw the original exception, you can change the number of retries and the retry interval: + +```csharp +PreConfigure(options => +{ + options.UseRetryStrategy(retryStrategyOptions => + { + retryStrategyOptions.IntervalMillisecond = 0; + retryStrategyOptions.MaxRetryAttempts = 1; + }); +}); +``` + +### Error Handle Selector + +By default all event types will be exception handling, you can use `ErrorHandleSelector` of `AbpEventBusOptions` to change it: + +```csharp +PreConfigure(options => +{ + options.ErrorHandleSelector = type => type == typeof(MyExceptionHandleEventData); +}); +``` + +`options.ErrorHandleSelector` actually a list of type predicate. You can write a lambda expression to define your filter. + +### Customize Exception Handling + +ABP defines the `IEventErrorHandler` interface and implemented by `LocalEventErrorHandler`, you can replace it via [dependency injection](Dependency-Injection.md) + +### Transaction & Exception Behavior When an event published, subscribed event handlers are immediately executed. So; diff --git a/docs/en/Modules/Account.md b/docs/en/Modules/Account.md index 94cdb7af95..72f6f55c0e 100644 --- a/docs/en/Modules/Account.md +++ b/docs/en/Modules/Account.md @@ -1,12 +1,54 @@ # Account Module -This module provides necessary UI pages/components to make the user login and register to the application. +Account module implements the basic authentication features like **login**, **register**, **forgot password** and **account management**. -> This document is incomplete. +This module is based on [Microsoft's Identity library](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity) and the [Identity Module](Identity.md). It has [IdentityServer](https://github.com/IdentityServer) integration (based on the [IdentityServer Module](IdentityServer.md)) to provide **single sign-on**, access control and other advanced authentication features. + +## How to Install + +This module comes as pre-installed (as NuGet/NPM packages) when you [create a new solution](https://abp.io/get-started) with the ABP Framework. You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module. + +### The Source Code + +The source code of this module can be accessed [here](https://github.com/abpframework/abp/tree/dev/modules/account). The source code is licensed with [MIT](https://choosealicense.com/licenses/mit/), so you can freely use and customize it. + +## User Interface + +This section introduces the main pages provided by this module. + +### Login + +`/Account/Login` page provides the login functionality. + +![account-module-login](../images/account-module-login.png) + +Social/external login buttons becomes visible if you setup it. See the *Social/External Logins* section below. Register and Forgot password and links redirect to the pages explained in the next sections. + +### Register + +`/Account/Register` page provides the new user registration functionality. + +![account-module-register](../images/account-module-register.png) + +### Forgot Password & Reset Password + +`/Account/ForgotPassword` page provides a way of sending password reset link to user's email address. The user then clicks to the link and determines a new password. + +![account-module-forgot-password](../images/account-module-forgot-password.png) + +### Account Management + +`/Account/Manage` page is used to change password and personal information of the user. + +![account-module-manage-account](../images/account-module-manage-account.png) + +## IdentityServer Integration + +[Volo.Abp.Account.Web.IdentityServer](https://www.nuget.org/packages/Volo.Abp.Account.Web.IdentityServer) package provides integration for the [IdentityServer](https://github.com/IdentityServer). This package comes as installed with the [application startup template](../Startup-Templates/Application.md). See the [IdentityServer Module](IdentityServer.md) documentation. ## Social/External Logins -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. +The Account Module 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 @@ -32,7 +74,3 @@ context.Services.AddAuthentication() ```` > 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. - -### Other UI Types - -Beginning from the v3.1, the [Angular UI](../UI/Angular/Quick-Start.md) uses authorization code flow (as a best practice) to authenticate the user by redirecting to the MVC UI login page. So, even if you are using the Angular UI, social/external login integration is same as explained above and it will work out of the box. As similar, The [Blazor UI](../UI/Blazor/Overall.md) also uses the MVC UI to logic. \ No newline at end of file diff --git a/docs/en/Modules/Audit-Logging.md b/docs/en/Modules/Audit-Logging.md index 039c61d3a2..30e1ef4e04 100644 --- a/docs/en/Modules/Audit-Logging.md +++ b/docs/en/Modules/Audit-Logging.md @@ -2,6 +2,59 @@ The Audit Logging Module basically implements the `IAuditingStore` to save the audit log objects to a database. -> Audit Logging module is already installed and configured for [the startup templates](../Startup-Templates/Index.md). So, most of the times you don't need to manually add this module to your application. +> This document covers only the audit logging module which persists audit logs to a database. See [the audit logging](../Audit-Logging.md) document for more about the audit logging system. -See [the audit logging system](../Audit-Logging.md) document for more about the audit logging. \ No newline at end of file +## How to Install + +This module comes as pre-installed (as NuGet/NPM packages) when you [create a new solution](https://abp.io/get-started) with the ABP Framework. You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module. + +### The Source Code + +The source code of this module can be accessed [here](https://github.com/abpframework/abp/tree/dev/modules/audit-logging). The source code is licensed with [MIT](https://choosealicense.com/licenses/mit/), so you can freely use and customize it. + +## Internals + +### Domain Layer + +#### Aggregates + +- `AuditLog` (aggregate root): Represents an audit log record in the system. + - `EntityChange` (collection): Changed entities of audit log. + - `AuditLogAction` (collection): Executed actions of audit log. + +#### Repositories + +Following custom repositories are defined for this module: + +- `IAuditLogRepository` + +### Database providers + +#### Common + +##### Table / collection prefix & schema + +All tables/collections use the `Abp` prefix by default. Set static properties on the `AbpAuditLoggingDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). + +##### Connection string + +This module uses `AbpAuditLogging` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string. See the [connection strings](https://docs.abp.io/en/abp/latest/Connection-Strings) documentation for details. + +#### Entity Framework Core + +##### Tables + +- **AbpAuditLogs** + - AbpAuditLogActions + - AbpEntityChanges + - AbpEntityPropertyChanges + +#### MongoDB + +##### Collections + +- **AbpAuditLogs** + +## See Also + +* [Audit logging system](../Audit-Logging.md) \ No newline at end of file diff --git a/docs/en/Modules/Background-Jobs.md b/docs/en/Modules/Background-Jobs.md index 18d7f43486..6cce8a6c95 100644 --- a/docs/en/Modules/Background-Jobs.md +++ b/docs/en/Modules/Background-Jobs.md @@ -1,3 +1,55 @@ # Background Jobs Module -TODO \ No newline at end of file +The Background Jobs module implements the `IBackgroundJobStore` interface and makes possible to use the default background job manager of the ABP Framework. If you don't want to use this module, then you should implement the `IBackgroundJobStore` interface yourself. + +> This document covers only the background jobs module which persists background jobs to a database. See [the background jobs](../Background-Jobs.md) document for more about the background jobs system. + +## How to Install + +This module comes as pre-installed (as NuGet/NPM packages) when you [create a new solution](https://abp.io/get-started) with the ABP Framework. You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module. + +### The Source Code + +The source code of this module can be accessed [here](https://github.com/abpframework/abp/tree/dev/modules/background-jobs). The source code is licensed with [MIT](https://choosealicense.com/licenses/mit/), so you can freely use and customize it. + +## Internals + +### Domain Layer + +#### Aggregates + +- `BackgroundJobRecord` (aggregate root): Represents a background job record. + +#### Repositories + +Following custom repositories are defined for this module: + +- `IBackgroundJobRepository` + +### Database providers + +#### Common + +##### Table / collection prefix & schema + +All tables/collections use the `Abp` prefix by default. Set static properties on the `BackgroundJobsDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). + +##### Connection string + +This module uses `AbpBackgroundJobs` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string. See the [connection strings](https://docs.abp.io/en/abp/latest/Connection-Strings) documentation for details. + +#### Entity Framework Core + +##### Tables + +- **AbpBackgroundJobs** + +#### MongoDB + +##### Collections + +- **AbpBackgroundJobs** + +## See Also + +* [Background job system](../Background-Jobs.md) \ No newline at end of file diff --git a/docs/en/Modules/Docs.md b/docs/en/Modules/Docs.md index 90c0699dfa..4c764ee2c6 100644 --- a/docs/en/Modules/Docs.md +++ b/docs/en/Modules/Docs.md @@ -38,7 +38,7 @@ Note that this document covers `Entity Framework Core` provider but you can also ### 2- Running The Empty Application -After you download the project, extract the ZIP file and open `Acme.MyProject.sln`. You will see that the solution consists of `Application`, `Application.Contracts`, `DbMigrator`, `Domain`, `Domain.Shared`, `EntityFrameworkCore`, `EntityFrameworkCore.DbMigations`, `HttpApi`, `HttpApi.Client` and `Web` projects. Right click on `Acme.MyProject.Web` project and **Set as StartUp Project**. +After you download the project, extract the ZIP file and open `Acme.MyProject.sln`. You will see that the solution consists of `Application`, `Application.Contracts`, `DbMigrator`, `Domain`, `Domain.Shared`, `EntityFrameworkCore`, `HttpApi`, `HttpApi.Client` and `Web` projects. Right click on `Acme.MyProject.Web` project and **Set as StartUp Project**. ![Create a new project](../images/docs-module_solution-explorer.png) @@ -237,15 +237,15 @@ If you choose Entity Framework as your database provider, you need to configure } ``` -* Open `Package Manager Console` in `Visual Studio` and choose `Acme.MyProject.EntityFrameworkCore.DbMigrations` as default project. Then write the below command to add the migration for Docs Module. +* Open `Package Manager Console` in `Visual Studio` and choose `Acme.MyProject.EntityFrameworkCore` as default project. Then write the below command to add the migration for Docs Module. ```csharp add-migration Added_Docs_Module ``` - When the command successfully executes , you will see a new migration file named as `20181221111621_Added_Docs_Module` in the folder `Acme.MyProject.EntityFrameworkCore.DbMigrations\Migrations`. + When the command successfully executes , you will see a new migration file named as `20181221111621_Added_Docs_Module` in the folder `Acme.MyProject.EntityFrameworkCore\Migrations`. - Now, update the database for Docs module database changes. To do this run the below code on `Package Manager Console` in `Visual Studio`. Be sure `Acme.MyProject.EntityFrameworkCore.DbMigrations` is still default project. + Now, update the database for Docs module database changes. To do this run the below code on `Package Manager Console` in `Visual Studio`. Be sure `Acme.MyProject.EntityFrameworkCore` is still default project. ```csharp update-database @@ -623,6 +623,46 @@ If your `IElasticClient` needs additional configuration, please use override `IE } ``` + +## Row Highlighting + +You can apply highlight to specific code lines or a range of sequential lines. +See the following examples: + +``` + ```C# {3, 5} + public class Book : Entity + { + public string Name { get; set; } + public string Surname { get; set; } + } + ``` +``` + +``` + ```C# {2-4} + public class Book : Entity + { + public string Name { get; set; } + public string Surname { get; set; } + } + ``` +``` + +``` + ```C# {1, 2-4} + public class Book : Entity + { + public string Name { get; set; } + public string Surname { get; set; } + } + ``` +``` + +--- + + + ## Next Docs Module is also available as a standalone application. Check out [VoloDocs](../Apps/VoloDocs). diff --git a/docs/en/Modules/Feature-Management.md b/docs/en/Modules/Feature-Management.md index 968b742092..9677e2f470 100644 --- a/docs/en/Modules/Feature-Management.md +++ b/docs/en/Modules/Feature-Management.md @@ -1,5 +1,106 @@ # Feature Management Module -> This module implements the `IFeatureStore` to store and manage feature values in a database. See the [Features System document](../Features.md) to understand the features first. +The Feature Management module implements the `IFeatureManagementStore` interface defined by the [Feature System](../Features.md). + +> This document covers only the feature management module which persists feature values to a database. See [the features](../Features.md) document for more about the feature system. + +## How to Install + +This module comes as pre-installed (as NuGet/NPM packages) when you [create a new solution](https://abp.io/get-started) with the ABP Framework. You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module. + +### The Source Code + +The source code of this module can be accessed [here](https://github.com/abpframework/abp/tree/dev/modules/feature-management). The source code is licensed with [MIT](https://choosealicense.com/licenses/mit/), so you can freely use and customize it. + +## User Interface + +### Feature Management Dialog + +Feature management module provides a reusable dialog to manage features related to an object. For example, the [Tenant Management Module](Tenant-Management.md) uses it to manage features of tenants in the Tenant Management page. + +![features-module-opening](../images/features-module-opening.png) + +When you click *Actions* -> *Features* for a tenant, the feature management dialog is opened. An example screenshot from this dialog with two features defined: + +![features-modal](../images/features-modal.png) + +In this dialog, you can enable, disable or set values for the features for a tenant. + +## IFeatureManager + +`IFeatureManager` is the main service provided by this module. It is used to read and change the setting values for the tenants in a multi-tenant application. `IFeatureManager` is typically used by the *Feature Management Dialog*. However, you can inject it if you need to set a feature value. + +> If you just want to read feature values, use the `IFeatureChecker` as explained in the [Features document](../Features.md). + +**Example: Get/set a feature's value for a tenant** + +````csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.FeatureManagement; + +namespace Demo +{ + public class MyService : ITransientDependency + { + private readonly IFeatureManager _featureManager; + + public MyService(IFeatureManager featureManager) + { + _featureManager = featureManager; + } + + public async Task SetFeatureDemoAsync(Guid tenantId, string value) + { + await _featureManager + .SetForTenantAsync(tenantId, "Feature1", value); + + var currentValue = await _featureManager + .GetOrNullForTenantAsync("Feature1", tenantId); + } + } +} +```` + +## Feature Management Providers + +Features Management Module is extensible, just like the [features system](../Features.md). You can extend it by defining feature management providers. There are 3 pre-built feature management providers registered it the following order: + +* `DefaultValueFeatureManagementProvider`: Gets the value from the default value of the feature definition. It can not set the default value since default values are hard-coded on the feature definition. +* `EditionFeatureManagementProvider`: Gets or sets the feature values for an edition. Edition is a group of features assigned to tenants. Edition system has not implemented by the Tenant Management module. You can implement it yourself or purchase the ABP Commercial [SaaS Module](https://commercial.abp.io/modules/Volo.Saas) which implements it and also provides more SaaS features, like subscription and payment. +* `TenantFeatureManagementProvider`: Gets or sets the features values for tenants. + +`IFeatureManager` uses these providers on get/set methods. Typically, every feature management provider defines extension methods on the `IFeatureManager` service (like `SetForTenantAsync` defined by the tenant feature management provider). + +If you want to create your own provider, implement the `IFeatureManagementProvider` interface or inherit from the `FeatureManagementProvider` base class: + +````csharp +public class CustomFeatureProvider : FeatureManagementProvider +{ + public override string Name => "Custom"; + + public CustomFeatureProvider(IFeatureManagementStore store) + : base(store) + { + } +} +```` + +`FeatureManagementProvider` base class makes the default implementation (using the `IFeatureManagementStore`) for you. You can override base methods as you need. Every provider must have a unique name, which is `Custom` in this example (keep it short since it is saved to database for each feature value record). + +Once you create your provider class, you should register it using the `FeatureManagementOptions` [options class](../Options.md): + +````csharp +Configure(options => +{ + options.Providers.Add(); +}); +```` + +The order of the providers are important. Providers are executed in the reverse order. That means the `CustomFeatureProvider` is executed first for this example. You can insert your provider in any order in the `Providers` list. + +## See Also + +* [Features](../Features.md) -TODO \ No newline at end of file diff --git a/docs/en/Modules/Identity.md b/docs/en/Modules/Identity.md index 8cd7bf3966..c06f649723 100644 --- a/docs/en/Modules/Identity.md +++ b/docs/en/Modules/Identity.md @@ -1,38 +1,63 @@ # Identity Management Module -Identity module is used to manage organization units, roles, users and their permissions, based on the Microsoft Identity library. +Identity module is used to manage roles, users and their permissions, based on the [Microsoft Identity library](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity). -> **See [the source code](https://github.com/abpframework/abp/tree/dev/modules/identity). Documentation will come soon...** +## How to Install +This module comes as pre-installed (as NuGet/NPM packages) when you [create a new solution](https://abp.io/get-started) with the ABP Framework. You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module. -## Identity Security Log +### The Source Code -The security log can record some important operations or changes about your account. You can save the security log if needed. +The source code of this module can be accessed [here](https://github.com/abpframework/abp/tree/dev/modules/identity). The source code is licensed with [MIT](https://choosealicense.com/licenses/mit/), so you can freely use and customize it. -You can inject and use `IdentitySecurityLogManager` or `ISecurityLogManager` to write security logs. It will create a log object by default and fill in some common values, such as `CreationTime`, `ClientIpAddress`, `BrowserInfo`, `current user/tenant`, etc. Of course, you can override them. +## User Interface -```cs -await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() -{ - Identity = "IdentityServer"; - Action = "ChangePassword"; -}); -``` +This module provides [Blazor](../UI/Blazor/Overall.md), [Angular](../UI/Angular/Quick-Start.md) and [MVC / Razor Pages](../UI/AspNetCore/Overall.md) UI options. -Configure `AbpSecurityLogOptions` to provide the application name for the log or disable this feature. **Enabled** by default. +### Menu Items -```cs -Configure(options => -{ - options.ApplicationName = "AbpSecurityTest"; -}); -``` +This module adds an *Identity management* menu item under the *Administration* menu: + +![identity-module-menu](../images/identity-module-menu.png) + +The menu items and the related pages are authorized. That means the current user must have the related permissions to make them visible. The `admin` role (and the users with this role - like the `admin` user) already has these permissions. If you want to enable permissions for other roles/users, open the *Permissions* dialog on the *Roles* or *Users* page and check the permissions as shown below: + +![identity-module-permissions](../images/identity-module-permissions.png) + +See the [Authorization document](../Authorization.md) to understand the permission system. + +### Pages + +This section introduces the main pages provided by this module. + +#### Users + +This page is used to see the list of users. You can create/edit and delete users, assign users to roles. + +![identity-module-users](../images/identity-module-users.png) + +A user can have zero or more roles. Users inherit permissions from their roles. In addition, you can assign permissions directly to the users (by clicking the *Actions* button, then selecting the *Permissions*). + +#### Roles + +Roles are used to group permissions assign them to users. + +![identity-module-roles](../images/identity-module-roles.png) + +Beside the role name, there are two properties of a role: + +* `Default`: If a role is marked as "default", then that role is assigned to new users by default when they register to the application themselves (using the [Account Module](Account.md)). +* `Public`: A public role of a user can be seen by other users in the application. This feature has no usage in the Identity module, but provided as a feature that you may want to use in your own application. + +## Other Features + +This section covers some other features provided by this module which don't have the UI pages. -## Organization Unit Management +### Organization Units -Organization units (OU) is a part of **Identity Module** and can be used to **hierarchically group users and entities**. +Organization Units (OU) can be used to **hierarchically group users and entities**. -### OrganizationUnit Entity +#### OrganizationUnit Entity An OU is represented by the **OrganizationUnit** entity. The fundamental properties of this entity are: @@ -41,8 +66,6 @@ An OU is represented by the **OrganizationUnit** entity. The fundamental propert - **Code**: A hierarchical string code that is unique for a tenant. - **DisplayName**: Shown name of the OU. -The OrganizationUnit entity's primary key (Id) is a **Guid** type and it derives from the [**FullAuditedAggregateRoot**](../Entities.md) class. - #### Organization Tree Since an OU can have a parent, all OUs of a tenant are in a **tree** structure. There are some rules for this tree; @@ -52,26 +75,248 @@ Since an OU can have a parent, all OUs of a tenant are in a **tree** structure. #### OU Code -OU code is automatically generated and maintained by the OrganizationUnit Manager. It's a string that looks something like this: +OU code is automatically generated and maintained by the `OrganizationUnitManager` service. It's a string that looks something like this: "**00001.00042.00005**" -This code can be used to easily query the database for all the children of an OU (recursively). There are some rules for this code: +This code can be used to easily query the database for all the children of an OU (recursively). There are some rules for this code (automatically applied when you use `OrganizationUnitManager`): -- It must be **unique** for a [tenant](../Multi-Tenancy.md). +- It is **unique** for a [tenant](../Multi-Tenancy.md). - All the children of the same OU have codes that **start with the parent OU's code**. - It's **fixed length** and based on the level of the OU in the tree, as shown in the sample. -- While the OU code is unique, it can be **changeable** if you move an OU. -- You must reference an OU by Id, not Code. +- While the OU code is unique, it can be **changed** if you move the related OU. + +Notice that you must reference an OU by Id, not Code, because the Code can be changed later. -### OrganizationUnit Manager +#### OrganizationUnit Manager -The **OrganizationUnitManager** class can be [injected](../Dependency-Injection.md) and used to manage OUs. Common use cases are: +The `OrganizationUnitManager` class can be [injected](../Dependency-Injection.md) and used to manage OUs. Common use cases are: - Create, Update or Delete an OU - Move an OU in the OU tree. - Getting information about the OU tree and its items. -#### Multi-Tenancy +### Identity Security Log + +The security log system records some important operations or changes about your account (like *login* and *change password*). You can also save the security log if needed. + +You can inject and use `IdentitySecurityLogManager` or `ISecurityLogManager` to write security logs. It will create a log object by default and fill in some common values, such as `CreationTime`, `ClientIpAddress`, `BrowserInfo`, `current user/tenant`, etc. Of course, you can override them. + +```cs +await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() +{ + Identity = "IdentityServer"; + Action = "ChangePassword"; +}); +``` + +Configure `AbpSecurityLogOptions` to provide the application name (in case of you have multiple applications and want to distinguish the applications in the logs) for the log or disable this feature. + +```cs +Configure(options => +{ + options.ApplicationName = "AbpSecurityTest"; +}); +``` + +## Options + +`IdentityOptions` is the standard [options class](../Options.md) provided by the Microsoft [Identity library](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity). So, you can set these options in the `ConfigureServices` method of your [module](../Module-Development-Basics.md) class. + +**Example: Set minimum required length of passwords** + +````csharp +Configure(options => +{ + options.Password.RequiredLength = 5; +}); +```` + +ABP takes these options one step further and allows you to change them on runtime by using the [setting system](../Settings.md). You can [inject](../Dependency-Injection.md) `ISettingManager` and use one of the `Set...` methods to change the option values for a user, a tenant or globally for all users. + +**Example: Change minimum required length of passwords for the current tenant** + +````csharp +public class MyService : ITransientDependency +{ + private readonly ISettingManager _settingManager; + + public MyService(ISettingManager settingManager) + { + _settingManager = settingManager; + } + + public async Task ChangeMinPasswordLength(int minLength) + { + await _settingManager.SetForCurrentTenantAsync( + IdentitySettingNames.Password.RequiredLength, + minLength.ToString() + ); + } +} +```` + +`IdentitySettingNames` class (in the `Volo.Abp.Identity.Settings` namespace) defines constants for the setting names. + +## Distributed Events + +This module defines the following ETOs (Event Transfer Objects) to allow you to subscribe to changes on the entities of the module; + +* `UserEto` is published on changes done on an `IdentityUser` entity. +* `IdentityRoleEto` is published on changes done on an `IdentityRole` entity. +* `IdentityClaimTypeEto` is published on changes done on an `IdentityClaimType` entity. +* `OrganizationUnitEto` is published on changes done on an `OrganizationUnit` entity. + +**Example: Get notified when a new user has been created** + +````csharp +public class MyHandler : + IDistributedEventHandler>, + ITransientDependency +{ + public async Task HandleEventAsync(EntityCreatedEto eventData) + { + UserEto user = eventData.Entity; + // TODO: ... + } +} +```` + +`UserEto` and `IdentityRoleEto` are configured to automatically publish the events. You should configure yourself for the others. See the [Distributed Event Bus document](../Distributed-Event-Bus.md) to learn details of the pre-defined events. + +> Subscribing to the distributed events is especially useful for distributed scenarios (like microservice architecture). If you are building a monolithic application, or listening events in the same process that runs the Identity Module, then subscribing to the [local events](../Local-Event-Bus.md) can be more efficient and easier. + +## Internals + +This section covers some internal details of the module that you don't need much, but may need to use in some cases. + +### Domain layer + +#### Aggregates + +##### User + +A user is generally a person logins to and uses the application. + +* `IdentityUser` (aggregate root): Represents a user in the system. + * `IdentityUserRole` (collection): Roles to the user. + * `IdentityUserClaim` (collection): Custom claims of the user. + * `IdentityUserLogin` (collection): External logins of the user. + * `IdentityUserToken` (collection): Tokens of the user (used by the Microsoft Identity services). + +##### Role + +A role is typically a group of permissions to assign to the users. + +* `IdentityRole` (aggregate root): Represents a role in the system. + * `IdentityRoleClaim` (collection): Custom claims of the role. + +##### Claim Type + +A claim type is a definition of a custom claim that can be assigned to other entities (like roles and users) in the system. + +* `IdentityClaimType` (aggregate root): Represents a claim type definition. It contains some properties (e.g. Required, Regex, Description, ValueType) to define the claim type and the validation rules. + +##### Identity Security Log + +A `IdentitySecurityLog` object represents an authentication related operation (like *login*) in the system. + +* `IdentitySecurityLog` (aggregate root): Represents a security log in the system. + +##### OrganizationUnit + +An Organization unit is a entity in a hierarchical structure. + +* ```OrganizationUnit``` (aggregate root): Represents an organization unit in the system. + * ```Roles``` (collection): Roles of the organization unit. + +#### Repositories + +Following custom repositories are defined for this module: + +* `IIdentityUserRepository` +* `IIdentityRoleRepository` +* `IIdentityClaimTypeRepository` +* ```IIdentitySecurityLogRepository``` +* ```IOrganizationUnitRepository``` + +#### Domain services + +##### User manager + +`IdentityUserManager` is used to manage users, their roles, claims, passwords, emails, etc. It is derived from Microsoft Identity's `UserManager` class where `T` is `IdentityUser`. + +##### Role manager + +`IdentityRoleManager` is used to manage roles and their claims. It is derived from Microsoft Identity's `RoleManager` class where `T` is `IdentityRole`. + +##### Claim type manager + +`IdenityClaimTypeManager` is used to perform some operations for the `IdentityClaimType` aggregate root. + +##### Organization unit manager + +```OrganizationUnitManager``` is used to perform some operations for the ```OrganizationUnit``` aggregate root. + +##### Security log manager + +```IdentitySecurityLogManager``` is used to save security logs. + +### Application Layer + +#### Application Services + +* `IdentityUserAppService` (implements `IIdentityUserAppService`): Implements the use cases of the user management UI. +* `IdentityRoleAppService` (implement `IIdentityRoleAppService`): Implements the use cases of the role management UI. +* `IdentityClaimTypeAppService` (implements `IIdentityClaimTypeAppService`): Implements the use cases of the claim type management UI. +* `IdentitySettingsAppService` (implements `IIdentitySettingsAppService`): Used to get and update settings for the Identity module. +* `IdentityUserLookupAppService` (implements `IIdentityUserLookupAppService`): Used to get information for a user by `id` or `userName`. It is aimed to be used internally by the ABP framework. +* `ProfileAppService` (implements `IProfileAppService`): Used to change a user's profile and the password. +* ```IdentitySecurityLogAppService``` (implements ```IIdentitySecurityLogAppService```): Implements the use cases of the security logs UI. +* ```OrganizationUnitAppService``` (implements ```OrganizationUnitAppService```): Implements the use cases of the organization unit management UI. + +### Database Providers + +This module provides [Entity Framework Core](../Entity-Framework-Core.md) and [MongoDB](../MongoDB.md) options for the database. + +#### Entity Framework Core + +[Volo.Abp.Identity.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.Identity.EntityFrameworkCore) NuGet package implements the EF Core integration. + +##### Database Tables + +* **AbpRoles** + * AbpRoleClaims +* **AbpUsers** + * AbpUserClaims + * AbpUserLogins + * AbpUserRoles + * AbpUserTokens +* **AbpClaimTypes** +* **AbpOrganizationUnits** + * AbpOrganizationUnitRoles + * AbpUserOrganizationUnits +* **AbpSecurityLogs** + +#### MongoDB + +[Volo.Abp.Identity.MongoDB](https://www.nuget.org/packages/Volo.Abp.Identity.MongoDB) NuGet package implements the MongoDB integration. + +##### Database Collections + +* **AbpRoles** +* **AbpUsers** +* **AbpClaimTypes** +* **AbpOrganizationUnits** +* **AbpSecurityLogs** + +#### Common Database Properties + +You can set the following properties of the `AbpIdentityDbProperties` class to change the database options: + +* `DbTablePrefix` (`Abp` by default) is the prefix for table/collection names. +* `DbSchema` (`null` by default) is the database schema. +* `ConnectionStringName` (`AbpIdentity` by default) is the [connection string](../Connection-Strings.md) name for this module. + +These are static properties. If you want to set, do it in the beginning of your application (typically, in `Program.cs`). -The `OrganizationUnitManager` is designed to work for a **single tenant** at a time. It works for the **current tenant** by default. \ No newline at end of file diff --git a/docs/en/Modules/IdentityServer.md b/docs/en/Modules/IdentityServer.md index 0d2ab35c21..90e9fe19eb 100644 --- a/docs/en/Modules/IdentityServer.md +++ b/docs/en/Modules/IdentityServer.md @@ -1,3 +1,173 @@ # IdentityServer Module -TODO \ No newline at end of file +IdentityServer module provides a full integration with the [IdentityServer](https://github.com/IdentityServer/IdentityServer4) (IDS) framework, which provides advanced authentication features like single sign-on and API access control. This module persists clients, resources and other IDS-related objects to database. + +## How to Install + +This module comes as pre-installed (as NuGet/NPM packages) when you [create a new solution](https://abp.io/get-started) with the ABP Framework. You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module. + +### The Source Code + +The source code of this module can be accessed [here](https://github.com/abpframework/abp/tree/dev/modules/identityserver). The source code is licensed with [MIT](https://choosealicense.com/licenses/mit/), so you can freely use and customize it. + +## User Interface + +This module implements the domain logic and database integrations, but not provides any UI. Management UI is useful if you need to add clients and resources on the fly. In this case, you may build the management UI yourself or consider to purchase the [ABP Commercial](https://commercial.abp.io/) which provides the management UI for this module. + +## Relations to Other Modules + +This module is based on the [Identity Module](Identity.md) and have an [integration package](https://www.nuget.org/packages/Volo.Abp.Account.Web.IdentityServer) with the [Account Module](Account.md). + +## Options + +### AbpIdentityServerBuilderOptions + +`AbpIdentityServerBuilderOptions` can be configured in `PreConfigureServices` method of your Identity Server [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics). Example: + +````csharp +public override void PreConfigureServices(ServiceConfigurationContext context) +{ + PreConfigure(builder => + { + //Set options here... + }); +} +```` + +`AbpIdentityServerBuilderOptions` properties: + +* `UpdateJwtSecurityTokenHandlerDefaultInboundClaimTypeMap` (default: true): Updates `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap` to be compatible with Identity Server claims. +* `UpdateAbpClaimTypes` (default: true): Updates `AbpClaimTypes` to be compatible with identity server claims. +* `IntegrateToAspNetIdentity` (default: true): Integrate to ASP.NET Identity. +* `AddDeveloperSigningCredential` (default: true): Set false to suppress AddDeveloperSigningCredential() call on the IIdentityServerBuilder. + +`IIdentityServerBuilder` can be configured in `PreConfigureServices` method of your Identity Server [module](https://docs.abp.io/en/abp/latest/Module-Development-Basics). Example: + +````csharp +public override void PreConfigureServices(ServiceConfigurationContext context) +{ + PreConfigure(builder => + { + builder.AddSigningCredential(...); + }); +} +```` + +## Internals + +### Domain Layer + +#### Aggregates + +##### ApiResource + +API Resources are needed for allowing clients to request access tokens. + +* `ApiResource` (aggregate root): Represents an API resource in the system. + * `ApiSecret` (collection): secrets of the API resource. + * `ApiScope` (collection): scopes of the API resource. + * `ApiResourceClaim` (collection): claims of the API resource. + +##### Client + +Clients represent applications that can request tokens from your Identity Server. + +* `Client` (aggregate root): Represents an Identity Server client application. + * `ClientScope` (collection): Scopes of the client. + * `ClientSecret` (collection): Secrets of the client. + * `ClientGrantType` (collection): Grant types of the client. + * `ClientCorsOrigin` (collection): CORS origins of the client. + * `ClientRedirectUri` (collection): redirect URIs of the client. + * `ClientPostLogoutRedirectUri` (collection): Logout redirect URIs of the client. + * `ClientIdPRestriction` (collection): Provider restrictions of the client. + * `ClientClaim` (collection): Claims of the client. + * `ClientProperty` (collection): Custom properties of the client. + +##### PersistedGrant + +Persisted Grants stores AuthorizationCodes, RefreshTokens and UserConsent. + +* `PersistedGrant` (aggregate root): Represents PersistedGrant for identity server. + +##### IdentityResource + +Identity resources are data like user ID, name, or email address of a user. + +* `IdentityResource` (aggregate root): Represents and Identity Server identity resource. + * `IdentityClaim` (collection): Claims of identity resource. + +#### Repositories + +Following custom repositories are defined for this module: + +* `IApiResourceRepository` +* `IClientRepository` +* `IPersistentGrantRepository` +* `IIdentityResourceRepository` + +#### Domain Services + +This module doesn't contain any domain service but overrides the services below; + +* `AbpProfileService` (Used when `AbpIdentityServerBuilderOptions.IntegrateToAspNetIdentity` is true) +* `AbpClaimsService` +* `AbpCorsPolicyService` + +### Settings + +This module doesn't define any settings. + +### Application Layer + +#### Application Services + +* `ApiResourceAppService` (implements `IApiResourceAppService`): Implements the use cases of the API resource management UI. +* `IdentityServerClaimTypeAppService` (implement `IIdentityServerClaimTypeAppService`): Used to get list of claims. +* `ApiResourceAppService` (implements `IApiResourceAppService`): Implements the use cases of the API resource management UI. +* `IdentityResourceAppService` (implements `IIdentityResourceAppService`): Implements the use cases of the Identity resource management UI. + +### Database Providers + +#### Common + +##### Table/Collection Prefix & Schema + +All tables/collections use the `IdentityServer` prefix by default. Set static properties on the `AbpIdentityServerDbProperties` class if you need to change the table prefix or set a schema name (if supported by your database provider). + +##### Connection String + +This module uses `AbpIdentityServer` for the connection string name. If you don't define a connection string with this name, it fallbacks to the `Default` connection string. + +See the [connection strings](https://docs.abp.io/en/abp/latest/Connection-Strings) documentation for details. + +#### Entity Framework Core + +##### Tables + +* **IdentityServerApiResources** + * IdentityServerApiSecrets + * IdentityServerApiScopes + * IdentityServerApiScopeClaims + * IdentityServerApiClaims +* **IdentityServerClients** + * IdentityServerClientScopes + * IdentityServerClientSecrets + * IdentityServerClientGrantTypes + * IdentityServerClientCorsOrigins + * IdentityServerClientRedirectUris + * IdentityServerClientPostLogoutRedirectUris + * IdentityServerClientIdPRestrictions + * IdentityServerClientClaims + * IdentityServerClientProperties +* **IdentityServerPersistedGrants** +* **IdentityServerIdentityResources** + * IdentityServerIdentityClaims + +#### MongoDB + +##### Collections + +* **IdentityServerApiResources** +* **IdentityServerClients** +* **IdentityServerPersistedGrants** +* **IdentityServerIdentityResources** \ No newline at end of file diff --git a/docs/en/Modules/Index.md b/docs/en/Modules/Index.md index 831a55fc8e..35d46dece0 100644 --- a/docs/en/Modules/Index.md +++ b/docs/en/Modules/Index.md @@ -24,7 +24,7 @@ There are some **free and open source** application modules developed and mainta * [**Tenant Management**](Tenant-Management.md): Manages tenants for a [multi-tenant](../Multi-Tenancy.md) application. * [**Virtual File Explorer**](Virtual-File-Explorer.md): Provided a simple UI to view files in [virtual file system](../Virtual-File-System.md). -See [the GitHub repository](https://github.com/abpframework/abp/tree/master/modules) for source code of all modules. +See [the GitHub repository](https://github.com/abpframework/abp/tree/dev/modules) for source code of all modules. ## Commercial Application Modules diff --git a/docs/en/Modules/Permission-Management.md b/docs/en/Modules/Permission-Management.md index ad48b60171..ba4b34d8ed 100644 --- a/docs/en/Modules/Permission-Management.md +++ b/docs/en/Modules/Permission-Management.md @@ -1,5 +1,109 @@ # Permission Management Module -This module implements the `IPermissionStore` to store and manage feature values in a database. See the [Authorization document](../Authorization.md) to understand the authorization and permission systems first. +This module implements the `IPermissionStore` to store and manage permissions values in a database. -TODO \ No newline at end of file +> This document covers only the permission management module which persists permission values to a database. See the [Authorization document](../Authorization.md) to understand the authorization and permission systems. + +## How to Install + +This module comes as pre-installed (as NuGet/NPM packages) when you [create a new solution](https://abp.io/get-started) with the ABP Framework. You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module. + +### The Source Code + +The source code of this module can be accessed [here](https://github.com/abpframework/abp/tree/dev/modules/permission-management). The source code is licensed with [MIT](https://choosealicense.com/licenses/mit/), so you can freely use and customize it. + +## User Interface + +### Permission Management Dialog + +Permission management module provides a reusable dialog to manage permissions related to an object. For example, the [Identity Module](Identity.md) uses it to manage permissions of users and roles. The following image shows Identity Module's Role Management page: + +![permissions-module-open-dialog](../images/permissions-module-open-dialog.png) + +When you click *Actions* -> *Permissions* for a role, the permission management dialog is opened. An example screenshot from this dialog: + +![permissions-module-dialog](../images/permissions-module-dialog.png) + +In this dialog, you can grant permissions for the selected role. The tabs in the left side represents main permission groups and the right side contains the permissions defined in the selected group. + +## IPermissionManager + +`IPermissionManager` is the main service provided by this module. It is used to read and change the permission values. `IPermissionManager` is typically used by the *Feature Management Dialog*. However, you can inject it if you need to set a permission value. + +> If you just want to read/check permission values for the current user, use the `IAuthorizationService` or the `[Authorize]` attribute as explained in the [Authorization document](../Authorization.md). + +**Example: Grant permissions to roles and users using the `IPermissionManager` service** + +````csharp +public class MyService : ITransientDependency +{ + private readonly IPermissionManager _permissionManager; + + public MyService(IPermissionManager permissionManager) + { + _permissionManager = permissionManager; + } + + public async Task GrantRolePermissionDemoAsync( + string roleName, string permission) + { + await _permissionManager + .SetForRoleAsync(roleName, permission, true); + } + + public async Task GrantUserPermissionDemoAsync( + Guid userId, string roleName, string permission) + { + await _permissionManager + .SetForUserAsync(userId, permission, true); + } +} +```` + +## Permission Management Providers + +Permission Management Module is extensible, just like the [permission system](../Authorization.md). You can extend it by defining permission management providers. + +[Identity Module](Identity.md) defines the following permission management providers: + +* `UserPermissionManagementProvider`: Manages user-based permissions. +* `RolePermissionManagementProvider`: Manages role-based permissions. + +`IPermissionManager` uses these providers when you get/set permissions. You can define your own provider by implementing the `IPermissionManagementProvider` or inheriting from the `PermissionManagementProvider` base class. + +**Example:** + +````csharp +public class CustomPermissionManagementProvider : PermissionManagementProvider +{ + public override string Name => "Custom"; + + public CustomPermissionManagementProvider( + IPermissionGrantRepository permissionGrantRepository, + IGuidGenerator guidGenerator, + ICurrentTenant currentTenant) + : base( + permissionGrantRepository, + guidGenerator, + currentTenant) + { + } +} +```` + +`PermissionManagementProvider` base class makes the default implementation (using the `IPermissionGrantRepository`) for you. You can override base methods as you need. Every provider must have a unique name, which is `Custom` in this example (keep it short since it is saved to database for each feature value record). + +Once you create your provider class, you should register it using the `FeatureManagementOptions` [options class](../Options.md): + +````csharp +Configure(options => +{ + options.ManagementProviders.Add(); +}); +```` + +The order of the providers are important. Providers are executed in the reverse order. That means the `CustomPermissionManagementProvider` is executed first for this example. You can insert your provider in any order in the `Providers` list. + +## See Also + +* [Authorization](../Authorization.md) \ No newline at end of file diff --git a/docs/en/Modules/Setting-Management.md b/docs/en/Modules/Setting-Management.md index 255efc9a5f..bf4ab30031 100644 --- a/docs/en/Modules/Setting-Management.md +++ b/docs/en/Modules/Setting-Management.md @@ -85,6 +85,37 @@ Setting Management module is extensible, just like the [setting system](../Setti `ISettingManager` uses the setting management providers on get/set methods. Typically, every setting management provider defines extension methods on the `ISettingManagement` service (like `SetForUserAsync` defined by the user setting management provider). +If you want to create your own provider, implement the `ISettingManagementProvider` interface or inherit from the `SettingManagementProvider` base class: + +````csharp +public class CustomSettingProvider : SettingManagementProvider +{ + public override string Name => "Custom"; + + public CustomSettingProvider(ISettingManagementStore store) + : base(store) + { + } +} +```` + +`SettingManagementProvider` base class makes the default implementation (using the `ISettingManagementStore`) for you. You can override base methods as you need. Every provider must have a unique name, which is `Custom` in this example (keep it short since it is saved to database for each feature value record). + +Once you create your provider class, you should register it using the `SettingManagementOptions` [options class](../Options.md): + +````csharp +Configure(options => +{ + options.Providers.Add(); +}); +```` + +The order of the providers are important. Providers are executed in the reverse order. That means the `CustomSettingProvider` is executed first for this example. You can insert your provider in any order in the `Providers` list. + +## See Also + +* [Settings](../Settings.md) + ## Setting Management UI Setting Mangement module provided the email setting UI by default, and it is extensible; You can add your tabs to this page for your application settings. @@ -271,4 +302,5 @@ export class AppComponent { Navigate to `/setting-management` route to see the changes: -![Custom Settings Tab](../images/custom-settings.png) \ No newline at end of file +![Custom Settings Tab](../images/custom-settings.png) + diff --git a/docs/en/Modules/Tenant-Management.md b/docs/en/Modules/Tenant-Management.md index 73e16aee8e..e12b6fc7a6 100644 --- a/docs/en/Modules/Tenant-Management.md +++ b/docs/en/Modules/Tenant-Management.md @@ -1,3 +1,107 @@ # Tenant Management Module -TODO \ No newline at end of file +[Multi-Tenancy](../Multi-Tenancy.md) is one of the core features of ABP Framework. It provides the fundamental infrastructure to build your own SaaS (Software-as-a-Service) solution. ABP's multi-tenancy system abstracts where your tenants are stored, by providing the `ITenantStore` interface. All you need to do is to implement that interface. + +**The Tenant Management module is an implementation of the the `ITenantStore` interface. It stores tenants in a database. It also provides UI to manage your tenants and their [features](../Features.md).** + +> Please **refer to the [Multi-Tenancy](../Multi-Tenancy.md) documentation** to understand the multi-tenancy system of the ABP Framework. This document focuses on the Tenant Management module. + +### About the ABP Commercial SaaS Module + +The [SaaS Module](https://commercial.abp.io/modules/Volo.Saas) is an alternative implementation of this module with more features and possibilities. It is distributed as a part of the [ABP Commercial](https://commercial.abp.io/) subscription. + +## How to Install + +This module comes as pre-installed (as NuGet/NPM packages) when you [create a new solution](https://abp.io/get-started) with the ABP Framework. You can continue to use it as package and get updates easily, or you can include its source code into your solution (see `get-source` [CLI](../CLI.md) command) to develop your custom module. + +### The Source Code + +The source code of this module can be accessed [here](https://github.com/abpframework/abp/tree/dev/modules/tenant-management). The source code is licensed with [MIT](https://choosealicense.com/licenses/mit/), so you can freely use and customize it. + +## User Interface + +This module adds "*Administration -> Tenant Management -> Tenants*" menu item to the main menu of the application, which opens the page shown below: + +![module-tenant-management-page](../images/module-tenant-management-page.png) + +In this page, you see the all the tenants. You can create a new tenant as shown below: + +![module-tenant-management-new-tenant](../images/module-tenant-management-new-tenant.png) + +In this modal; + +* **Name**: The unique name of the tenant. If you use subdomains for your tenants (like https://some-tenant.your-domain.com), this will be the subdomain name. +* **Admin Email Address**: Email address of the admin user for this tenant. +* **Admin Password**: The password of the admin user for this tenant. + +When you click to *Actions* button near to a tenant, you will see the actions you can take: + +![module-tenant-management-actions](../images/module-tenant-management-actions.png) + +### Managing the Tenant Features + +The Features action opens a modal to enable/disable/set [features](../Features.md) for the related tenant. Here, an example modal: + +![features-modal](../images/features-modal.png) + +### Managing the Host Features + +*Manage Host features* button is used to set features for the host side, if you use the features of your application also in the host side. + +## Internals + +This section can be used as a reference if you want to [customize](../Customizing-Application-Modules-Guide.md) this module without changing [its source code](https://github.com/abpframework/abp/tree/dev/modules/tenant-management). + +### Domain Layer + +#### Aggregates + +* `Tenant` + +#### Repositories + +* `ITenantRepository` + +#### Domain Services + +* `TenantManager` + +### Application Layer + +#### Application Services + +* `TenantAppService` + +#### Permissions + +- `AbpTenantManagement.Tenants`: Tenant management. +- `AbpTenantManagement.Tenants.Create`: Creating a new tenant. +- `AbpTenantManagement.Tenants.Update`: Editing an existing tenant. +- `AbpTenantManagement.Tenants.Delete`: Deleting an existing tenant. +- `AbpTenantManagement.Tenants.ManageFeatures`: Manage features of the tenants. + +### Entity Framework Core Integration + +* `TenantManagementDbContext` (implements `ITenantManagementDbContext`) + +**Database Tables:** + +* `AbpTenants` +* `AbpTenantConnectionStrings` + +### MongoDB Integration + +* `TenantManagementMongoDbContext` (implements `ITenantManagementMongoDbContext`) + +**Database Collections:** + +* `AbpTenants` (also includes the connection string) + +## Notices + +ABP Framework allows to use *database per tenant* approach that allows a tenant can have a dedicated database. This module has the fundamental infrastructure to make that implementation possible (see its source code), however it doesn't implement the application layer and UI functionalities to provide it as an out of the box implementation. You can implement these features yourself, or consider to use the [ABP Commercial Saas Module](https://docs.abp.io/en/commercial/latest/modules/saas) that fully implements it and provides much more business features. + +## See Also + +* [Multi-Tenancy](../Multi-Tenancy.md) +* [ABP Commercial SaaS Module](https://docs.abp.io/en/commercial/latest/modules/saas) \ No newline at end of file diff --git a/docs/en/Multi-Tenancy.md b/docs/en/Multi-Tenancy.md index 5db646b7a7..f8098a4875 100644 --- a/docs/en/Multi-Tenancy.md +++ b/docs/en/Multi-Tenancy.md @@ -296,6 +296,32 @@ services.Configure(options => }); ```` +If you change the `TenantKey`, make sure to pass it to `CoreModule` in the Angular client as follows: + +```js +@NgModule({ + imports: [ + CoreModule.forRoot({ + // ... + tenantKey: 'MyTenantKey' + }), + ], + // ... +}) +export class AppModule {} +``` + +If you need to access it, you can inject it as follows: + +```js +import { Inject } from '@angular/core'; +import { TENANT_KEY } from '@abp/ng.core'; + +class SomeComponent { + constructor(@Inject(TENANT_KEY) private tenantKey: string) {} +} +``` + > However, we don't suggest to change this value since some clients may assume the the `__tenant` as the parameter name and they might need to manually configure then. ##### Domain/Subdomain Tenant Resolver diff --git a/docs/en/Repositories.md b/docs/en/Repositories.md index 7ed10b9bb8..dc82876efb 100644 --- a/docs/en/Repositories.md +++ b/docs/en/Repositories.md @@ -222,8 +222,8 @@ public class PersonRepository : EfCoreRepository, IPe public async Task FindByNameAsync(string name) { - var dbSet = await GetDbSetAsync(); - return await dbSet.Set() + var dbContext = await GetDbContextAsync(); + return await dbContext.Set() .Where(p => p.Name == name) .FirstOrDefaultAsync(); } diff --git a/docs/en/Road-Map.md b/docs/en/Road-Map.md index f57e35c863..d98c626dd9 100644 --- a/docs/en/Road-Map.md +++ b/docs/en/Road-Map.md @@ -4,27 +4,16 @@ This document provides a road map, release schedule and planned features for the ## Next Versions -### v4.4 - -This version will focus on **documentation** and **improvements** of current features. In addition, the following features are planned: - -* Publishing distributed events as transactional ([#6126](https://github.com/abpframework/abp/issues/6126)) -* Revisit the microservice demo solution ([#8385](https://github.com/abpframework/abp/issues/8385)) -* A new UI Theme alternative to the Basic Theme ([#6132](https://github.com/abpframework/abp/issues/6132)) -* Improvements and new features to the [CMS Kit](Modules/Cms-Kit.md) module ([#8380](https://github.com/abpframework/abp/issues/8380) [#8381](https://github.com/abpframework/abp/issues/8381)) -* Pre-configured test project for the [Blazor UI](UI/Blazor/Overall.md) ([#5516](https://github.com/abpframework/abp/issues/5516)) -* Razor engine support for text templating ([#8373](https://github.com/abpframework/abp/issues/8373)) - -**Planned release date**: End of Quarter 2, 2021. See the [4.4 milestone](https://github.com/abpframework/abp/milestone/52) to track the progress. - ### v5.0 -We planned to focus on the ABP Framework v5.0 after the 4.4 release. This version will focus on the following works: +This version will focus on the following works: * Upgrading to .NET 6.0 ([#9004](https://github.com/abpframework/abp/issues/9004)) * Upgrading to Bootstrap 5.x ([#8922](https://github.com/abpframework/abp/issues/8922)) * Alternative to IdentityServer4 ([#7221](https://github.com/abpframework/abp/issues/7221)) +* Revisit the microservice demo solution ([#8385](https://github.com/abpframework/abp/issues/8385)) * Dapr integration ([#2183](https://github.com/abpframework/abp/issues/2183)) +* Publishing distributed events as transactional ([#6126](https://github.com/abpframework/abp/issues/6126)) * Resource based authorization system ([#236](https://github.com/abpframework/abp/issues/236)) * API Versioning system: finalize & document ([#497](https://github.com/abpframework/abp/issues/497)) * Performance optimizations; Enabling .NET Trimming, using source generators and reducing reflection, etc. diff --git a/docs/en/Samples/Index.md b/docs/en/Samples/Index.md index 18312e4b55..7388516ed0 100644 --- a/docs/en/Samples/Index.md +++ b/docs/en/Samples/Index.md @@ -76,6 +76,12 @@ While there is no Razor Pages & MongoDB combination, you can check both document * **DevExtreme MVC / Razor Pages Component Integration**: How to install and use DevExtreme components in the ABP Framework MVC / Razor Pages UI. * [Source code](https://github.com/abpframework/abp-samples/tree/master/DevExtreme-Mvc) * [Article](https://community.abp.io/articles/using-devextreme-components-with-the-abp-framework-zb8z7yqv) +* **Syncfusion Blazor Integration**: Shows how to install and integrate Syncfusion UI with ABP Framework Blazor UI. + * [Source code](https://github.com/abpframework/abp-samples/tree/master/SyncfusionSample) + * [Article](https://community.abp.io/articles/using-syncfusion-components-with-the-abp-framework-5ccvi8kc) * **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) \ No newline at end of file + * [Documentation](../Getting-Started-AspNetCore-Application.md) +* **Using Elsa Workflow with ABP Framework**: Shows how we can use Elsa Core workflow library within our ABP-based application. + * [Source code](https://github.com/abpframework/abp-samples/tree/master/ElsaDemo) + * [Article](https://community.abp.io/articles/using-elsa-workflow-with-the-abp-framework-773siqi9) diff --git a/docs/en/Startup-Templates/Application.md b/docs/en/Startup-Templates/Application.md index 81f0170e59..1fdf980ddc 100644 --- a/docs/en/Startup-Templates/Application.md +++ b/docs/en/Startup-Templates/Application.md @@ -128,20 +128,6 @@ This is the integration project for the EF Core. It defines the `DbContext` and > This project is available only if you are using EF Core as the database provider. If you select another database provider, its name will be different. -#### .EntityFrameworkCore.DbMigrations Project - -Contains EF Core database migrations for the solution. It has a separated `DbContext` to dedicated to manage migrations. - -ABP is a modular framework and with an ideal design, each module has its own `DbContext` class. This is where the migration `DbContext` comes into play and unifies all `DbContext` configurations into a single model to maintain a single database schema. For more advanced scenarios, you can have multiple databases (each contains a single or a few module tables) and multiple migration `DbContext`s (each maintains a different database schema). - -Notice that the migration `DbContext` is only used for database migrations and *not used on runtime*. - -* Depends on the `.EntityFrameworkCore` project since it re-uses the configuration defined for the `DbContext` of the application. - -> This project is available only if you are using EF Core as the database provider. -> -> See the [Entity Framework Core Migrations Guide](../Entity-Framework-Core-Migrations.md) to understand this project in details. - #### .DbMigrator Project This is a console application which simplifies to execute database migrations on development and production environments. When you run this application, it; @@ -156,7 +142,7 @@ Especially, seeding initial data is important at this point. ABP has a modular d While creating database & applying migrations seems only necessary for relational databases, this projects comes even if you choose a NoSQL database provider (like MongoDB). In that case, it still seeds initial data which is necessary for the application. -* Depends on the `.EntityFrameworkCore.DbMigrations` project (for EF Core) since it needs to access to the migrations. +* Depends on the `.EntityFrameworkCore` project (for EF Core) since it needs to access to the migrations. * Depends on the `.Application.Contracts` project to be able to access permission definitions, because initial data seeder grants all permissions for the admin role by default. #### .HttpApi Project @@ -187,7 +173,7 @@ This project contains the main `appsettings.json` file that contains the connect * Depends on the `.HttpApi` since UI layer needs to use APIs and application service interfaces of the solution. -> If you check the source code of the `.Web.csproj` file, you will see the references to the `.Application` and the `.EntityFrameworkCore.DbMigrations` projects. +> If you check the source code of the `.Web.csproj` file, you will see the references to the `.Application` and the `.EntityFrameworkCore` projects. > > These references are actually not needed while coding your UI layer, because UI layer normally doesn't depend on the EF Core or the Application layer's implementation. This startup templates are ready for the tiered deployment, where API layer is hosted in a separate server than the UI layer. > diff --git a/docs/en/Startup-Templates/Module.md b/docs/en/Startup-Templates/Module.md index 84b7525944..cdcb964bef 100644 --- a/docs/en/Startup-Templates/Module.md +++ b/docs/en/Startup-Templates/Module.md @@ -20,23 +20,9 @@ abp new Acme.IssueManagement -t module - `Acme.IssueManagement` is the solution name, like *YourCompany.YourProduct*. You can use single level, two-levels or three-levels naming. -### Specify the UI Framework - -This template provides multiple UI frameworks: - -* `mvc`: ASP.NET Core MVC UI with Razor Pages (default) -* `blazor`: Blazor UI -* `angular`: Angular UI - -Use `-u` or `--ui` option to specify the UI framework: - -````bash -abp new Acme.IssueManagement -t module -u angular -```` - ### Without User Interface -The template comes with an MVC UI by default. You can use `--no-ui` option to not include the UI layer. +The template comes with MVC, Blazor & Angular user interfaces by default. You can use `--no-ui` option to not include any of these UI layers. ````bash abp new Acme.IssueManagement -t module --no-ui @@ -179,7 +165,7 @@ You should run the application with the given order: ### Angular UI -If you choose `Angular` as the UI framework (using the `-u angular` option), the solution will have a folder called `angular` in it. This is where the client-side code is located. When you open that folder in an IDE, the folder structure will look like below: +The solution will have a folder called `angular` in it. This is where the Angular client-side code is located. When you open that folder in an IDE, the folder structure will look like below: ![Folder structure of ABP Angular module project](../images/angular-module-folder-structure.png) diff --git a/docs/en/Tutorials/Part-1.md b/docs/en/Tutorials/Part-1.md index 7000e39d8c..d419fc8d27 100644 --- a/docs/en/Tutorials/Part-1.md +++ b/docs/en/Tutorials/Part-1.md @@ -142,28 +142,36 @@ public class BookStoreMongoDbContext : AbpMongoDbContext ### Map the Book Entity to a Database Table -Open `BookStoreDbContextModelCreatingExtensions.cs` file in the `Acme.BookStore.EntityFrameworkCore` project and add the mapping code for the `Book` entity. The final class should be the following: +Locate to `OnModelCreating` method in the `BookStoreDbContext` class and add the mapping code for the `Book` entity: ````csharp using Acme.BookStore.Books; -using Microsoft.EntityFrameworkCore; -using Volo.Abp; -using Volo.Abp.EntityFrameworkCore.Modeling; +... namespace Acme.BookStore.EntityFrameworkCore { - public static class BookStoreDbContextModelCreatingExtensions + public class BookStoreDbContext : + AbpDbContext, + IIdentityDbContext, + ITenantManagementDbContext { - public static void ConfigureBookStore(this ModelBuilder builder) + ... + + protected override void OnModelCreating(ModelBuilder builder) { - Check.NotNull(builder, nameof(builder)); + base.OnModelCreating(builder); + + /* Include modules to your migration db context */ + + builder.ConfigurePermissionManagement(); + ... /* Configure your own tables/entities inside here */ builder.Entity(b => { b.ToTable(BookStoreConsts.DbTablePrefix + "Books", - BookStoreConsts.DbSchema); + BookStoreConsts.DbSchema); b.ConfigureByConvention(); //auto configure for the base class props b.Property(x => x.Name).IsRequired().HasMaxLength(128); }); @@ -179,7 +187,7 @@ namespace Acme.BookStore.EntityFrameworkCore The startup solution is configured to use [Entity Framework Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). Since we've changed the database mapping configuration, we should create a new migration and apply changes to the database. -Open a command-line terminal in the directory of the `Acme.BookStore.EntityFrameworkCore.DbMigrations` project and type the following command: +Open a command-line terminal in the directory of the `Acme.BookStore.EntityFrameworkCore` project and type the following command: ```bash dotnet ef migrations add Created_Book_Entity @@ -189,7 +197,7 @@ This will add a new migration class to the project: ![bookstore-efcore-migration](./images/bookstore-efcore-migration.png) -> If you are using Visual Studio, you may want to use `Add-Migration Created_Book_Entity -c BookStoreMigrationsDbContext` and `Update-Database -c BookStoreMigrationsDbContext` commands in the *Package Manager Console (PMC)*. In this case, ensure that {{if UI=="MVC"}}`Acme.BookStore.Web`{{else if UI=="BlazorServer"}}`Acme.BookStore.Blazor`{{else if UI=="Blazor" || UI=="NG"}}`Acme.BookStore.HttpApi.Host`{{end}} is the startup project and `Acme.BookStore.EntityFrameworkCore.DbMigrations` is the *Default Project* in PMC. +> If you are using Visual Studio, you may want to use `Add-Migration Created_Book_Entity -c BookStoreMigrationsDbContext` and `Update-Database -c BookStoreMigrationsDbContext` commands in the *Package Manager Console (PMC)*. In this case, ensure that {{if UI=="MVC"}}`Acme.BookStore.Web`{{else if UI=="BlazorServer"}}`Acme.BookStore.Blazor`{{else if UI=="Blazor" || UI=="NG"}}`Acme.BookStore.HttpApi.Host`{{end}} is the startup project and `Acme.BookStore.EntityFrameworkCore` is the *Default Project* in PMC. {{end}} diff --git a/docs/en/Tutorials/Part-10.md b/docs/en/Tutorials/Part-10.md index 09df003703..11fdd9b781 100644 --- a/docs/en/Tutorials/Part-10.md +++ b/docs/en/Tutorials/Part-10.md @@ -70,7 +70,7 @@ We prefer to **delete the database** {{if DB=="EF"}}(you can run the `Drop-Datab ### Update the EF Core Mapping -Open the `BookStoreDbContextModelCreatingExtensions` class under the `EntityFrameworkCore` folder of the `Acme.BookStore.EntityFrameworkCore` project and change the `builder.Entity` part as shown below: +Locate to `OnModelCreating` method in the `BookStoreDbContext` class that under the `EntityFrameworkCore` folder of the `Acme.BookStore.EntityFrameworkCore` project and change the `builder.Entity` part as shown below: ````csharp builder.Entity(b => @@ -88,7 +88,7 @@ builder.Entity(b => The startup solution is configured to use [Entity Framework Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). Since we've changed the database mapping configuration, we should create a new migration and apply changes to the database. -Open a command-line terminal in the directory of the `Acme.BookStore.EntityFrameworkCore.DbMigrations` project and type the following command: +Open a command-line terminal in the directory of the `Acme.BookStore.EntityFrameworkCore` project and type the following command: ````bash dotnet ef migrations add Added_AuthorId_To_Book @@ -121,7 +121,7 @@ migrationBuilder.AddForeignKey( * Creates an index on the `AuthorId` field. * Declares the foreign key to the `AppAuthors` table. -> If you are using Visual Studio, you may want to use `Add-Migration Added_AuthorId_To_Book -c BookStoreMigrationsDbContext` and `Update-Database -c BookStoreMigrationsDbContext` commands in the *Package Manager Console (PMC)*. In this case, ensure that {{if UI=="MVC"}}`Acme.BookStore.Web`{{else if UI=="BlazorServer"}}`Acme.BookStore.Blazor`{{else if UI=="Blazor" || UI=="NG"}}`Acme.BookStore.HttpApi.Host`{{end}} is the startup project and `Acme.BookStore.EntityFrameworkCore.DbMigrations` is the *Default Project* in PMC. +> If you are using Visual Studio, you may want to use `Add-Migration Added_AuthorId_To_Book -c BookStoreMigrationsDbContext` and `Update-Database -c BookStoreMigrationsDbContext` commands in the *Package Manager Console (PMC)*. In this case, ensure that {{if UI=="MVC"}}`Acme.BookStore.Web`{{else if UI=="BlazorServer"}}`Acme.BookStore.Blazor`{{else if UI=="Blazor" || UI=="NG"}}`Acme.BookStore.HttpApi.Host`{{end}} is the startup project and `Acme.BookStore.EntityFrameworkCore` is the *Default Project* in PMC. {{end}} diff --git a/docs/en/Tutorials/Part-4.md b/docs/en/Tutorials/Part-4.md index 85c6c6502e..2202aba704 100644 --- a/docs/en/Tutorials/Part-4.md +++ b/docs/en/Tutorials/Part-4.md @@ -52,7 +52,7 @@ This part covers the **server side** tests. There are several test projects in t Each project is used to test the related project. Test projects use the following libraries for testing: -* [Xunit](https://xunit.github.io/) as the main test framework. +* [Xunit](https://github.com/xunit/xunit) as the main test framework. * [Shoudly](https://github.com/shouldly/shouldly) as the assertion library. * [NSubstitute](http://nsubstitute.github.io/) as the mocking library. @@ -249,4 +249,4 @@ Congratulations, the **green icons** indicates that the tests have been successf ## The Next Part -See the [next part](Part-5.md) of this tutorial. \ No newline at end of file +See the [next part](Part-5.md) of this tutorial. diff --git a/docs/en/Tutorials/Part-7.md b/docs/en/Tutorials/Part-7.md index 312255e74c..45820cde09 100644 --- a/docs/en/Tutorials/Part-7.md +++ b/docs/en/Tutorials/Part-7.md @@ -48,7 +48,7 @@ Open the `BookStoreDbContext` in the `Acme.BookStore.EntityFrameworkCore` projec public DbSet Authors { get; set; } ```` -Then open the `BookStoreDbContextModelCreatingExtensions` class in the same project and add the following lines to the end of the `ConfigureBookStore` method: +Then locate to the `OnModelCreating` method in `BookStoreDbContext` class in the same project and add the following lines to the end of the method: ````csharp builder.Entity(b => @@ -72,7 +72,7 @@ This is just like done for the `Book` entity before, so no need to explain again The startup solution is configured to use [Entity Framework Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). Since we've changed the database mapping configuration, we should create a new migration and apply changes to the database. -Open a command-line terminal in the directory of the `Acme.BookStore.EntityFrameworkCore.DbMigrations` project and type the following command: +Open a command-line terminal in the directory of the `Acme.BookStore.EntityFrameworkCore` project and type the following command: ````bash dotnet ef migrations add Added_Authors @@ -88,7 +88,7 @@ You can apply changes to the database using the following command, in the same c dotnet ef database update ```` -> If you are using Visual Studio, you may want to use `Add-Migration Added_Authors -c BookStoreMigrationsDbContext` and `Update-Database -c BookStoreMigrationsDbContext` commands in the *Package Manager Console (PMC)*. In this case, ensure that {{if UI=="MVC"}}`Acme.BookStore.Web`{{else if UI=="BlazorServer"}}`Acme.BookStore.Blazor`{{else if UI=="Blazor" || UI=="NG"}}`Acme.BookStore.HttpApi.Host`{{end}} is the startup project and `Acme.BookStore.EntityFrameworkCore.DbMigrations` is the *Default Project* in PMC. +> If you are using Visual Studio, you may want to use `Add-Migration Added_Authors -c BookStoreMigrationsDbContext` and `Update-Database -c BookStoreMigrationsDbContext` commands in the *Package Manager Console (PMC)*. In this case, ensure that {{if UI=="MVC"}}`Acme.BookStore.Web`{{else if UI=="BlazorServer"}}`Acme.BookStore.Blazor`{{else if UI=="Blazor" || UI=="NG"}}`Acme.BookStore.HttpApi.Host`{{end}} is the startup project and `Acme.BookStore.EntityFrameworkCore` is the *Default Project* in PMC. {{else if DB=="Mongo"}} diff --git a/docs/en/Tutorials/Part-9.md b/docs/en/Tutorials/Part-9.md index 1ea6e5507d..2a3e43c736 100644 --- a/docs/en/Tutorials/Part-9.md +++ b/docs/en/Tutorials/Part-9.md @@ -719,7 +719,7 @@ Open the `/src/app/author/author.component.html` and replace the content as belo
- @@ -745,10 +745,10 @@ Open the `/src/app/author/author.component.html` and replace the content as belo {%{{{ '::Actions' | abpLocalization }}}%}
- -
diff --git a/docs/en/Tutorials/Todo/Index.md b/docs/en/Tutorials/Todo/Index.md index 64cbca13f3..9f4a33a9c0 100644 --- a/docs/en/Tutorials/Todo/Index.md +++ b/docs/en/Tutorials/Todo/Index.md @@ -151,13 +151,19 @@ Open the `TodoAppDbContext` class in the `EntityFrameworkCore` folder of the *To public DbSet TodoItems { get; set; } ```` -Then open the `TodoAppDbContextModelCreatingExtensions` class in the same folder and add a mapping configuration for the `TodoItem` class as shown below: +Then locate to `OnModelCreating` method in the `TodoAppDbContext` class and add the mapping code for the `TodoItem ` entity: ````csharp -public static void ConfigureTodoApp(this ModelBuilder builder) +protected override void OnModelCreating(ModelBuilder builder) { - Check.NotNull(builder, nameof(builder)); + base.OnModelCreating(builder); + /* Include modules to your migration db context */ + + builder.ConfigurePermissionManagement(); + ... + + /* Configure your own tables/entities inside here */ builder.Entity(b => { b.ToTable("TodoItems"); @@ -171,7 +177,7 @@ We've mapped `TodoItem` entity to a `TodoItems` table in the database. The startup solution is configured to use Entity Framework Core [Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations). Since we've changed the database mapping configuration, we should create a new migration and apply changes to the database. -Open a command-line terminal in the directory of the *TodoApp.EntityFrameworkCore.DbMigrations* project and type the following command: +Open a command-line terminal in the directory of the *TodoApp.EntityFrameworkCore* project and type the following command: ````bash dotnet ef migrations add Added_TodoItem @@ -187,7 +193,7 @@ You can apply changes to the database using the following command, in the same c dotnet ef database update ```` -> If you are using Visual Studio, you may want to use `Add-Migration Added_TodoItem` and `Update-Database` commands in the *Package Manager Console (PMC)*. In this case, ensure that {{if UI=="MVC"}}`TodoApp.Web`{{else if UI=="BlazorServer"}}`TodoApp.Blazor`{{else if UI=="Blazor" || UI=="NG"}}`TodoApp.HttpApi.Host`{{end}} is the startup project and `TodoApp.EntityFrameworkCore.DbMigrations` is the *Default Project* in PMC. +> If you are using Visual Studio, you may want to use `Add-Migration Added_TodoItem` and `Update-Database` commands in the *Package Manager Console (PMC)*. In this case, ensure that {{if UI=="MVC"}}`TodoApp.Web`{{else if UI=="BlazorServer"}}`TodoApp.Blazor`{{else if UI=="Blazor" || UI=="NG"}}`TodoApp.HttpApi.Host`{{end}} is the startup project and `TodoApp.EntityFrameworkCore` is the *Default Project* in PMC. {{else if DB=="Mongo"}} diff --git a/docs/en/Tutorials/Todo/todo-efcore-migration.png b/docs/en/Tutorials/Todo/todo-efcore-migration.png index df370aebcf..9fffeb716b 100644 Binary files a/docs/en/Tutorials/Todo/todo-efcore-migration.png and b/docs/en/Tutorials/Todo/todo-efcore-migration.png differ diff --git a/docs/en/Tutorials/images/bookstore-efcore-migration-authors.png b/docs/en/Tutorials/images/bookstore-efcore-migration-authors.png index a702875c71..2cfb29f86f 100644 Binary files a/docs/en/Tutorials/images/bookstore-efcore-migration-authors.png and b/docs/en/Tutorials/images/bookstore-efcore-migration-authors.png differ diff --git a/docs/en/Tutorials/images/bookstore-efcore-migration.png b/docs/en/Tutorials/images/bookstore-efcore-migration.png index 9714e795e2..5c034f6e80 100644 Binary files a/docs/en/Tutorials/images/bookstore-efcore-migration.png and b/docs/en/Tutorials/images/bookstore-efcore-migration.png differ diff --git a/docs/en/UI/Angular/Account-Module.md b/docs/en/UI/Angular/Account-Module.md index 9496eb95c5..3670a14f73 100644 --- a/docs/en/UI/Angular/Account-Module.md +++ b/docs/en/UI/Angular/Account-Module.md @@ -1,10 +1,10 @@ # Angular UI Account Module -Angular UI account module is available as of v4.3. It contains some pages (login, register, manage your profile, etc.). +Angular UI account module is available as of v4.3. It contains some pages (login, register, My account, etc.). If you add the account module to your project; -- "Manage your profile" link in the current user dropdown on the top bar will redirect the user to a page in the account module. +- "My account" link in the current user dropdown on the top bar will redirect the user to a page in the account module. - You can switch the authentication flow to the resource owner password flow. @@ -91,13 +91,13 @@ const routes: Routes = [ export class AppRoutingModule {} ``` -### Manage Profile Page +### My Account Page -Before v4.3, the "Manage Your Profile" link in the current user dropdown on the top bar redirected the user to MVC's profile management page. As of v4.3, if you added the account module to your project, the same link will land on a page in the Angular UI account module instead. +Before v4.3, the "My account" link in the current user dropdown on the top bar redirected the user to MVC's profile management page. As of v4.3, if you added the account module to your project, the same link will land on a page in the Angular UI account module instead. -### My Security Logs Page [COMMERCIAL] +### Security Logs Page [COMMERCIAL] -Before v4.3, the "My Security Logs" link in the current user dropdown on the top bar redirected the user to MVC's my security logs page. As of v4.3, if you added the account module to your project, the same link will land on a page in the Angular UI account public module instead. +Before v4.3, the "Security Logs" link in the current user dropdown on the top bar redirected the user to MVC's security logs page. As of v4.3, if you added the account module to your project, the same link will land on a page in the Angular UI account public module instead. ### Resource Owner Password Flow @@ -120,6 +120,4 @@ export const environment = { } as Config.Environment; ``` -> Note: The resource owner password flow does not support the two-factor authentication for some technical reasons. - See the [Authorization in Angular UI](./Authorization.md) document for more details. diff --git a/docs/en/UI/Angular/Authorization.md b/docs/en/UI/Angular/Authorization.md index 54850e4569..1bde885180 100644 --- a/docs/en/UI/Angular/Authorization.md +++ b/docs/en/UI/Angular/Authorization.md @@ -52,5 +52,3 @@ export const environment = { ``` According to this flow, the user is redirected to the login page in the account module. - -> Note: The resource owner password flow does not support the two-factor authentication for some technical reasons. \ No newline at end of file diff --git a/docs/en/UI/Angular/Component-Replacement.md b/docs/en/UI/Angular/Component-Replacement.md index 1b757c7c23..b75184c1ed 100644 --- a/docs/en/UI/Angular/Component-Replacement.md +++ b/docs/en/UI/Angular/Component-Replacement.md @@ -491,7 +491,7 @@ Open the generated `nav-items.component.html` in `src/app/nav-items` folder and [class.d-block]="smallScreen && currentUserDropdown.isOpen()" > {%{{{ 'AbpAccount::ManageYourProfile' | abpLocalization }}}%}{%{{{ 'AbpAccount::MyAccount' | abpLocalization }}}%} {%{{{ 'AbpUi::Logout' | abpLocalization }}}%} Bootstrap's JavaScript part is not used since the Blazorise library already provides the necessary functionalities to the Bootstrap components in a native way. +> Beginning from June, 2021, the Blazorise library has dual licenses; open source & commercial. Based on your yearly revenue, you may need to buy a commercial license. See [this post](https://blazorise.com/news/blazorise-commercial-going-live/) for the announcement. + ### The Layout The themes provide the layout. So, you have a responsive layout with the standard features already implemented. The screenshot below has taken from the layout of the [Basic Theme](Basic-Theme.md): diff --git a/docs/en/UI/Blazor/Theming.md b/docs/en/UI/Blazor/Theming.md index 00bf911ff9..067052e322 100644 --- a/docs/en/UI/Blazor/Theming.md +++ b/docs/en/UI/Blazor/Theming.md @@ -63,7 +63,7 @@ A theme is simply a Razor Class Library. ### The Easy Way -The easiest way to create a new theme is to copy the [Basic Theme Source Code](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme) and customize it. Once you get a copy of the theme in your solution, remove the `Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme` NuGet package and reference to the local project. +The easiest way to create a new theme is to copy the [Basic Theme Source Code](https://github.com/abpframework/abp/blob/dev/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme) and customize it. Once you get a copy of the theme in your solution, remove the `Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme` NuGet package and reference to the local project. ### Global Styles / Scripts diff --git a/docs/en/Upgrading.md b/docs/en/Upgrading.md index 9c4e39ce7e..2141a6823b 100644 --- a/docs/en/Upgrading.md +++ b/docs/en/Upgrading.md @@ -22,7 +22,7 @@ Run this command in the terminal while you are in the root folder of your soluti When you upgrade to a new version, it is good to check if there is a database schema change and upgrade your database schema if your database provider is **Entity Framework Core**; -* Use `Add-Migration "Upgraded_To_Abp_4_1"` or a similar command in the Package Manager Console (PMC) to create a new migration (Set the `EntityFrameworkCore.DbMigrations` as the Default project in the PMC and `.DbMigrator` as the Startup Project in the Solution Explorer, in the Visual Studio). +* Use `Add-Migration "Upgraded_To_Abp_4_1"` or a similar command in the Package Manager Console (PMC) to create a new migration (Set the `EntityFrameworkCore` as the Default project in the PMC and `.DbMigrator` as the Startup Project in the Solution Explorer, in the Visual Studio). * Run the `.DbMigrator` application to upgrade the database and seed the initial data. If `Add-Migration` generates an empty migration, you can use `Remove-Migration` to delete it before executing the `.DbMigrator`. diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index a8554d9354..c946b8401c 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -102,11 +102,7 @@ "path": "CLI.md", "items": [ { - "text": "Build Command", - "path": "CLI-BuildCommand.md" - }, - { - "text": "Create Solution Sample Commands", + "text": "Examples for the new command", "path": "CLI-New-Command-Samples.md" } ] @@ -418,26 +414,13 @@ "text": "Plug-In Modules", "path": "PlugIn-Modules.md" }, - { - "text": "Customizing/Extending Modules", - "path": "Customizing-Application-Modules-Guide.md" - }, - { - "text": "Customizing/Extending Entities", - "path": "Customizing-Application-Modules-Extending-Entities.md" - }, - { - "text": "Customizing/Overriding Services", - "path": "Customizing-Application-Modules-Overriding-Services.md" - }, - { - "text": "Module Entity Extensions", - "path": "Module-Entity-Extensions.md" - }, { "text": "Best Practices", - "path": "Best-Practices/Index.md", "items": [ + { + "text": "Overall", + "path": "Best-Practices/Index.md" + }, { "text": "Module Architecture", "path": "Best-Practices/Module-Architecture.md" @@ -486,6 +469,28 @@ ] } ] + }, + { + "text": "Customizing/Extending Modules", + "path": "Customizing-Application-Modules-Guide.md", + "items": [ + { + "text": "Overall", + "path": "Customizing-Application-Modules-Guide.md" + }, + { + "text": "Module Entity Extension System", + "path": "Module-Entity-Extensions.md" + }, + { + "text": "Customizing/Extending Entities", + "path": "Customizing-Application-Modules-Extending-Entities.md" + }, + { + "text": "Customizing/Overriding Services", + "path": "Customizing-Application-Modules-Overriding-Services.md" + } + ] } ] }, @@ -503,10 +508,6 @@ "text": "Entities & Aggregate Roots", "path": "Entities.md" }, - { - "text": "Multi Lingual Entities", - "path": "Multi-Lingual-Entities.md" - }, { "text": "Value Objects", "path": "Value-Objects.md" @@ -543,8 +544,8 @@ ] }, { - "text": "Guide: Implementing DDD", - "path": "Domain-Driven-Design-Implementation-Guide.md" + "text": "E-Book: Implementing DDD", + "path": "https://abp.io/books/implementing-domain-driven-design" } ] }, @@ -733,19 +734,24 @@ }, { "text": "Customize/Extend the UI", - "path": "UI/AspNetCore/Customization-User-Interface.md" - }, - { - "text": "Entity Action Extensions", - "path": "UI/AspNetCore/Entity-Action-Extensions.md" - }, - { - "text": "Data Table Column Extensions", - "path": "UI/AspNetCore/Data-Table-Column-Extensions.md" - }, - { - "text": "Page Toolbar Extensions", - "path": "UI/AspNetCore/Page-Toolbar-Extensions.md" + "items": [ + { + "text": "Overall", + "path": "UI/AspNetCore/Customization-User-Interface.md" + }, + { + "text": "Entity Action Extensions", + "path": "UI/AspNetCore/Entity-Action-Extensions.md" + }, + { + "text": "Data Table Column Extensions", + "path": "UI/AspNetCore/Data-Table-Column-Extensions.md" + }, + { + "text": "Page Toolbar Extensions", + "path": "UI/AspNetCore/Page-Toolbar-Extensions.md" + } + ] } ] }, @@ -1122,11 +1128,11 @@ "path": "Entity-Framework-Core-Oracle.md", "items": [ { - "text": "Oracle", + "text": "Oracle (Official)", "path": "Entity-Framework-Core-Oracle-Official.md" }, { - "text": "Oracle Devart", + "text": "Oracle (Devart)", "path": "Entity-Framework-Core-Oracle-Devart.md" } ] @@ -1257,56 +1263,7 @@ }, { "text": "Migration Guides", - "path": "Migration-Guides/Index.md", - "items": [ - { - "text": "MVC / Razor Pages", - "items": [ - { - "text": "3.3 to 4.0", - "path": "Migration-Guides/Abp-4_0-MVC-Razor-Pages.md" - } - ] - }, - { - "text": "Blazor", - "items": [ - { - "text": "3.2 to 3.3", - "path": "Migration-Guides/BlazorUI-3_3.md" - }, - { - "text": "3.3 to 4.0", - "path": "Migration-Guides/Abp-4_0-Blazor.md" - } - ] - }, - { - "text": "Angular", - "items": [ - { - "text": "3.3 to 4.0", - "path": "Migration-Guides/Abp-4_0-Angular.md" - } - ] - }, - { - "text": "3.3 to 4.0", - "path": "Migration-Guides/Abp-4_0.md" - }, - { - "text": "4.2", - "path": "Migration-Guides/Abp-4_2.md" - }, - { - "text": "4.x to 4.3", - "path": "Migration-Guides/Abp-4_3.md" - }, - { - "text": "Upgrading the Startup Template", - "path": "Migration-Guides/Upgrading-Startup-Template.md" - } - ] + "path": "Migration-Guides/Index.md" } ] }, diff --git a/docs/en/images/account-module-forgot-password.png b/docs/en/images/account-module-forgot-password.png new file mode 100644 index 0000000000..92fce47a22 Binary files /dev/null and b/docs/en/images/account-module-forgot-password.png differ diff --git a/docs/en/images/account-module-login.png b/docs/en/images/account-module-login.png new file mode 100644 index 0000000000..abde22db57 Binary files /dev/null and b/docs/en/images/account-module-login.png differ diff --git a/docs/en/images/account-module-manage-account.png b/docs/en/images/account-module-manage-account.png new file mode 100644 index 0000000000..92a5f34d72 Binary files /dev/null and b/docs/en/images/account-module-manage-account.png differ diff --git a/docs/en/images/account-module-register.png b/docs/en/images/account-module-register.png new file mode 100644 index 0000000000..f2b9c0587a Binary files /dev/null and b/docs/en/images/account-module-register.png differ diff --git a/docs/en/images/bookstore-visual-studio-solution-v3.png b/docs/en/images/bookstore-visual-studio-solution-v3.png index 307e3516a5..f8ef7f2ba0 100644 Binary files a/docs/en/images/bookstore-visual-studio-solution-v3.png and b/docs/en/images/bookstore-visual-studio-solution-v3.png differ diff --git a/docs/en/images/docs-create-project-v4.4.0.png b/docs/en/images/docs-create-project-v4.4.0.png new file mode 100644 index 0000000000..fa30fcbc3a Binary files /dev/null and b/docs/en/images/docs-create-project-v4.4.0.png differ diff --git a/docs/en/images/docs-module_solution-explorer.png b/docs/en/images/docs-module_solution-explorer.png index 2988ec4134..42b2c31143 100644 Binary files a/docs/en/images/docs-module_solution-explorer.png and b/docs/en/images/docs-module_solution-explorer.png differ diff --git a/docs/en/images/features-module-opening.png b/docs/en/images/features-module-opening.png new file mode 100644 index 0000000000..bd99604cd7 Binary files /dev/null and b/docs/en/images/features-module-opening.png differ diff --git a/docs/en/images/identity-module-menu.png b/docs/en/images/identity-module-menu.png new file mode 100644 index 0000000000..8083e2f722 Binary files /dev/null and b/docs/en/images/identity-module-menu.png differ diff --git a/docs/en/images/identity-module-permissions.png b/docs/en/images/identity-module-permissions.png new file mode 100644 index 0000000000..24ef15e3b4 Binary files /dev/null and b/docs/en/images/identity-module-permissions.png differ diff --git a/docs/en/images/identity-module-roles.png b/docs/en/images/identity-module-roles.png new file mode 100644 index 0000000000..c8e2c9cde1 Binary files /dev/null and b/docs/en/images/identity-module-roles.png differ diff --git a/docs/en/images/identity-module-users.png b/docs/en/images/identity-module-users.png new file mode 100644 index 0000000000..7de6da77d4 Binary files /dev/null and b/docs/en/images/identity-module-users.png differ diff --git a/docs/en/images/implementing-domain-driven-design-book.png b/docs/en/images/implementing-domain-driven-design-book.png new file mode 100644 index 0000000000..6568b46f66 Binary files /dev/null and b/docs/en/images/implementing-domain-driven-design-book.png differ diff --git a/docs/en/images/module-tenant-management-actions.png b/docs/en/images/module-tenant-management-actions.png new file mode 100644 index 0000000000..d1f01c2e93 Binary files /dev/null and b/docs/en/images/module-tenant-management-actions.png differ diff --git a/docs/en/images/module-tenant-management-new-tenant.png b/docs/en/images/module-tenant-management-new-tenant.png new file mode 100644 index 0000000000..ab9ee6ed56 Binary files /dev/null and b/docs/en/images/module-tenant-management-new-tenant.png differ diff --git a/docs/en/images/module-tenant-management-page.png b/docs/en/images/module-tenant-management-page.png new file mode 100644 index 0000000000..563c64aac8 Binary files /dev/null and b/docs/en/images/module-tenant-management-page.png differ diff --git a/docs/en/images/permissions-module-dialog.png b/docs/en/images/permissions-module-dialog.png new file mode 100644 index 0000000000..6b190484a2 Binary files /dev/null and b/docs/en/images/permissions-module-dialog.png differ diff --git a/docs/en/images/permissions-module-open-dialog.png b/docs/en/images/permissions-module-open-dialog.png new file mode 100644 index 0000000000..46631fe126 Binary files /dev/null and b/docs/en/images/permissions-module-open-dialog.png differ diff --git a/docs/pt-BR/MongoDB.md b/docs/pt-BR/MongoDB.md new file mode 100644 index 0000000000..bc4a7882a7 --- /dev/null +++ b/docs/pt-BR/MongoDB.md @@ -0,0 +1,466 @@ +# Integração do MongoDB + +Este documento explica como integrar e configurar o MongoDB como um provedor de banco de dados para aplicações baseadas no ABP. + +## Instalação + +`Volo.Abp.MongoDB` é o pacote nuget principal para a integração do MongoDB. Instale-o em seu projeto (para uma aplicação em camadas, será sua camada de dados ou infraestrutura): + +``` +Install-Package Volo.Abp.MongoDB +``` + +Então adicione a dependência de módulo `AbpMongoDbModule` para o seu [module](Module-Development-Basics.md): + +```c# +using Volo.Abp.MongoDB; +using Volo.Abp.Modularity; + +namespace MyCompany.MyProject +{ + [DependsOn(typeof(AbpMongoDbModule))] + public class MyModule : AbpModule + { + // ... + } +} +``` + +## Criando um Mongo Db Context + +ABP apresenta o conceito **Mongo Db Context** (que é semelhante ao Entity Framework Core DbContext) para tornar mais fácil usar coleções e configurá-las. Um exemplo é mostrado abaixo: + +```c# +public class MyDbContext : AbpMongoDbContext +{ + public IMongoCollection Questions => Collection(); + + public IMongoCollection Categories => Collection(); + + protected override void CreateModel(IMongoModelBuilder modelBuilder) + { + base.CreateModel(modelBuilder); + + // Personalize a configuração para as suas collections. + } +} +``` + +* É derivado da classe `AbpMongoDbContext`. +* Adiciona uma propriedade pública `IMongoCollection` para cada mongo collection. Por padrão ABP usa essas propriedades para criar repositórios padrão. +* Substituir o método `CreateModel` permite definir a configuração da collection. + +### Configurar o Mapeamento para uma Collection + +ABP registra automaticamente entidades MongoDB client library para todas propriedades `IMongoCollection` em seu DbContext. Para o exemplo acima, as entidades `Question` e `Category` são registradas automaticamente. + +Para cada entidade registrada, chama `AutoMap()` e configura propriedades conhecidas de sua entidade. Por exemplo, se sua entidade implementa uma interface `IHasExtraProperties` (que já está implementada para cada raiz agregada por padrão), ele configura automaticamente `ExtraProperties`. + +Portanto, na maioria das vezes, você não precisa configurar explicitamente o registro de suas entidades. No entanto, se você precisar, pode fazer isso sobrescrevendo o método `CreateModel` em seu DbContext. Exemplo: + +````csharp +protected override void CreateModel(IMongoModelBuilder modelBuilder) +{ + base.CreateModel(modelBuilder); + + modelBuilder.Entity(b => + { + b.CollectionName = "MyQuestions"; // Define o nome da collection + b.BsonMap.UnmapProperty(x => x.MyProperty); // Ignora 'MyProperty' + }); +} +```` + +Este exemplo altera o nome da collection mapeada para 'MyQuestions' no banco de dados e ignora uma propriedade na classe `Question`. + +Se você só precisa configurar o nome da collection, você também pode usar o atributo `[MongoCollection]` para a collection em seu DbContext. Exemplo: + +````csharp +[MongoCollection("MyQuestions")] // Define o nome da collection +public IMongoCollection Questions => Collection(); +```` + +### Configurar a Seleção da String de Conexão + +Se você tiver vários bancos de dados em seu aplicativo, você pode configurar o nome da string de conexão para o seu DbContext usando o atributo `[ConnectionStringName]`. Exemplo: + +````csharp +[ConnectionStringName("MySecondConnString")] +public class MyDbContext : AbpMongoDbContext +{ + +} +```` + +Se você não configurar, a string de conexão `Default` é usada. Se você configurar um nome de string de conexão, mas não definir este nome da string de conexão na configuração da aplicação, então ele retorna para a string de conexão `Default`. + +## Registrando DbContext para Injeção de Dependência + +Use o método `AddAbpDbContext` em seu module para registrar sua classe DbContext para o sistema de [injeção de dependência](Dependency-Injection.md). + +```c# +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.MongoDB; +using Volo.Abp.Modularity; + +namespace MyCompany.MyProject +{ + [DependsOn(typeof(AbpMongoDbModule))] + public class MyModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddMongoDbContext(); + + // ... + } + } +} +``` + +### Adicionar Repositories Padrão + +O ABP pode criar automaticamente [repositories genéricas](Repositories.md) padrão para as entidades em seu DbContext. Basta usar a opção `AddDefaultRepositories()` no registro: + +````C# +services.AddMongoDbContext(options => +{ + options.AddDefaultRepositories(); +}); +```` + +Isso criará uma repository para cada [aggregate root entity](Entities.md) (classes derivadas de `AggregateRoot`) por padrão. Se você quiser criar repositories para outras entidades também, em seguida defina `includeAllEntities` para `true`: + +```c# +services.AddMongoDbContext(options => +{ + options.AddDefaultRepositories(includeAllEntities: true); +}); +``` + +Então você pode injetar e usar `IRepository` nas suas services. Suponha que você tenha uma entidade `Book` com a chave primária `Guid`: + +```csharp +public class Book : AggregateRoot +{ + public string Name { get; set; } + + public BookType Type { get; set; } +} +``` + +(`BookType` é um simples `enum` aqui) E você deseja criar uma nova entidade `Book` em uma [domain service](Domain-Services.md): + +```csharp +public class BookManager : DomainService +{ + private readonly IRepository _bookRepository; + + public BookManager(IRepository bookRepository) // injetar repositório padrão + { + _bookRepository = bookRepository; + } + + public async Task CreateBook(string name, BookType type) + { + Check.NotNullOrWhiteSpace(name, nameof(name)); + + var book = new Book + { + Id = GuidGenerator.Create(), + Name = name, + Type = type + }; + + await _bookRepository.InsertAsync(book); // Use um método de repositório padrão + + return book; + } +} +``` + +Este exemplo usa o método `InsertAsync` para inserir uma nova entity no banco de dados. + +### Adicionar Repositories Personalizadas + +Repositories genéricas padrão são poderosas e suficiente na maioria dos casos (uma vez que implementam `IQueryable`). No entanto, pode ser necessário criar uma repository customizada para adicionar seus próprios métodos de repository. + +Suponha que você deseja excluir todos os books por type. É sugerido definir uma interface para sua repository personalizada: + +```csharp +public interface IBookRepository : IRepository +{ + Task DeleteBooksByType( + BookType type, + CancellationToken cancellationToken = default(CancellationToken) + ); +} +``` + +Você geralmente vai querer derivar de `IRepository` para herdar métodos de repository padrão. No entanto, você não precisa. As interfaces de repository são definidas na camada de domínio de uma aplicação em camadas. Elas são implementadas na camada de dados ou infraestrutura (projeto `MongoDB` em um [startup template](https://abp.io/Templates)). + +Exemplo de implementação da interface `IBookRepository`: + +```csharp +public class BookRepository : + MongoDbRepository, + IBookRepository +{ + public BookRepository(IMongoDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + + public async Task DeleteBooksByType( + BookType type, + CancellationToken cancellationToken = default(CancellationToken)) + { + var collection = await GetCollectionAsync(cancellationToken); + await collection.DeleteManyAsync( + Builders.Filter.Eq(b => b.Type, type), + cancellationToken + ); + } +} +``` + +Agora é possível [injetar](Dependency-Injection.md) a `IBookRepository` e usar o método `DeleteBooksByType` quando necessário. + +#### Substituir Repository Genérica Padrão + +Mesmo se você criar uma repository personalizada, você ainda pode injetar a repository genérica padrão (`IRepository` para este exemplo). A implementação da repository padrão não usará a classe que você criou. + +Se você querer substituir a implementação da repository padrão pela sua repository personalizada, faça dentro das opções `AddMongoDbContext`: + +```csharp +context.Services.AddMongoDbContext(options => +{ + options.AddDefaultRepositories(); + options.AddRepository(); //Replaces IRepository +}); +``` + +Isso é especialmente importante quando você deseja dar **override em um método da base repository** para customizar. Por exemplo, você pode querer substituir o método `DeleteAsync` para excluir uma entidade de uma forma mais eficiente: + +```csharp +public async override Task DeleteAsync( + Guid id, + bool autoSave = false, + CancellationToken cancellationToken = default) +{ + // TODO: Implementação customizada do método delete +} +``` + +### Acesso à API MongoDB + +Na maioria dos casos, você vai querer ocultar APIs do MongoDB atrás de uma repository (este é o objetivo principal da repository). No entanto, se você quiser acessar a API MongoDB através da Repository, você pode usar os métodos de extensão `GetDatabaseAsync()`, `GetCollectionAsync()` ou `GetAggregateAsync()`. Exemplo: + +```csharp +public class BookService +{ + private readonly IRepository _bookRepository; + + public BookService(IRepository bookRepository) + { + _bookRepository = bookRepository; + } + + public async Task FooAsync() + { + IMongoDatabase database = await _bookRepository.GetDatabaseAsync(); + IMongoCollection books = await _bookRepository.GetCollectionAsync(); + IAggregateFluent bookAggregate = await _bookRepository.GetAggregateAsync(); + } +} +``` + +> Importante: Você deve fazer referência ao pacote `Volo.Abp.MongoDB` do projeto que deseja acessar a API MongoDB. Isso quebra o encapsulamento, mas é o que você deseja nesse caso. + +### Transactions + +O MongoDB oferece suporte multi-document transactions a partir da versão 4.0 e o ABP Framework oferece suporte para isso. No entanto, o [startup template](Startup-templates/Index.md) **desativa** transactions por padrão. Se o seu **servidor** MongoDB suportar transactions, você pode habilitar esse recurso na classe *YourProjectMongoDbModule*: + +```csharp +Configure(options => +{ + options.TransactionBehavior = UnitOfWorkTransactionBehavior.Auto; +}); +``` + +> Ou você pode excluir este código, pois este já é o comportamento padrão. + +### Tópicos Avançados + +### Controlando o Multi-Tenancy + +Se sua solução for [multi-tenant](Multi-Tenancy.md), os tenants podem ter **bancos de dados separados**, você tem **múltiplas** classes `DbContext` em sua solução e algumas de suas classes `DbContext` devem ser utilizáveis **apenas do lado do host**, é recomendado adicionar o atributo `[IgnoreMultiTenancy]` em sua classe `DbContext`. Nesse caso, a ABP garante que o `DbContext` relacionado sempre usa a [connection string](Connection-Strings.md) do host, mesmo se você estiver em um tenant context. + +**Exemplo:** + +````csharp +[IgnoreMultiTenancy] +public class MyDbContext : AbpMongoDbContext +{ + ... +} +```` + +Não use o atributo `[IgnoreMultiTenancy]` se qualquer uma de suas entidades em seu `DbContext` puder ser persistida em um outro banco de dados de um tenant. + +> When you use repositories, ABP already uses the host database for the entities don't implement the `IMultiTenant` interface. So, most of time you don't need to `[IgnoreMultiTenancy]` attribute if you are using the repositories to work with the database. + +#### Definir Classes Repository Padrão + +Repositories genéricas padrão são implementadas pela classe `MongoDbRepository` por padrão. Você pode criar sua própria implementação e usá-la para implementação da repository padrão. + +Primeiro, defina suas classes de repository assim: + +```csharp +public class MyRepositoryBase + : MongoDbRepository + where TEntity : class, IEntity +{ + public MyRepositoryBase(IMongoDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } +} + +public class MyRepositoryBase + : MongoDbRepository + where TEntity : class, IEntity +{ + public MyRepositoryBase(IMongoDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } +} +``` + +O primeiro é para [entities com chaves compostas](Entities.md), o segundo é para entities com uma única chave primária. + +É sugerido herdar da classe `MongoDbRepository` e substituir os métodos, se necessário. Caso contrário, você terá que implementar todos os métodos de repository padrão manualmente. + +Agora, você pode usar a opção `SetDefaultRepositoryClasses`: + +```csharp +context.Services.AddMongoDbContext(options => +{ + options.SetDefaultRepositoryClasses( + typeof(MyRepositoryBase<,>), + typeof(MyRepositoryBase<>) + ); + // ... +}); +``` + +#### Definir classe base MongoDbContext ou Interface para Repositories padrão + +Se seu MongoDbContext herda de outro MongoDbContext ou implementa uma interface, você pode usar essa classe base ou interface como o MongoDbContext para repositories padrão. Exemplo: + +```csharp +public interface IBookStoreMongoDbContext : IAbpMongoDbContext +{ + Collection Books { get; } +} +``` + +`IBookStoreMongoDbContext` é implementado pela classe `BookStoreMongoDbContext`. Então você pode usar sobrecarga genérica do `AddDefaultRepositories`: + +```csharp +context.Services.AddMongoDbContext(options => +{ + options.AddDefaultRepositories(); + // ... +}); +``` + +Agora, seu `BookRepository` personalizado também pode usar a interface `IBookStoreMongoDbContext`: + +```csharp +public class BookRepository + : MongoDbRepository, + IBookRepository +{ + // ... +} +``` + +Uma vantagem de usar a interface para um MongoDbContext é que ela pode ser substituída por outra implementação. + +#### Substituir Outros DbContexts + +Depois de definir e usar adequadamente uma interface para um MongoDbContext, qualquer outra implementação pode usar as seguintes maneiras de substituí-lo: + +**ReplaceDbContextAttribute** + +```csharp +[ReplaceDbContext(typeof(IBookStoreMongoDbContext))] +public class OtherMongoDbContext : AbpMongoDbContext, IBookStoreMongoDbContext +{ + // ... +} +``` + +**Opção ReplaceDbContext** + +```csharp +context.Services.AddMongoDbContext(options => +{ + // ... + options.ReplaceDbContext(); +}); +``` + +Neste exemplo, `OtherMongoDbContext` implementa `IBookStoreMongoDbContext`. Este recurso permite que você tenha vários MongoDbContext (um por módulo) no desenvolvimento, mas um único MongoDbContext (implementa todas as interfaces de todos os MongoDbContexts) no tempo de execução. + +### Personalizar Operações em Massa + +Se você tiver uma lógica melhor ou usar uma biblioteca externa para operações em massa, pode substituir a lógica por meio da implementação de `IMongoDbBulkOperationProvider`. + +- Você pode usar o modelo de exemplo abaixo: + +```csharp +public class MyCustomMongoDbBulkOperationProvider + : IMongoDbBulkOperationProvider, ITransientDependency +{ + public async Task DeleteManyAsync( + IMongoDbRepository repository, + IEnumerable entities, + IClientSessionHandle sessionHandle, + bool autoSave, + CancellationToken cancellationToken) + where TEntity : class, IEntity + { + // Sua lógica aqui. + } + + public async Task InsertManyAsync( + IMongoDbRepository repository, + IEnumerable entities, + IClientSessionHandle sessionHandle, + bool autoSave, + CancellationToken cancellationToken) + where TEntity : class, IEntity + { + // Sua lógica aqui. + } + + public async Task UpdateManyAsync( + IMongoDbRepository repository, + IEnumerable entities, + IClientSessionHandle sessionHandle, + bool autoSave, + CancellationToken cancellationToken) + where TEntity : class, IEntity + { + // Sua lógica aqui. + } +} +``` + +## Veja Também + +* [Entities](Entities.md) +* [Repositories](Repositories.md) \ No newline at end of file diff --git a/docs/zh-Hans/CLI.md b/docs/zh-Hans/CLI.md index 262f546bf2..4325ee1f07 100644 --- a/docs/zh-Hans/CLI.md +++ b/docs/zh-Hans/CLI.md @@ -39,6 +39,7 @@ dotnet tool update -g Volo.Abp.Cli * **`translate`**: 当源代码控制存储库中有多个JSON[本地化](Localization.md文件时,可简化翻译本地化文件的过程. * **`login`**: 使用你在[abp.io](https://abp.io/)的用户名和密码在你的计算机上认证. * **`logout`**: 在你的计算机注销认证. +* **`install-libs`**: 为 MVC / Razor Pages 和 Blazor Server UI 类型安装NPM包. ### help @@ -339,3 +340,19 @@ abp login -p -o # You can enter both your ``` abp logout ``` + +### install-libs + +为 MVC / Razor Pages 和 Blazor Server UI 类型安装NPM包, 它的 **执行目录** 或者传递的 ```--working-directory``` 目录必须包含一个项目文件(*.csproj). + +`install-libs` 命令读取 `abp.resourcemapping.js` 来管理包. 参阅[客户端包管理](UI/AspNetCore/Client-Side-Package-Management.md)了解更多细节. + +用法: + +````bash +abp install-libs [options] +```` + +#### Options + +* ```--working-directory``` 或 ```-wd```: 指定工作目录, 当执行目录不包含项目文件时会很有用. \ No newline at end of file diff --git a/docs/zh-Hans/Customizing-Application-Modules-Extending-Entities.md b/docs/zh-Hans/Customizing-Application-Modules-Extending-Entities.md index 6c895f71d2..8fd0921a83 100644 --- a/docs/zh-Hans/Customizing-Application-Modules-Extending-Entities.md +++ b/docs/zh-Hans/Customizing-Application-Modules-Extending-Entities.md @@ -151,14 +151,14 @@ public class MyDistributedIdentityUserChangeEventHandler : * 它实现了多个 `IDistributedEventHandler` 接口: **创建**,**更改**和**删除**,因为分布式事件总线单独发布事件,没有本地事件总线那样的"Changed"事件. * 它订阅了 `EntityEto`, 这是一个通用的事件类,ABP框架针对所有类型的实体**自动发布**. 这就是为什么它检查**实体类型**(因为我们没有假设有对 `IdentityUser` 实体有安全的类型引用,所以它是字符串类型的). -预构建应用模块没有定义专门的事件类型(如`IdentityUserEto` - "ETO" 意思是 "事件传输对象"). 此功能在路线图上([关注这个issue](https://github.com/abpframework/abp/issues/3033)),一旦完成后,你就可以订阅独立的实体类型: +预构建应用模块没有定义专门的事件类型(如`UserEto` - "ETO" 意思是 "事件传输对象"). 此功能在路线图上([关注这个issue](https://github.com/abpframework/abp/issues/3033)),一旦完成后,你就可以订阅独立的实体类型: ````csharp public class MyDistributedIdentityUserCreatedEventHandler : - IDistributedEventHandler>, + IDistributedEventHandler>, ITransientDependency { - public async Task HandleEventAsync(EntityCreatedEto eventData) + public async Task HandleEventAsync(EntityCreatedEto eventData) { var userId = eventData.Entity.Id; var userName = eventData.Entity.UserName; diff --git a/docs/zh-Hans/Distributed-Event-Bus.md b/docs/zh-Hans/Distributed-Event-Bus.md index 4b4203911b..bd086c3d83 100644 --- a/docs/zh-Hans/Distributed-Event-Bus.md +++ b/docs/zh-Hans/Distributed-Event-Bus.md @@ -4,7 +4,7 @@ ## 提供程序 -分布式事件总线系统提供了一个可以被任何提供程序实现的**抽象**. 有两种开箱即用的提供程序: +分布式事件总线系统提供了一个可以被任何提供程序实现的**抽象**. 有四种开箱即用的提供程序: * `LocalDistributedEventBus` 是默认实现,实现作为进程内工作的分布式事件总线. 是的!如果没有配置真正的分布式提供程序,**默认实现的工作方式与[本地事件总线](Local-Event-Bus.md)一样**. * `RabbitMqDistributedEventBus` 通过[RabbitMQ](https://www.rabbitmq.com/)实现分布式事件总线. 请参阅[RabbitMQ集成文档](Distributed-Event-Bus-RabbitMQ-Integration.md)了解如何配置它. @@ -299,4 +299,55 @@ namespace AbpDemo } ```` -此示例使用AutoMapper的 `AutoMap` 属性配置的映射. 你可以创建一个配置文件类代替. 请参阅AutoMapper文档了解更多选项. \ No newline at end of file +此示例使用AutoMapper的 `AutoMap` 属性配置的映射. 你可以创建一个配置文件类代替. 请参阅AutoMapper文档了解更多选项. + +## 异常处理 + +ABP提供了异常处理, 它会进行重试并且重试失败后移动到死信队列. + +启用异常处理: + +```csharp +public override void PreConfigureServices(ServiceConfigurationContext context) +{ + PreConfigure(options => + { + options.EnabledErrorHandle = true; + options.UseRetryStrategy(); + }); +} +``` + +* `EnabledErrorHandle` 用于启用异常处理. +* `UseRetryStrategy` 用于启用重试. + +当一个异常抛出,它会每3秒重试一次直到最大重试次数(默认是3)并且移动到错误队列, 你可以更改重试次数,重试间隔和死信队列名称: + +```csharp +PreConfigure(options => +{ + options.DeadLetterName = "dead_queue"; + options.UseRetryStrategy(retryStrategyOptions => + { + retryStrategyOptions.IntervalMillisecond = 0; + retryStrategyOptions.MaxRetryAttempts = 1; + }); +}); +``` + +### 错误处理选择器 + +默认所有的事件类型都会被处理, 你可以使用 `AbpEventBusOptions` 的 `ErrorHandleSelector` 来更改它: + +```csharp +PreConfigure(options => +{ + options.ErrorHandleSelector = type => type == typeof(MyExceptionHandleEventData); +}); +``` + +`options.ErrorHandleSelector` 实际上是一个类型类型谓词列表. 你可以编写lambda来定义你的过滤. + +### 自定义异常处理 + +ABP定义了 `IEventErrorHandler` 接口并且由提供程序实现, 你可以通过[依赖注入](Dependency-Injection.md)替换它. \ No newline at end of file diff --git a/docs/zh-Hans/Entity-Framework-Core.md b/docs/zh-Hans/Entity-Framework-Core.md index 4139407f04..78df73150c 100644 --- a/docs/zh-Hans/Entity-Framework-Core.md +++ b/docs/zh-Hans/Entity-Framework-Core.md @@ -30,7 +30,7 @@ namespace MyCompany.MyProject ### 数据库管理系统选择 -EF Core支持多种数据库管理系统([查看全部](https://docs.microsoft.com/en-us/ef/core/providers/)). ABP框架和本文档不依赖于任何特定的DBMS. +EF Core支持多种数据库管理系统([查看全部](https://docs.microsoft.com/zh-cn/ef/core/providers/)). ABP框架和本文档不依赖于任何特定的DBMS. 如果要创建一个可重用的[应用程序模块](Modules/Index.md),应避免依赖于特定的DBMS包.但在最终的应用程序中,始终会选择一个DBMS. @@ -60,7 +60,7 @@ namespace MyCompany.MyProject ### 关于EF Core Fluent Mapping -[应用程序启动模板](Startup-Templates/Application.md)已配置使用[EF Core fluent configuration API](https://docs.microsoft.com/en-us/ef/core/modeling/)映射你的实体到数据库表. +[应用程序启动模板](Startup-Templates/Application.md)已配置使用[EF Core fluent configuration API](https://docs.microsoft.com/zh-cn/ef/core/modeling/)映射你的实体到数据库表. 你依然为你的实体属性使用**data annotation attributes**(像`[Required]`),而ABP文档通常遵循**fluent mapping API** approach方法. 如何使用取决与你. @@ -272,6 +272,285 @@ public async override Task DeleteAsync( } ```` +## 加载关联实体 + +假设你拥有带有`OrderLine`集合的`Order`,并且`OrderLine`具有`Order`的导航属性: + +````csharp +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Volo.Abp.Auditing; +using Volo.Abp.Domain.Entities; + +namespace MyCrm +{ + public class Order : AggregateRoot, IHasCreationTime + { + public Guid CustomerId { get; set; } + public DateTime CreationTime { get; set; } + + public ICollection Lines { get; set; } //子集合 + + public Order() + { + Lines = new Collection(); + } + } + + public class OrderLine : Entity + { + public Order Order { get; set; } //导航属性 + public Guid OrderId { get; set; } + + public Guid ProductId { get; set; } + public int Count { get; set; } + public double UnitPrice { get; set; } + } +} + +```` + +然后象下面显示的这样定义数据库映射: + +````csharp +builder.Entity(b => +{ + b.ToTable("Orders"); + b.ConfigureByConvention(); + + //定义关系 + b.HasMany(x => x.Lines) + .WithOne(x => x.Order) + .HasForeignKey(x => x.OrderId) + .IsRequired(); +}); + +builder.Entity(b => +{ + b.ToTable("OrderLines"); + b.ConfigureByConvention(); +}); +```` + +当你查询一个 `Order`, 你可能想要在单个查询中**包含**所有的 `OrderLine`s 或根据需要在**以后加载它们**. + +> 实际上这与ABP框架没有直接关系. 你可以按照 [EF Core 文档](https://docs.microsoft.com/zh-cn/ef/core/querying/related-data/) 了解全部细节. 本节将涵盖与 ABP 框架相关的一些主题. + +### 预先加载 / 包含子对象的加载 + +当你想加载一个带有关联实体的实体时,可以使用不同的选项. + +#### Repository.WithDetails + +`IRepository.WithDetailsAsync(...)` 可以通过包含一个关系收集/属性来获得 `IQueryable` . + +**示例: 获取一个带有 `lines` 的 `order` 对象** + +````csharp +using System; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Domain.Services; + +namespace AbpDemo.Orders +{ + public class OrderManager : DomainService + { + private readonly IRepository _orderRepository; + + public OrderManager(IRepository orderRepository) + { + _orderRepository = orderRepository; + } + + public async Task TestWithDetails(Guid id) + { + //通过包含子集合获取一个 IQueryable + var queryable = await _orderRepository.WithDetailsAsync(x => x.Lines); + + //应用其他的 LINQ 扩展方法 + var query = queryable.Where(x => x.Id == id); + + //执行此查询并获取结果 + var order = await AsyncExecuter.FirstOrDefaultAsync(query); + } + } +} +```` + +> `AsyncExecuter` 用于执行异步 LINQ 扩展,而无需依赖 EF Core. 如果你将 EF Core NuGet 包引用添加到你的项目中,则可以直接使用 `await query.FirstOrDefaultAsync()`. 但是, 这次你依赖于域层中的 EF 核心.请参阅. 请参阅 [仓储文档](Repositories.md) 以了解更多. + +**示例: 获取一个包含 `lines` 的 `orders` 列表** + +````csharp +public async Task TestWithDetails() +{ + //通过包含一个子集合获取一个 IQueryable + var queryable = await _orderRepository.WithDetailsAsync(x => x.Lines); + + //执行此查询并获取结果 + var orders = await AsyncExecuter.ToListAsync(queryable); +} +```` + +> 如果你需要包含多个导航属性或集合,`WithDetailsAsync`方法可以获得多个表达参数. + +#### DefaultWithDetailsFunc + +如果你没有将任何表达式传递到 `WithDetailsAsync` 方法,则它包括使用你提供的 `DefaultWithDetailsFunc` 选项的所有详细信息. + +你可以在你的 `EntityFrameworkCore` 项目[模块](Module-Development-Basics.md)的 `ConfigureServices`方法为一个实体配置 `DefaultWithDetailsFunc`. + +**示例: 在查询一个 `Order` 时包含 `Lines`** + +````csharp +Configure(options => +{ + options.Entity(orderOptions => + { + orderOptions.DefaultWithDetailsFunc = query => query.Include(o => o.Lines); + }); +}); +```` + +> 你可以在这里完全使用 EF Core API,因为这位于 EF Core集成项目中. + +然后你可以不带任何参数地调用 `WithDetails` 方法: + +````csharp +public async Task TestWithDetails() +{ + //通过包含一个子集合获取一个 IQueryable + var queryable = await _orderRepository.WithDetailsAsync(); + + //执行此查询并获取结果 + var orders = await AsyncExecuter.ToListAsync(queryable); +} +```` + +`WithDetailsAsync()` 执行你已经在 `DefaultWithDetailsFunc` 中设置的表达式. + +#### 仓储 Get/Find 方法 + +有些标准的 [仓储](Repositories.md) 方法带有可选的 `includeDetails` 参数; + +* `GetAsync` 和 `FindAsync` 方法带有默认值为 `true` 的 `includeDetails`. +* `GetListAsync` 和 `GetPagedListAsync` 方法带有默认值为 `false` 的 `includeDetails`. + +这意味着,默认情况下返回**包含子对象的单个实体**,而列表返回方法则默认不包括子对象信息.你可以明确通过 `includeDetails` 来更改此行为. + +> 这些方法使用上面解释的 `DefaultWithDetailsFunc` 选项. + +**示例:获取一个包含子对象的 `order`** + +````csharp +public async Task TestWithDetails(Guid id) +{ + var order = await _orderRepository.GetAsync(id); +} +```` + +**示例:获取一个不包含子对象的 `order`** + +````csharp +public async Task TestWithoutDetails(Guid id) +{ + var order = await _orderRepository.GetAsync(id, includeDetails: false); +} +```` + +**示例:获取一个包含子对象的实体列表** + +````csharp +public async Task TestWithDetails() +{ + var orders = await _orderRepository.GetListAsync(includeDetails: true); +} +```` + +#### 选择 + +存储库模式尝试封装 EF Core, 因此你的选项是有限的. 如果你需要高级方案,你可以按照其中一个选项执行: + +* 创建自定义存储库方法并使用完整的 EF Core API. +* 在你的项目中引用 `Volo.Abp.EntityFrameworkCore` . 通过这种方式,你可以直接在代码中使用 `Include` 和 `ThenInclude` . + +请参阅 EF Core 的 [预先加载文档](https://docs.microsoft.com/zh-cn/ef/core/querying/related-data/eager). + +### 显示 / 延迟加载 + +如果你在查询实体时不包括关系,并且以后需要访问导航属性或集合,则你有不同的选择. + +#### EnsurePropertyLoadedAsync / EnsureCollectionLoadedAsync + +仓储提供 `EnsurePropertyLoadedAsync` 和 `EnsureCollectionLoadedAsync` 扩展方法来**显示加载**一个导航属性或子集合. + +**示例: 在需要时加载一个 `Order` 的 `Lines`** + +````csharp +public async Task TestWithDetails(Guid id) +{ + var order = await _orderRepository.GetAsync(id, includeDetails: false); + //order.Lines 此时是空的 + + await _orderRepository.EnsureCollectionLoadedAsync(order, x => x.Lines); + //order.Lines 被填充 +} +```` + +如果导航属性或集合已经被加载那么 `EnsurePropertyLoadedAsync` 和 `EnsureCollectionLoadedAsync` 方法不做任何处理. 所以,调用多次也没有问题. + +请参阅 EF Core 的[显示加载文档](https://docs.microsoft.com/zh-cn/ef/core/querying/related-data/explicit). + +#### 使用代理的延时加载 + +在某些情况下,可能无法使用显示加载,尤其是当你没有引用 `Repository` 或 `DbContext`时.延时加载是 EF Core 加载关联属性/集合的一个功能,,当你第一次访问它. + +启用延时加载: + +1. 安装 [Microsoft.EntityFrameworkCore.Proxies](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.Proxies/) 包到你的项目(通常是 EF Core 集成项目) +2. 为你的 `DbContext` 配置 `UseLazyLoadingProxies` (在 EF Core 项目的模块的 `ConfigureServices` 方法中). 例如: + +````csharp +Configure(options => +{ + options.PreConfigure(opts => + { + opts.DbContextOptions.UseLazyLoadingProxies(); //启用延时加载 + }); + + options.UseSqlServer(); +}); +```` + +3. 使你的导航属性和集合是 `virtual`. 例如: + +````csharp +public virtual ICollection Lines { get; set; } //虚集合 +public virtual Order Order { get; set; } //虚导航属性 +```` + +启用延时加载并整理实体后,你可以自由访问导航属性和集合: + +````csharp +public async Task TestWithDetails(Guid id) +{ + var order = await _orderRepository.GetAsync(id); + //order.Lines 此时是空的 + + var lines = order.Lines; + //order.Lines 被填充 (延时加载) +} +```` + +每当你访问属性/集合时,EF Core 都会自动执行额外的查询,从数据库中加载属性/集合. + +> 应谨慎使用延时加载,因为它可能会在某些特定情况下导致性能问题. + +请参阅 EF Core 的[延时加载文档](https://docs.microsoft.com/zh-cn/ef/core/querying/related-data/lazy). + ## 访问 EF Core API 大多数情况下应该隐藏仓储后面的EF Core API(这也是仓储的设计目地). 但是如果想要通过仓储访问DbContext实现,则可以使用`GetDbContext()`或`GetDbSet()`扩展方法. 例: diff --git a/docs/zh-Hans/Local-Event-Bus.md b/docs/zh-Hans/Local-Event-Bus.md index 3a955e90d7..3a2944d32b 100644 --- a/docs/zh-Hans/Local-Event-Bus.md +++ b/docs/zh-Hans/Local-Event-Bus.md @@ -158,7 +158,53 @@ namespace AbpDemo 如果您执行**数据库操作**并在事件处理程序中使用[仓储](Repositories.md),那么您可能需要创建一个[工作单元](Unit-Of-Work.md),因为一些存储库方法需要在**活动的工作单元**中工作. 确保处理方法设置为 `virtual`,并为该方法添加一个 `[UnitOfWork]` attribute. 或者手动使用 `IUnitOfWorkManager` 创建一个工作单元范围. -## 事务和异常行为 +## 异常处理 + +ABP提供了异常处理, 当异常发现时它会进行重试. + +启用异常处理: + +```csharp +public override void PreConfigureServices(ServiceConfigurationContext context) +{ + PreConfigure(options => + { + options.UseRetryStrategy(); + }); +} +``` + +当一个异常抛出,它会每3秒重试一次直到最大重试次数(默认是3)并且移动抛出原始异常, 你可以更改重试次数和重试间隔和: + +```csharp +PreConfigure(options => +{ + options.UseRetryStrategy(retryStrategyOptions => + { + retryStrategyOptions.IntervalMillisecond = 0; + retryStrategyOptions.MaxRetryAttempts = 1; + }); +}); +``` + +### 错误处理选择器 + +默认所有的事件类型都会被处理, 你可以使用 `AbpEventBusOptions` 的 `ErrorHandleSelector` 来更改它: + +```csharp +PreConfigure(options => +{ + options.ErrorHandleSelector = type => type == typeof(MyExceptionHandleEventData); +}); +``` + +`options.ErrorHandleSelector` 实际上是一个类型类型谓词列表. 你可以编写lambda来定义你的过滤. + +### 自定义异常处理 + +ABP定义了 `IEventErrorHandler` 接口并且由 `LocalEventErrorHandler` 实现, 你可以通过[依赖注入](Dependency-Injection.md)替换它. + +### 事务和异常行为 当一个事件发布,订阅的事件处理程序将立即执行.所以; diff --git a/docs/zh-Hans/UI/Angular/Component-Replacement.md b/docs/zh-Hans/UI/Angular/Component-Replacement.md index 2c37a5a2e7..0877ac7a32 100644 --- a/docs/zh-Hans/UI/Angular/Component-Replacement.md +++ b/docs/zh-Hans/UI/Angular/Component-Replacement.md @@ -495,7 +495,7 @@ export class NavItemsComponent implements AfterViewInit { [class.d-block]="smallScreen && currentUserDropdown.isOpen()" > {%{{{ 'AbpAccount::ManageYourProfile' | abpLocalization }}}%}{%{{{ 'AbpAccount::MyAccount' | abpLocalization }}}%} {%{{{ 'AbpUi::Logout' | abpLocalization }}}%}(options => #### 布局位置 -你可以在[这里](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts)找到基本主题的布局文件. 你可以将它们作用构建自己的布局的参考,也可以在必要时覆盖它们. +你可以在[这里](https://github.com/abpframework/abp/blob/dev/modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts)找到基本主题的布局文件. 你可以将它们作用构建自己的布局的参考,也可以在必要时覆盖它们. #### ITheme diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index b71288ab93..0605f5c103 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -155,16 +155,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.EntityFrameworkCor EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.EventBus.RabbitMQ", "src\Volo.Abp.EventBus.RabbitMQ\Volo.Abp.EventBus.RabbitMQ.csproj", "{468C3DCB-8C00-40E7-AE51-0738EAAB312A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic", "src\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.csproj", "{2F5EE6D9-511B-4998-BD62-0B9F03E02432}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared", "src\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj", "{86A3BB43-8FA2-4CC2-BAD0-A86C6C9D9585}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.UI.Tests", "test\Volo.Abp.AspNetCore.Mvc.UI.Tests\Volo.Abp.AspNetCore.Mvc.UI.Tests.csproj", "{7E0517E0-AE09-4E10-8469-308F065F2F43}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo", "test\Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo\Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.csproj", "{57E29185-6DAF-4A41-8CE0-3F07EE3E289A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Tests", "test\Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Tests\Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Tests.csproj", "{869F7453-7DDC-4915-B92C-7861466AB68B}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Emailing", "src\Volo.Abp.Emailing\Volo.Abp.Emailing.csproj", "{8B1CB44B-BA40-4C78-9447-A7864126D7C3}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Sms", "src\Volo.Abp.Sms\Volo.Abp.Sms.csproj", "{8BB10746-8BAD-4317-8EE5-A36805DB93F6}" @@ -271,8 +265,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BackgroundWorkers. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo", "src\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.csproj", "{29E42ADB-85F8-44AE-A9B0-078F84C1B866}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo", "test\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo.csproj", "{0C498CF2-D052-4BF7-AD35-509A90F69707}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Http.Client.IdentityModel.Web.Tests", "test\Volo.Abp.Http.Client.IdentityModel.Web.Tests\Volo.Abp.Http.Client.IdentityModel.Web.Tests.csproj", "{E1963439-2BE5-4DB5-8438-2A9A792A1ADA}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.ObjectExtending", "src\Volo.Abp.ObjectExtending\Volo.Abp.ObjectExtending.csproj", "{D1815C77-16D6-4F99-8814-69065CD89FB3}" @@ -343,8 +335,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Compone EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.BlazoriseUI", "src\Volo.Abp.BlazoriseUI\Volo.Abp.BlazoriseUI.csproj", "{4EBDDB1B-D6C5-4FAE-B5A7-2171B18CDFA5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme", "src\Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme\Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.csproj", "{ABC27C10-C0FF-44CB-B4FF-A09C0B79F695}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Components.WebAssembly.Theming", "src\Volo.Abp.AspNetCore.Components.WebAssembly.Theming\Volo.Abp.AspNetCore.Components.WebAssembly.Theming.csproj", "{29CA7471-4E3E-4E75-8B33-001DDF682F01}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Autofac.WebAssembly", "src\Volo.Abp.Autofac.WebAssembly\Volo.Abp.Autofac.WebAssembly.csproj", "{37F89B0B-1C6B-426F-A5EE-676D1956D9E9}" @@ -365,14 +355,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Http.Tests", "test EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Components.Web", "src\Volo.Abp.AspNetCore.Components.Web\Volo.Abp.AspNetCore.Components.Web.csproj", "{F03A1CEA-FA44-4F30-BFC2-00BC2EAAB4E2}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Components.Web.BasicTheme", "src\Volo.Abp.AspNetCore.Components.Web.BasicTheme\Volo.Abp.AspNetCore.Components.Web.BasicTheme.csproj", "{5AD34048-5DAA-4067-9C7D-09295617A057}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Components.Web.Theming", "src\Volo.Abp.AspNetCore.Components.Web.Theming\Volo.Abp.AspNetCore.Components.Web.Theming.csproj", "{B9133C38-AC24-4E2F-B581-D124CF410CDF}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.EventBus.Abstractions", "src\Volo.Abp.EventBus.Abstractions\Volo.Abp.EventBus.Abstractions.csproj", "{8FDB3BF7-AD89-43F6-8DEB-C3E29B8801FE}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Components.Server.BasicTheme", "src\Volo.Abp.AspNetCore.Components.Server.BasicTheme\Volo.Abp.AspNetCore.Components.Server.BasicTheme.csproj", "{F35E5CFC-569F-4D7D-A30F-DD8AE97FEC5A}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Sms.Aliyun", "src\Volo.Abp.Sms.Aliyun\Volo.Abp.Sms.Aliyun.csproj", "{ACFBA3FB-18CE-4655-9D14-1F1F5C3DFC30}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Sms.Aliyun.Tests", "test\Volo.Abp.Sms.Aliyun.Tests\Volo.Abp.Sms.Aliyun.Tests.csproj", "{DADEA538-3CA1-4ADE-A7E6-EF77A0CE4401}" @@ -699,10 +685,6 @@ Global {468C3DCB-8C00-40E7-AE51-0738EAAB312A}.Debug|Any CPU.Build.0 = Debug|Any CPU {468C3DCB-8C00-40E7-AE51-0738EAAB312A}.Release|Any CPU.ActiveCfg = Release|Any CPU {468C3DCB-8C00-40E7-AE51-0738EAAB312A}.Release|Any CPU.Build.0 = Release|Any CPU - {2F5EE6D9-511B-4998-BD62-0B9F03E02432}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2F5EE6D9-511B-4998-BD62-0B9F03E02432}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2F5EE6D9-511B-4998-BD62-0B9F03E02432}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2F5EE6D9-511B-4998-BD62-0B9F03E02432}.Release|Any CPU.Build.0 = Release|Any CPU {86A3BB43-8FA2-4CC2-BAD0-A86C6C9D9585}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {86A3BB43-8FA2-4CC2-BAD0-A86C6C9D9585}.Debug|Any CPU.Build.0 = Debug|Any CPU {86A3BB43-8FA2-4CC2-BAD0-A86C6C9D9585}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -711,14 +693,6 @@ Global {7E0517E0-AE09-4E10-8469-308F065F2F43}.Debug|Any CPU.Build.0 = Debug|Any CPU {7E0517E0-AE09-4E10-8469-308F065F2F43}.Release|Any CPU.ActiveCfg = Release|Any CPU {7E0517E0-AE09-4E10-8469-308F065F2F43}.Release|Any CPU.Build.0 = Release|Any CPU - {57E29185-6DAF-4A41-8CE0-3F07EE3E289A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {57E29185-6DAF-4A41-8CE0-3F07EE3E289A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {57E29185-6DAF-4A41-8CE0-3F07EE3E289A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {57E29185-6DAF-4A41-8CE0-3F07EE3E289A}.Release|Any CPU.Build.0 = Release|Any CPU - {869F7453-7DDC-4915-B92C-7861466AB68B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {869F7453-7DDC-4915-B92C-7861466AB68B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {869F7453-7DDC-4915-B92C-7861466AB68B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {869F7453-7DDC-4915-B92C-7861466AB68B}.Release|Any CPU.Build.0 = Release|Any CPU {8B1CB44B-BA40-4C78-9447-A7864126D7C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8B1CB44B-BA40-4C78-9447-A7864126D7C3}.Debug|Any CPU.Build.0 = Debug|Any CPU {8B1CB44B-BA40-4C78-9447-A7864126D7C3}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -931,10 +905,6 @@ Global {29E42ADB-85F8-44AE-A9B0-078F84C1B866}.Debug|Any CPU.Build.0 = Debug|Any CPU {29E42ADB-85F8-44AE-A9B0-078F84C1B866}.Release|Any CPU.ActiveCfg = Release|Any CPU {29E42ADB-85F8-44AE-A9B0-078F84C1B866}.Release|Any CPU.Build.0 = Release|Any CPU - {0C498CF2-D052-4BF7-AD35-509A90F69707}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0C498CF2-D052-4BF7-AD35-509A90F69707}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0C498CF2-D052-4BF7-AD35-509A90F69707}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0C498CF2-D052-4BF7-AD35-509A90F69707}.Release|Any CPU.Build.0 = Release|Any CPU {E1963439-2BE5-4DB5-8438-2A9A792A1ADA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E1963439-2BE5-4DB5-8438-2A9A792A1ADA}.Debug|Any CPU.Build.0 = Debug|Any CPU {E1963439-2BE5-4DB5-8438-2A9A792A1ADA}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1075,10 +1045,6 @@ Global {4EBDDB1B-D6C5-4FAE-B5A7-2171B18CDFA5}.Debug|Any CPU.Build.0 = Debug|Any CPU {4EBDDB1B-D6C5-4FAE-B5A7-2171B18CDFA5}.Release|Any CPU.ActiveCfg = Release|Any CPU {4EBDDB1B-D6C5-4FAE-B5A7-2171B18CDFA5}.Release|Any CPU.Build.0 = Release|Any CPU - {ABC27C10-C0FF-44CB-B4FF-A09C0B79F695}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ABC27C10-C0FF-44CB-B4FF-A09C0B79F695}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ABC27C10-C0FF-44CB-B4FF-A09C0B79F695}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ABC27C10-C0FF-44CB-B4FF-A09C0B79F695}.Release|Any CPU.Build.0 = Release|Any CPU {29CA7471-4E3E-4E75-8B33-001DDF682F01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {29CA7471-4E3E-4E75-8B33-001DDF682F01}.Debug|Any CPU.Build.0 = Debug|Any CPU {29CA7471-4E3E-4E75-8B33-001DDF682F01}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1119,10 +1085,6 @@ Global {F03A1CEA-FA44-4F30-BFC2-00BC2EAAB4E2}.Debug|Any CPU.Build.0 = Debug|Any CPU {F03A1CEA-FA44-4F30-BFC2-00BC2EAAB4E2}.Release|Any CPU.ActiveCfg = Release|Any CPU {F03A1CEA-FA44-4F30-BFC2-00BC2EAAB4E2}.Release|Any CPU.Build.0 = Release|Any CPU - {5AD34048-5DAA-4067-9C7D-09295617A057}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5AD34048-5DAA-4067-9C7D-09295617A057}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5AD34048-5DAA-4067-9C7D-09295617A057}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5AD34048-5DAA-4067-9C7D-09295617A057}.Release|Any CPU.Build.0 = Release|Any CPU {B9133C38-AC24-4E2F-B581-D124CF410CDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B9133C38-AC24-4E2F-B581-D124CF410CDF}.Debug|Any CPU.Build.0 = Debug|Any CPU {B9133C38-AC24-4E2F-B581-D124CF410CDF}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1131,10 +1093,6 @@ Global {8FDB3BF7-AD89-43F6-8DEB-C3E29B8801FE}.Debug|Any CPU.Build.0 = Debug|Any CPU {8FDB3BF7-AD89-43F6-8DEB-C3E29B8801FE}.Release|Any CPU.ActiveCfg = Release|Any CPU {8FDB3BF7-AD89-43F6-8DEB-C3E29B8801FE}.Release|Any CPU.Build.0 = Release|Any CPU - {F35E5CFC-569F-4D7D-A30F-DD8AE97FEC5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F35E5CFC-569F-4D7D-A30F-DD8AE97FEC5A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F35E5CFC-569F-4D7D-A30F-DD8AE97FEC5A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F35E5CFC-569F-4D7D-A30F-DD8AE97FEC5A}.Release|Any CPU.Build.0 = Release|Any CPU {ACFBA3FB-18CE-4655-9D14-1F1F5C3DFC30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ACFBA3FB-18CE-4655-9D14-1F1F5C3DFC30}.Debug|Any CPU.Build.0 = Debug|Any CPU {ACFBA3FB-18CE-4655-9D14-1F1F5C3DFC30}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1262,11 +1220,8 @@ Global {82ED4DD2-DEF8-40D5-9BF9-663AFD35B06D} = {447C8A77-E5F0-4538-8687-7383196D04EA} {6EABA98D-0B71-4ED7-A939-AFDA106D1151} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {468C3DCB-8C00-40E7-AE51-0738EAAB312A} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} - {2F5EE6D9-511B-4998-BD62-0B9F03E02432} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {86A3BB43-8FA2-4CC2-BAD0-A86C6C9D9585} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {7E0517E0-AE09-4E10-8469-308F065F2F43} = {447C8A77-E5F0-4538-8687-7383196D04EA} - {57E29185-6DAF-4A41-8CE0-3F07EE3E289A} = {447C8A77-E5F0-4538-8687-7383196D04EA} - {869F7453-7DDC-4915-B92C-7861466AB68B} = {447C8A77-E5F0-4538-8687-7383196D04EA} {8B1CB44B-BA40-4C78-9447-A7864126D7C3} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {8BB10746-8BAD-4317-8EE5-A36805DB93F6} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {EC71FBDD-A6BD-4B5D-92FE-E108FE12CE8B} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} @@ -1320,7 +1275,6 @@ Global {9467418B-4A9B-4093-9B31-01A9DEF5B372} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {CD5770BB-2E0C-4B3C-80E0-21B8CC43DBA9} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {29E42ADB-85F8-44AE-A9B0-078F84C1B866} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} - {0C498CF2-D052-4BF7-AD35-509A90F69707} = {447C8A77-E5F0-4538-8687-7383196D04EA} {E1963439-2BE5-4DB5-8438-2A9A792A1ADA} = {447C8A77-E5F0-4538-8687-7383196D04EA} {D1815C77-16D6-4F99-8814-69065CD89FB3} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5} = {447C8A77-E5F0-4538-8687-7383196D04EA} @@ -1356,7 +1310,6 @@ Global {8A22D962-016E-474A-8BB7-F831F0ABF3AC} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {E1A62D10-F2FB-4040-BD60-11A3934058DF} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {4EBDDB1B-D6C5-4FAE-B5A7-2171B18CDFA5} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} - {ABC27C10-C0FF-44CB-B4FF-A09C0B79F695} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {29CA7471-4E3E-4E75-8B33-001DDF682F01} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {37F89B0B-1C6B-426F-A5EE-676D1956D9E9} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {DEFE3DB2-EA4F-4F90-87FC-B25D64427BC5} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} @@ -1367,10 +1320,8 @@ Global {00D07595-993C-40FC-BD90-0DD6331414D3} = {447C8A77-E5F0-4538-8687-7383196D04EA} {A37BFEB5-7C57-4CDC-93B8-B5CE4BB9ACE1} = {447C8A77-E5F0-4538-8687-7383196D04EA} {F03A1CEA-FA44-4F30-BFC2-00BC2EAAB4E2} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} - {5AD34048-5DAA-4067-9C7D-09295617A057} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {B9133C38-AC24-4E2F-B581-D124CF410CDF} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {8FDB3BF7-AD89-43F6-8DEB-C3E29B8801FE} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} - {F35E5CFC-569F-4D7D-A30F-DD8AE97FEC5A} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {ACFBA3FB-18CE-4655-9D14-1F1F5C3DFC30} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {DADEA538-3CA1-4ADE-A7E6-EF77A0CE4401} = {447C8A77-E5F0-4538-8687-7383196D04EA} {863C18F9-2407-49F9-9ADC-F6229AF3B385} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Microsoft/AspNetCore/Authentication/OAuth/Claims/AbpClaimActionCollectionExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Microsoft/AspNetCore/Authentication/OAuth/Claims/AbpClaimActionCollectionExtensions.cs index f51706113a..2ad72955af 100644 --- a/framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Microsoft/AspNetCore/Authentication/OAuth/Claims/AbpClaimActionCollectionExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Microsoft/AspNetCore/Authentication/OAuth/Claims/AbpClaimActionCollectionExtensions.cs @@ -11,12 +11,14 @@ namespace Microsoft.AspNetCore.Authentication.OAuth.Claims { claimActions.MapJsonKey(AbpClaimTypes.UserName, "name"); claimActions.DeleteClaim("name"); + claimActions.RemoveDuplicate(AbpClaimTypes.UserName); } if (AbpClaimTypes.Email != "email") { claimActions.MapJsonKey(AbpClaimTypes.Email, "email"); claimActions.DeleteClaim("email"); + claimActions.RemoveDuplicate(AbpClaimTypes.Email); } if (AbpClaimTypes.EmailVerified != "email_verified") @@ -38,11 +40,18 @@ namespace Microsoft.AspNetCore.Authentication.OAuth.Claims { claimActions.MapJsonKeyMultiple(AbpClaimTypes.Role, "role"); } + + claimActions.RemoveDuplicate(AbpClaimTypes.Name); } public static void MapJsonKeyMultiple(this ClaimActionCollection claimActions, string claimType, string jsonKey) { claimActions.Add(new MultipleClaimAction(claimType, jsonKey)); } + + public static void RemoveDuplicate(this ClaimActionCollection claimActions, string claimType) + { + claimActions.Add(new RemoveDuplicateClaimAction(claimType)); + } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Volo/Abp/AspNetCore/Authentication/OAuth/Claims/MultipleClaimAction.cs b/framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Volo/Abp/AspNetCore/Authentication/OAuth/Claims/MultipleClaimAction.cs index 0c3b05090c..66ca78026d 100644 --- a/framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Volo/Abp/AspNetCore/Authentication/OAuth/Claims/MultipleClaimAction.cs +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Volo/Abp/AspNetCore/Authentication/OAuth/Claims/MultipleClaimAction.cs @@ -1,4 +1,5 @@ -using System.Security.Claims; +using System.Linq; +using System.Security.Claims; using System.Text.Json; using Microsoft.AspNetCore.Authentication.OAuth.Claims; @@ -24,15 +25,24 @@ namespace Volo.Abp.AspNetCore.Authentication.OAuth.Claims return; } + Claim claim; switch (prop.ValueKind) { case JsonValueKind.String: - identity.AddClaim(new Claim(ClaimType, prop.GetString(), ValueType, issuer)); + claim = new Claim(ClaimType, prop.GetString(), ValueType, issuer); + if (!identity.Claims.Any(c => c.Type == claim.Type && c.Value == claim.Value)) + { + identity.AddClaim(claim); + } break; case JsonValueKind.Array: foreach (var arramItem in prop.EnumerateArray()) { - identity.AddClaim(new Claim(ClaimType, arramItem.GetString(), ValueType, issuer)); + claim = new Claim(ClaimType, arramItem.GetString(), ValueType, issuer); + if (!identity.Claims.Any(c => c.Type == claim.Type && c.Value == claim.Value)) + { + identity.AddClaim(claim); + } } break; default: diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Volo/Abp/AspNetCore/Authentication/OAuth/Claims/RemoveDuplicateClaimAction.cs b/framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Volo/Abp/AspNetCore/Authentication/OAuth/Claims/RemoveDuplicateClaimAction.cs new file mode 100644 index 0000000000..ab3e4027c2 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Volo/Abp/AspNetCore/Authentication/OAuth/Claims/RemoveDuplicateClaimAction.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Text.Json; +using Microsoft.AspNetCore.Authentication.OAuth.Claims; + +namespace Volo.Abp.AspNetCore.Authentication.OAuth.Claims +{ + public class RemoveDuplicateClaimAction : ClaimAction + { + public RemoveDuplicateClaimAction(string claimType) + : base(claimType, ClaimValueTypes.String) + { + } + + /// + public override void Run(JsonElement userData, ClaimsIdentity identity, string issuer) + { + var claims = identity.Claims.Where(c => c.Type == ClaimType).ToArray(); + if (claims.Length < 2) + { + return; + } + + var previousValues = new List(); + foreach (var claim in claims) + { + if (claim.Value.IsIn(previousValues)) + { + identity.RemoveClaim(claim); + } + else + { + previousValues.Add(claim.Value); + } + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.BasicTheme/Volo.Abp.AspNetCore.Components.Server.BasicTheme.csproj b/framework/src/Volo.Abp.AspNetCore.Components.Server.BasicTheme/Volo.Abp.AspNetCore.Components.Server.BasicTheme.csproj deleted file mode 100644 index 73374aff94..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Components.Server.BasicTheme/Volo.Abp.AspNetCore.Components.Server.BasicTheme.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - net5.0 - - - - - - - - - - - - diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo.Abp.AspNetCore.Components.Server.csproj b/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo.Abp.AspNetCore.Components.Server.csproj index 050c438d11..86d870d2ca 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo.Abp.AspNetCore.Components.Server.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo.Abp.AspNetCore.Components.Server.csproj @@ -12,6 +12,8 @@ + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo/Abp/AspNetCore/Components/Server/AbpAspNetCoreComponentsServerModule.cs b/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo/Abp/AspNetCore/Components/Server/AbpAspNetCoreComponentsServerModule.cs index dfd3cafce2..58398b651a 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo/Abp/AspNetCore/Components/Server/AbpAspNetCoreComponentsServerModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo/Abp/AspNetCore/Components/Server/AbpAspNetCoreComponentsServerModule.cs @@ -5,8 +5,10 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; using Volo.Abp.AspNetCore.Auditing; using Volo.Abp.AspNetCore.Components.Web; +using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.SignalR; using Volo.Abp.AspNetCore.Uow; +using Volo.Abp.EventBus; using Volo.Abp.Http.Client; using Volo.Abp.Modularity; @@ -15,7 +17,9 @@ namespace Volo.Abp.AspNetCore.Components.Server [DependsOn( typeof(AbpHttpClientModule), typeof(AbpAspNetCoreComponentsWebModule), - typeof(AbpAspNetCoreSignalRModule) + typeof(AbpAspNetCoreSignalRModule), + typeof(AbpEventBusModule), + typeof(AbpAspNetCoreMvcContractsModule) )] public class AbpAspNetCoreComponentsServerModule : AbpModule { diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo/Abp/AspNetCore/Components/Server/Configuration/BlazorServerCurrentApplicationConfigurationCacheResetService.cs b/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo/Abp/AspNetCore/Components/Server/Configuration/BlazorServerCurrentApplicationConfigurationCacheResetService.cs new file mode 100644 index 0000000000..1e3cd857e5 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo/Abp/AspNetCore/Components/Server/Configuration/BlazorServerCurrentApplicationConfigurationCacheResetService.cs @@ -0,0 +1,29 @@ +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.Components.Web.Configuration; +using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus.Local; + +namespace Volo.Abp.AspNetCore.Components.Server.Configuration +{ + [Dependency(ReplaceServices = true)] + public class BlazorServerCurrentApplicationConfigurationCacheResetService : + ICurrentApplicationConfigurationCacheResetService, + ITransientDependency + { + private readonly ILocalEventBus _localEventBus; + + public BlazorServerCurrentApplicationConfigurationCacheResetService( + ILocalEventBus localEventBus) + { + _localEventBus = localEventBus; + } + + public async Task ResetAsync() + { + await _localEventBus.PublishAsync( + new CurrentApplicationConfigurationCacheResetEventData() + ); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo/Abp/AspNetCore/Components/Server/Extensibility/BlazorServerLookupApiRequestService.cs b/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo/Abp/AspNetCore/Components/Server/Extensibility/BlazorServerLookupApiRequestService.cs index b7e2de8364..952a2d6de4 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo/Abp/AspNetCore/Components/Server/Extensibility/BlazorServerLookupApiRequestService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo/Abp/AspNetCore/Components/Server/Extensibility/BlazorServerLookupApiRequestService.cs @@ -19,9 +19,7 @@ namespace Volo.Abp.AspNetCore.Components.Server.Extensibility { public IHttpClientFactory HttpClientFactory { get; } public IRemoteServiceHttpClientAuthenticator HttpClientAuthenticator { get; } - - public AbpRemoteServiceOptions RemoteServiceOptions { get; } - + public IRemoteServiceConfigurationProvider RemoteServiceConfigurationProvider { get; } public ICurrentTenant CurrentTenant { get; } public IHttpContextAccessor HttpContextAccessor { get; } public NavigationManager NavigationManager { get; } @@ -29,16 +27,16 @@ namespace Volo.Abp.AspNetCore.Components.Server.Extensibility public BlazorServerLookupApiRequestService(IHttpClientFactory httpClientFactory, IRemoteServiceHttpClientAuthenticator httpClientAuthenticator, ICurrentTenant currentTenant, - IOptions remoteServiceOptions, IHttpContextAccessor httpContextAccessor, - NavigationManager navigationManager) + NavigationManager navigationManager, + IRemoteServiceConfigurationProvider remoteServiceConfigurationProvider) { HttpClientFactory = httpClientFactory; HttpClientAuthenticator = httpClientAuthenticator; - RemoteServiceOptions = remoteServiceOptions.Value; CurrentTenant = currentTenant; HttpContextAccessor = httpContextAccessor; NavigationManager = navigationManager; + RemoteServiceConfigurationProvider = remoteServiceConfigurationProvider; } public async Task SendAsync(string url) @@ -53,7 +51,7 @@ namespace Volo.Abp.AspNetCore.Components.Server.Extensibility try { //Blazor tiered -- mode - var remoteServiceConfig = RemoteServiceOptions.RemoteServices.GetConfigurationOrDefault("Default"); + var remoteServiceConfig = await RemoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync("Default"); baseUrl = remoteServiceConfig.BaseUrl; client.BaseAddress = new Uri(baseUrl); AddHeaders(requestMessage); diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/NavMenu.razor.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/NavMenu.razor.cs deleted file mode 100644 index 302c3c9ec2..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Themes/Basic/NavMenu.razor.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Components; -using Volo.Abp.UI.Navigation; - -namespace Volo.Abp.AspNetCore.Components.Web.BasicTheme.Themes.Basic -{ - public partial class NavMenu - { - [Inject] - protected IMenuManager MenuManager { get; set; } - - protected ApplicationMenu Menu { get; set; } - - protected override async Task OnInitializedAsync() - { - Menu = await MenuManager.GetAsync(StandardMenus.Main); - } - } -} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Volo.Abp.AspNetCore.Components.Web.BasicTheme.csproj b/framework/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Volo.Abp.AspNetCore.Components.Web.BasicTheme.csproj deleted file mode 100644 index 4f3c306eab..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/Volo.Abp.AspNetCore.Components.Web.BasicTheme.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - net5.0 - - - - - - - diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/wwwroot/libs/abp/css/theme.css b/framework/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/wwwroot/libs/abp/css/theme.css deleted file mode 100644 index ac9aabb57f..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Components.Web.BasicTheme/wwwroot/libs/abp/css/theme.css +++ /dev/null @@ -1,110 +0,0 @@ - -#main-navbar-tools a.dropdown-toggle { - text-decoration: none; - color: #fff; -} - -.navbar .dropdown-submenu { - position: relative; -} -.navbar .dropdown-menu { - margin: 0; - padding: 0; -} - .navbar .dropdown-menu a { - font-size: .9em; - padding: 10px 15px; - display: block; - min-width: 210px; - text-align: left; - border-radius: 0.25rem; - min-height: 44px; - } -.navbar .dropdown-submenu a::after { - transform: rotate(-90deg); - position: absolute; - right: 16px; - top: 18px; -} -.navbar .dropdown-submenu .dropdown-menu { - top: 0; - left: 100%; -} - -.card-header .btn { - padding: 2px 6px; -} -.card-header h5 { - margin: 0; -} -.container > .card { - box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important; -} - -@media screen and (min-width: 768px) { - .navbar .dropdown:hover > .dropdown-menu { - display: block; - } - - .navbar .dropdown-submenu:hover > .dropdown-menu { - display: block; - } -} -.input-validation-error { - border-color: #dc3545; -} -.field-validation-error { - font-size: 0.8em; -} - -.dataTables_scrollBody { - min-height: 248px; -} - -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%; -} - -/* TEMP */ - -.navbar-dark .navbar-nav .nav-link { - color: #000 !important; -} - -.navbar-nav > .nav-item > .nav-link, -.navbar-nav > .nav-item > .dropdown > .nav-link { - color: #fff !important; -} - -.navbar-nav>.nav-item>div>button{ - color:#fff; -} - -.btn span.spinner-border { - margin-right: .5rem; -} - diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Configuration/ICurrentApplicationConfigurationCacheResetService.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Configuration/ICurrentApplicationConfigurationCacheResetService.cs new file mode 100644 index 0000000000..b1da30825d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Configuration/ICurrentApplicationConfigurationCacheResetService.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.AspNetCore.Components.Web.Configuration +{ + public interface ICurrentApplicationConfigurationCacheResetService + { + Task ResetAsync(); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Configuration/NullCurrentApplicationConfigurationCacheResetService.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Configuration/NullCurrentApplicationConfigurationCacheResetService.cs new file mode 100644 index 0000000000..6eeb0e9471 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Configuration/NullCurrentApplicationConfigurationCacheResetService.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.Web.Configuration +{ + public class NullCurrentApplicationConfigurationCacheResetService : ICurrentApplicationConfigurationCacheResetService, ISingletonDependency + { + public Task ResetAsync() + { + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/DefaultServerUrlProvider.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/DefaultServerUrlProvider.cs index 2d0473b4ea..629a19d9f5 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/DefaultServerUrlProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/DefaultServerUrlProvider.cs @@ -1,12 +1,13 @@ -using Volo.Abp.DependencyInjection; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; namespace Volo.Abp.AspNetCore.Components.Web { public class DefaultServerUrlProvider : IServerUrlProvider, ISingletonDependency { - public string GetBaseUrl(string remoteServiceName = null) + public Task GetBaseUrlAsync(string remoteServiceName = null) { - return "/"; + return Task.FromResult("/"); } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/IServerUrlProvider.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/IServerUrlProvider.cs index 808dc6c7d3..fffec2f606 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/IServerUrlProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/IServerUrlProvider.cs @@ -1,7 +1,9 @@ -namespace Volo.Abp.AspNetCore.Components.Web +using System.Threading.Tasks; + +namespace Volo.Abp.AspNetCore.Components.Web { public interface IServerUrlProvider { - string GetBaseUrl(string remoteServiceName = null); + Task GetBaseUrlAsync(string remoteServiceName = null); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.csproj b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.csproj deleted file mode 100644 index 9f456a72c2..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - net5.0 - Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme - - - - - - - - - diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/Extensibility/WebAssemblyLookupApiRequestService.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/Extensibility/WebAssemblyLookupApiRequestService.cs index be4a6f8bfb..594aa32bae 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/Extensibility/WebAssemblyLookupApiRequestService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/Extensibility/WebAssemblyLookupApiRequestService.cs @@ -17,20 +17,18 @@ namespace Volo.Abp.AspNetCore.Components.WebAssembly.Extensibility { public IHttpClientFactory HttpClientFactory { get; } public IRemoteServiceHttpClientAuthenticator HttpClientAuthenticator { get; } - - public AbpRemoteServiceOptions RemoteServiceOptions { get; } - + public IRemoteServiceConfigurationProvider RemoteServiceConfigurationProvider { get; } public ICurrentTenant CurrentTenant { get; } public WebAssemblyLookupApiRequestService(IHttpClientFactory httpClientFactory, IRemoteServiceHttpClientAuthenticator httpClientAuthenticator, ICurrentTenant currentTenant, - IOptions remoteServiceOptions) + IRemoteServiceConfigurationProvider remoteServiceConfigurationProvider) { HttpClientFactory = httpClientFactory; HttpClientAuthenticator = httpClientAuthenticator; - RemoteServiceOptions = remoteServiceOptions.Value; CurrentTenant = currentTenant; + RemoteServiceConfigurationProvider = remoteServiceConfigurationProvider; } public async Task SendAsync(string url) @@ -42,7 +40,7 @@ namespace Volo.Abp.AspNetCore.Components.WebAssembly.Extensibility var uri = new Uri(url, UriKind.RelativeOrAbsolute); if (!uri.IsAbsoluteUri) { - var remoteServiceConfig = RemoteServiceOptions.RemoteServices.GetConfigurationOrDefault("Default"); + var remoteServiceConfig = await RemoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync("Default"); client.BaseAddress = new Uri(remoteServiceConfig.BaseUrl); await HttpClientAuthenticator.Authenticate(new RemoteServiceHttpClientAuthenticateContext(client, requestMessage, new RemoteServiceConfiguration(remoteServiceConfig.BaseUrl), string.Empty)); } diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyServerUrlProvider.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyServerUrlProvider.cs index 865871b5a5..477dba5418 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyServerUrlProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyServerUrlProvider.cs @@ -1,5 +1,5 @@ using System; -using Microsoft.Extensions.Options; +using System.Threading.Tasks; using Volo.Abp.AspNetCore.Components.Web; using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Client; @@ -9,19 +9,21 @@ namespace Volo.Abp.AspNetCore.Components.WebAssembly [Dependency(ReplaceServices = true)] public class WebAssemblyServerUrlProvider : IServerUrlProvider, ITransientDependency { - public AbpRemoteServiceOptions Options { get; } + protected IRemoteServiceConfigurationProvider RemoteServiceConfigurationProvider { get; } public WebAssemblyServerUrlProvider( - IOptions options) + IRemoteServiceConfigurationProvider remoteServiceConfigurationProvider) { - Options = options.Value; + RemoteServiceConfigurationProvider = remoteServiceConfigurationProvider; } - public string GetBaseUrl(string remoteServiceName = null) + public async Task GetBaseUrlAsync(string remoteServiceName = null) { - return Options.RemoteServices.GetConfigurationOrDefault( + var remoteServiceConfiguration = await RemoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync( remoteServiceName ?? RemoteServiceConfigurationDictionary.DefaultName - ).BaseUrl.EnsureEndsWith('/'); + ); + + return remoteServiceConfiguration.BaseUrl.EnsureEndsWith('/'); } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpMultiTenancyCookieHelper.cs b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpMultiTenancyCookieHelper.cs new file mode 100644 index 0000000000..ca483930c9 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpMultiTenancyCookieHelper.cs @@ -0,0 +1,32 @@ +using System; +using Microsoft.AspNetCore.Http; + +namespace Volo.Abp.AspNetCore.MultiTenancy +{ + public static class AbpMultiTenancyCookieHelper + { + public static void SetTenantCookie( + HttpContext context, + Guid? tenantId, + string tenantKey) + { + if (tenantId != null) + { + context.Response.Cookies.Append( + tenantKey, + tenantId.ToString(), + new CookieOptions + { + Path = "/", + HttpOnly = false, + Expires = DateTimeOffset.Now.AddYears(10) + } + ); + } + else + { + context.Response.Cookies.Delete(tenantKey); + } + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/MultiTenancyMiddleware.cs b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/MultiTenancyMiddleware.cs index 361826672b..f5541e3b40 100644 --- a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/MultiTenancyMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/MultiTenancyMiddleware.cs @@ -1,9 +1,6 @@ using System; using System.Globalization; -using System.IO; -using System.Net; using System.Threading.Tasks; -using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Localization; using Microsoft.AspNetCore.RequestLocalization; @@ -21,14 +18,17 @@ namespace Volo.Abp.AspNetCore.MultiTenancy private readonly ITenantConfigurationProvider _tenantConfigurationProvider; private readonly ICurrentTenant _currentTenant; private readonly AbpAspNetCoreMultiTenancyOptions _options; + private readonly ITenantResolveResultAccessor _tenantResolveResultAccessor; public MultiTenancyMiddleware( ITenantConfigurationProvider tenantConfigurationProvider, ICurrentTenant currentTenant, - IOptions options) + IOptions options, + ITenantResolveResultAccessor tenantResolveResultAccessor) { _tenantConfigurationProvider = tenantConfigurationProvider; _currentTenant = currentTenant; + _tenantResolveResultAccessor = tenantResolveResultAccessor; _options = options.Value; } @@ -49,6 +49,12 @@ namespace Volo.Abp.AspNetCore.MultiTenancy { using (_currentTenant.Change(tenant?.Id, tenant?.Name)) { + if (_tenantResolveResultAccessor.Result != null && + _tenantResolveResultAccessor.Result.AppliedResolvers.Contains(QueryStringTenantResolveContributor.ContributorName)) + { + AbpMultiTenancyCookieHelper.SetTenantCookie(context, _currentTenant.Id, _options.TenantKey); + } + var requestCulture = await TryGetRequestCultureAsync(context); if (requestCulture != null) { diff --git a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/QueryStringTenantResolveContributor.cs b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/QueryStringTenantResolveContributor.cs index 7e9690077e..2edcac0eaf 100644 --- a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/QueryStringTenantResolveContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/QueryStringTenantResolveContributor.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Volo.Abp.MultiTenancy; @@ -12,9 +13,23 @@ namespace Volo.Abp.AspNetCore.MultiTenancy protected override Task GetTenantIdOrNameFromHttpContextOrNullAsync(ITenantResolveContext context, HttpContext httpContext) { - return Task.FromResult(httpContext.Request.QueryString.HasValue - ? httpContext.Request.Query[context.GetAbpAspNetCoreMultiTenancyOptions().TenantKey].ToString() - : null); + if (httpContext.Request.QueryString.HasValue) + { + var tenantKey = context.GetAbpAspNetCoreMultiTenancyOptions().TenantKey; + if (httpContext.Request.Query.ContainsKey(tenantKey)) + { + var tenantValue = httpContext.Request.Query[tenantKey].ToString(); + if (tenantValue.IsNullOrWhiteSpace()) + { + context.Handled = true; + return Task.FromResult(null); + } + + return Task.FromResult(tenantValue); + } + } + + return Task.FromResult(null); } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentUserDto.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentUserDto.cs index 5d1f6d8414..b1b97c4b91 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentUserDto.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentUserDto.cs @@ -11,6 +11,10 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations public Guid? TenantId { get; set; } + public Guid? ImpersonatorUserId { get; set; } + + public Guid? ImpersonatorTenantId { get; set; } + public string UserName { get; set; } public string Name { get; set; } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Table/AbpTableTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Table/AbpTableTagHelperService.cs index 86bfd43744..8f84a04bbe 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Table/AbpTableTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Table/AbpTableTagHelperService.cs @@ -9,6 +9,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Table { output.TagName = "table"; output.Attributes.AddClass("table"); + output.TagMode = TagMode.StartTagAndEndTag; SetResponsiveness(context, output); SetTheme(context, output); @@ -95,4 +96,4 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Table } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Pages/Abp/MultiTenancy/TenantSwitchModal.cshtml.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Pages/Abp/MultiTenancy/TenantSwitchModal.cshtml.cs index f47edab789..1edeadff16 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Pages/Abp/MultiTenancy/TenantSwitchModal.cshtml.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Pages/Abp/MultiTenancy/TenantSwitchModal.cshtml.cs @@ -41,11 +41,8 @@ namespace Pages.Abp.MultiTenancy public async Task OnPostAsync() { - if (Input.Name.IsNullOrEmpty()) - { - Response.Cookies.Delete(Options.TenantKey); - } - else + Guid? tenantId = null; + if (!Input.Name.IsNullOrEmpty()) { var tenant = await TenantStore.FindAsync(Input.Name); if (tenant == null) @@ -58,17 +55,10 @@ namespace Pages.Abp.MultiTenancy throw new UserFriendlyException(L["GivenTenantIsNotAvailable", Input.Name]); } - Response.Cookies.Append( - Options.TenantKey, - tenant.Id.ToString(), - new CookieOptions - { - Path = "/", - HttpOnly = false, - Expires = DateTimeOffset.Now.AddYears(10) - } - ); + tenantId = tenant.Id; } + + AbpMultiTenancyCookieHelper.SetTenantCookie(HttpContext, tenantId, Options.TenantKey); } public class TenantInfoModel diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/fi.json b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/fi.json index d6fb04eb4c..0547630188 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/fi.json +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/fi.json @@ -10,4 +10,4 @@ "SwitchTenant": "Vaihda vuokralainen", "NotSelected": "Ei valittu" } -} +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/it.json b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/it.json index e46b96fb20..edaf4fe608 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/it.json +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/it.json @@ -3,11 +3,11 @@ "texts": { "GivenTenantIsNotExist": "Il tenant specificato non esiste: {0}", "GivenTenantIsNotAvailable": "Il tenant specificato non è disponibile: {0}", - "Tenant": "Inquilino", - "Switch": "interruttore", + "Tenant": "Tenant", + "Switch": "Cambia", "Name": "Nome", "SwitchTenantHint": "Lascia vuoto il campo del nome per passare al lato host.", - "SwitchTenant": "Cambia inquilino", + "SwitchTenant": "Cambia tenant", "NotSelected": "Non selezionato" } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/sk.json b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/sk.json new file mode 100644 index 0000000000..e8d4134c2b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/sk.json @@ -0,0 +1,12 @@ +{ + "culture": "sk", + "texts": { + "GivenTenantIsNotAvailable": "Daný tenant nie je k dispozícii: {0}", + "Tenant": "Tenant", + "Switch": "prepnúť", + "Name": "Meno", + "SwitchTenantHint": "Ak sa chcete prihlásiť ako používateľ poskytovateľa, nechajte pole s menom prázdne.", + "SwitchTenant": "Prepnúť tenanta", + "NotSelected": "Nie je vybraný" + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/TuiEditor/TuiEditorScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/TuiEditor/TuiEditorScriptContributor.cs index a6342c6276..12ab7dce8b 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/TuiEditor/TuiEditorScriptContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/TuiEditor/TuiEditorScriptContributor.cs @@ -16,18 +16,6 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.TuiEditor { public override void ConfigureBundle(BundleConfigurationContext context) { - context.Files.AddIfNotContains("/libs/to-mark/to-mark.min.js"); - - if (context.FileProvider.GetFileInfo("/libs/tui-code-snippet/tui-code-snippet.min.js").Exists) - { - context.Files.AddIfNotContains("/libs/tui-code-snippet/tui-code-snippet.min.js"); - } - else - { - context.Files.AddIfNotContains("/libs/tui-code-snippet/tui-code-snippet.js"); - } - - context.Files.AddIfNotContains("/libs/squire-rte/squire.js"); context.Files.AddIfNotContains("/libs/tui-editor/toastui-editor.js"); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/MainNavbarMenuViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/MainNavbarMenuViewComponent.cs deleted file mode 100644 index 0369457165..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/MainNavbarMenuViewComponent.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.UI.Navigation; - -namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Themes.Basic.Components.Menu -{ - public class MainNavbarMenuViewComponent : AbpViewComponent - { - private readonly IMenuManager _menuManager; - - public MainNavbarMenuViewComponent(IMenuManager menuManager) - { - _menuManager = menuManager; - } - - public async Task InvokeAsync() - { - var menu = await _menuManager.GetAsync(StandardMenus.Main); - return View("~/Themes/Basic/Components/Menu/Default.cshtml", menu); - } - } -} 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 deleted file mode 100644 index 06b06c08dc..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml +++ /dev/null @@ -1,106 +0,0 @@ -@using Microsoft.Extensions.Localization -@using Microsoft.Extensions.Options -@using Volo.Abp.AspNetCore.MultiTenancy -@using Volo.Abp.AspNetCore.Mvc.UI.Components.LayoutHook -@using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy.Localization -@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Bundling -@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Themes.Basic.Components.MainNavbar -@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Themes.Basic.Components.PageAlerts -@using Volo.Abp.AspNetCore.Mvc.UI.Theming -@using Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetScripts -@using Volo.Abp.AspNetCore.Mvc.UI.Widgets.Components.WidgetStyles -@using Volo.Abp.MultiTenancy -@using Volo.Abp.Localization -@using Volo.Abp.Ui.Branding -@inject IBrandingProvider BrandingProvider -@inject IOptions MultiTenancyOptions -@inject ICurrentTenant CurrentTenant -@inject IStringLocalizer MultiTenancyStringLocalizer -@inject ITenantResolveResultAccessor TenantResolveResultAccessor - -@{ - Layout = null; - var containerClass = ViewBag.FluidLayout == true ? "container-fluid" : "container"; //TODO: Better and type-safe options - var rtl = CultureHelper.IsRtl ? "rtl" : string.Empty; -} - - - - - - @await Component.InvokeLayoutHookAsync(LayoutHooks.Head.First, StandardLayouts.Account) - - - - - - @(ViewBag.Title == null ? BrandingProvider.AppName : ViewBag.Title) - - @if (ViewBag.Description != null) - { - - } - - - @await RenderSectionAsync("styles", false) - - @await Component.InvokeAsync(typeof(WidgetStylesViewComponent)) - - @await Component.InvokeLayoutHookAsync(LayoutHooks.Head.Last, StandardLayouts.Account) - - - @await Component.InvokeLayoutHookAsync(LayoutHooks.Body.First, StandardLayouts.Account) - - @(await Component.InvokeAsync()) - -
- - - @if (MultiTenancyOptions.Value.IsEnabled && - (TenantResolveResultAccessor.Result?.AppliedResolvers?.Contains(CookieTenantResolveContributor.ContributorName) == true)) - { -
-
-
-
- @MultiTenancyStringLocalizer["Tenant"]
-
- @if (CurrentTenant.Id == null) - { - - @MultiTenancyStringLocalizer["NotSelected"] - - } - else - { - @(CurrentTenant.Name ?? CurrentTenant.Id.Value.ToString()) - } -
-
- -
-
-
- } - @(await Component.InvokeAsync()) - @await Component.InvokeLayoutHookAsync(LayoutHooks.PageContent.First, StandardLayouts.Account) - @RenderBody() - @await Component.InvokeLayoutHookAsync(LayoutHooks.PageContent.Last, StandardLayouts.Account) -
-
-
- - - - - - - @await RenderSectionAsync("scripts", false) - - @await Component.InvokeAsync(typeof(WidgetScriptsViewComponent)) - - @await Component.InvokeLayoutHookAsync(LayoutHooks.Body.Last, StandardLayouts.Account) - - diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.csproj deleted file mode 100644 index b421036ab0..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - net5.0 - true - Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic - Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic - $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; - false - false - false - true - Library - - - - - - - - - - - - - - - - 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 8023b59be1..6ae24f20dd 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 @@ -236,9 +236,11 @@ var abp = abp || {}; var $actionContainer = _createRowAction(aData, column.rowAction, tableInstance); hideEmptyColumn($actionContainer, tableInstance, colIndex); - var $actionButton = $(cells[colIndex]).find(".abp-action-button"); - if ($actionButton.length === 0) { - $(cells[colIndex]).empty().append($actionContainer); + if ($actionContainer) { + var $actionButton = $(cells[colIndex]).find(".abp-action-button"); + if ($actionButton.length === 0) { + $(cells[colIndex]).empty().append($actionContainer); + } } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs index 0c2e9345e6..8a83c93a85 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs @@ -157,10 +157,10 @@ namespace Volo.Abp.AspNetCore.Mvc context.Services.TryAddSingleton(); //Use DI to create controllers - context.Services.Replace(ServiceDescriptor.Transient()); + mvcBuilder.AddControllersAsServices(); //Use DI to create view components - context.Services.Replace(ServiceDescriptor.Singleton()); + mvcBuilder.AddViewComponentsAsServices(); //Use DI to create razor page context.Services.Replace(ServiceDescriptor.Singleton()); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpDataAnnotationAutoLocalizationMetadataDetailsProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpDataAnnotationAutoLocalizationMetadataDetailsProvider.cs index d9de135e7f..be088b0154 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpDataAnnotationAutoLocalizationMetadataDetailsProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpDataAnnotationAutoLocalizationMetadataDetailsProvider.cs @@ -54,7 +54,29 @@ namespace Volo.Abp.AspNetCore.Mvc displayMetadata.DisplayName = () => { - var localizedString = localizer[PropertyLocalizationKeyPrefix + context.Key.Name]; + /* + * DisplayName:ClassName:PropertyName + * DisplayName:PropertyName + * ClassName:PropertyName + * PropertyName + */ + + LocalizedString localizedString = null; + + if (context.Key.ContainerType != null) + { + localizedString = localizer[PropertyLocalizationKeyPrefix + context.Key.ContainerType.Name + ":" + context.Key.Name]; + } + + if (localizedString == null || localizedString.ResourceNotFound) + { + localizedString = localizer[PropertyLocalizationKeyPrefix + context.Key.Name]; + } + + if (localizedString.ResourceNotFound && context.Key.ContainerType != null) + { + localizedString = localizer[context.Key.ContainerType.Name + ":" + context.Key.Name]; + } if (localizedString.ResourceNotFound) { @@ -65,4 +87,4 @@ namespace Volo.Abp.AspNetCore.Mvc }; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs index eab7d968a9..c60fdeac93 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs @@ -127,6 +127,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations IsAuthenticated = _currentUser.IsAuthenticated, Id = _currentUser.Id, TenantId = _currentUser.TenantId, + ImpersonatorUserId = _currentUser.FindImpersonatorUserId(), + ImpersonatorTenantId = _currentUser.FindImpersonatorTenantId(), UserName = _currentUser.UserName, SurName = _currentUser.SurName, Name = _currentUser.Name, 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 ff7f4913a8..ca1ebef793 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 @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using System.Text; using JetBrains.Annotations; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ApiExplorer; @@ -102,6 +103,16 @@ namespace Volo.Abp.AspNetCore.Mvc Logger.LogDebug($"ActionApiDescriptionModel.Create: {controllerModel.ControllerName}.{uniqueMethodName}"); + bool? allowAnonymous = null; + if (apiDescription.ActionDescriptor.EndpointMetadata.Any(x => x is IAllowAnonymous)) + { + allowAnonymous = true; + } + else if (apiDescription.ActionDescriptor.EndpointMetadata.Any(x => x is IAuthorizeData)) + { + allowAnonymous = false; + } + var actionModel = controllerModel.AddAction( uniqueMethodName, ActionApiDescriptionModel.Create( @@ -109,7 +120,8 @@ namespace Volo.Abp.AspNetCore.Mvc method, apiDescription.RelativePath, apiDescription.HttpMethod, - GetSupportedVersions(controllerType, method, setting) + GetSupportedVersions(controllerType, method, setting), + allowAnonymous ) ); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinder.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinder.cs index e4f3e46a77..2b1fc8c125 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinder.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinder.cs @@ -108,13 +108,13 @@ namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters if (file.Name.Equals(modelName, StringComparison.OrdinalIgnoreCase)) { - postedFiles.Add(new RemoteStreamContent(file.OpenReadStream(), file.ContentType, file.Length)); + postedFiles.Add(new RemoteStreamContent(file.OpenReadStream(), file.FileName, file.ContentType, file.Length)); } } } - else if (bindingContext.IsTopLevelObject) - { - postedFiles.Add(new RemoteStreamContent(request.Body, request.ContentType, request.ContentLength)); + else if (bindingContext.IsTopLevelObject) + { + postedFiles.Add(new RemoteStreamContent(request.Body, null, request.ContentType, request.ContentLength)); } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentOutputFormatter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentOutputFormatter.cs index 0edc91d30a..26dd0b0999 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentOutputFormatter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentOutputFormatter.cs @@ -27,6 +27,13 @@ namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters { context.HttpContext.Response.ContentType = remoteStream.ContentType; + if (!remoteStream.FileName.IsNullOrWhiteSpace()) + { + var contentDisposition = new ContentDispositionHeaderValue("attachment"); + contentDisposition.SetHttpFileName(remoteStream.FileName); + context.HttpContext.Response.Headers[HeaderNames.ContentDisposition] = contentDisposition.ToString(); + } + using (var stream = remoteStream.GetStream()) { await stream.CopyToAsync(context.HttpContext.Response.Body); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpAspNetCoreMvcQueryStringCultureReplacement.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpAspNetCoreMvcQueryStringCultureReplacement.cs new file mode 100644 index 0000000000..900d1c5de7 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpAspNetCoreMvcQueryStringCultureReplacement.cs @@ -0,0 +1,33 @@ +using System; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Mvc.Localization +{ + public class AbpAspNetCoreMvcQueryStringCultureReplacement : IQueryStringCultureReplacement, ITransientDependency + { + public virtual Task ReplaceAsync(QueryStringCultureReplacementContext context) + { + if (!string.IsNullOrWhiteSpace(context.ReturnUrl)) + { + if (context.ReturnUrl.Contains("culture=", StringComparison.OrdinalIgnoreCase) && + context.ReturnUrl.Contains("ui-Culture=", StringComparison.OrdinalIgnoreCase)) + { + context.ReturnUrl = Regex.Replace( + context.ReturnUrl, + "culture=[A-Za-z-]+?&", + $"culture={context.RequestCulture.Culture}&", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + context.ReturnUrl = Regex.Replace( + context.ReturnUrl, "ui-culture=[A-Za-z-]+?$", + $"ui-culture={context.RequestCulture.UICulture}", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + } + } + + return Task.CompletedTask; + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpLanguagesController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpLanguagesController.cs index 6b73aa26da..b0fa63e4f5 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpLanguagesController.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpLanguagesController.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Localization; using Microsoft.AspNetCore.Mvc; using System; +using System.Threading.Tasks; using Microsoft.AspNetCore.RequestLocalization; using Volo.Abp.Localization; @@ -12,8 +13,15 @@ namespace Volo.Abp.AspNetCore.Mvc.Localization [ApiExplorerSettings(IgnoreApi = true)] public class AbpLanguagesController : AbpController { + protected IQueryStringCultureReplacement QueryStringCultureReplacement { get; } + + public AbpLanguagesController(IQueryStringCultureReplacement queryStringCultureReplacement) + { + QueryStringCultureReplacement = queryStringCultureReplacement; + } + [HttpGet] - public IActionResult Switch(string culture, string uiCulture = "", string returnUrl = "") + public virtual async Task Switch(string culture, string uiCulture = "", string returnUrl = "") { if (!CultureHelper.IsValidCultureCode(culture)) { @@ -25,15 +33,18 @@ namespace Volo.Abp.AspNetCore.Mvc.Localization new RequestCulture(culture, uiCulture) ); - if (!string.IsNullOrWhiteSpace(returnUrl)) + var context = new QueryStringCultureReplacementContext(HttpContext, new RequestCulture(culture, uiCulture), returnUrl); + await QueryStringCultureReplacement.ReplaceAsync(context); + + if (!string.IsNullOrWhiteSpace(context.ReturnUrl)) { - return Redirect(GetRedirectUrl(returnUrl)); + return Redirect(GetRedirectUrl(context.ReturnUrl)); } return Redirect("~/"); } - private string GetRedirectUrl(string returnUrl) + protected virtual string GetRedirectUrl(string returnUrl) { if (returnUrl.IsNullOrEmpty()) { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpMvcAttributeValidationResultProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpMvcAttributeValidationResultProvider.cs new file mode 100644 index 0000000000..c6a280cca3 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpMvcAttributeValidationResultProvider.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Options; +using Volo.Abp.AspNetCore.Mvc.Validation; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Validation; + +namespace Volo.Abp.AspNetCore.Mvc.Localization +{ + [Dependency(ReplaceServices = true)] + public class AbpMvcAttributeValidationResultProvider : DefaultAttributeValidationResultProvider + { + private readonly AbpMvcDataAnnotationsLocalizationOptions _abpMvcDataAnnotationsLocalizationOptions; + private readonly IStringLocalizerFactory _stringLocalizerFactory; + + public AbpMvcAttributeValidationResultProvider( + IOptions abpMvcDataAnnotationsLocalizationOptions, + IStringLocalizerFactory stringLocalizerFactory) + { + _abpMvcDataAnnotationsLocalizationOptions = abpMvcDataAnnotationsLocalizationOptions.Value; + _stringLocalizerFactory = stringLocalizerFactory; + } + + public override ValidationResult GetOrDefault(ValidationAttribute validationAttribute, object validatingObject, ValidationContext validationContext) + { + var resourceSource = _abpMvcDataAnnotationsLocalizationOptions.AssemblyResources.GetOrDefault(validationContext.ObjectType.Assembly); + if (resourceSource == null) + { + return base.GetOrDefault(validationAttribute, validatingObject, validationContext); + } + + if (validationAttribute.ErrorMessage == null) + { + ValidationAttributeHelper.SetDefaultErrorMessage(validationAttribute); + } + + if (validationAttribute.ErrorMessage != null) + { + validationAttribute.ErrorMessage = _stringLocalizerFactory.Create(resourceSource)[validationAttribute.ErrorMessage]; + } + + return base.GetOrDefault(validationAttribute, validatingObject, validationContext); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/IQueryStringCultureReplacement.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/IQueryStringCultureReplacement.cs new file mode 100644 index 0000000000..7cd8113060 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/IQueryStringCultureReplacement.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.AspNetCore.Mvc.Localization +{ + public interface IQueryStringCultureReplacement + { + Task ReplaceAsync(QueryStringCultureReplacementContext context); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/QueryStringCultureReplacementContext.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/QueryStringCultureReplacementContext.cs new file mode 100644 index 0000000000..11437a91ce --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/QueryStringCultureReplacementContext.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Localization; + +namespace Volo.Abp.AspNetCore.Mvc.Localization +{ + public class QueryStringCultureReplacementContext + { + public HttpContext HttpContext { get; } + + public RequestCulture RequestCulture { get; } + + public string ReturnUrl { get; set; } + + public QueryStringCultureReplacementContext(HttpContext httpContext, RequestCulture requestCulture, string returnUrl) + { + HttpContext = httpContext; + RequestCulture = requestCulture; + ReturnUrl = returnUrl; + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.SignalR/Volo/Abp/AspNetCore/SignalR/AbpAspNetCoreSignalRModule.cs b/framework/src/Volo.Abp.AspNetCore.SignalR/Volo/Abp/AspNetCore/SignalR/AbpAspNetCoreSignalRModule.cs index 6b559f4d3e..23fece372f 100644 --- a/framework/src/Volo.Abp.AspNetCore.SignalR/Volo/Abp/AspNetCore/SignalR/AbpAspNetCoreSignalRModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.SignalR/Volo/Abp/AspNetCore/SignalR/AbpAspNetCoreSignalRModule.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Volo.Abp.AspNetCore.Auditing; using Volo.Abp.DependencyInjection; using Volo.Abp.Modularity; @@ -30,6 +31,7 @@ namespace Volo.Abp.AspNetCore.SignalR public override void ConfigureServices(ServiceConfigurationContext context) { + var routePatterns = new List {"/signalr-hubs"}; var signalRServerBuilder = context.Services.AddSignalR(); context.Services.ExecutePreConfiguredActions(signalRServerBuilder); @@ -45,6 +47,8 @@ namespace Volo.Abp.AspNetCore.SignalR foreach (var hubConfig in signalROptions.Hubs) { + routePatterns.AddIfNotContains(hubConfig.RoutePattern); + MapHubType( hubConfig.HubType, endpointContext.Endpoints, @@ -60,6 +64,14 @@ namespace Volo.Abp.AspNetCore.SignalR } }); }); + + Configure(options => + { + foreach (var routePattern in routePatterns) + { + options.IgnoredUrls.AddIfNotContains(x => routePattern.StartsWith(x), () => routePattern); + } + }); } private void AutoAddHubTypes(IServiceCollection services) diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/DefaultHttpExceptionStatusCodeFinder.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/DefaultHttpExceptionStatusCodeFinder.cs index 2e1ef471a2..141fa3a7f4 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/DefaultHttpExceptionStatusCodeFinder.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/DefaultHttpExceptionStatusCodeFinder.cs @@ -3,6 +3,7 @@ using System.Net; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; using Volo.Abp.Authorization; +using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Entities; using Volo.Abp.ExceptionHandling; @@ -55,6 +56,11 @@ namespace Volo.Abp.AspNetCore.ExceptionHandling { return HttpStatusCode.NotFound; } + + if (exception is AbpDbConcurrencyException) + { + return HttpStatusCode.Conflict; + } if (exception is NotImplementedException) { diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Localization/sk.json b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Localization/sk.json new file mode 100644 index 0000000000..12f4a2a942 --- /dev/null +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Localization/sk.json @@ -0,0 +1,10 @@ +{ + "culture": "sk", + "texts": { + "Volo.Authorization:010001": "Autorizácia zlyhala! Daná politika nebola udelená.", + "Volo.Authorization:010002": "Autorizácia zlyhala! Daná politika nebola udelená: {PolicyName}", + "Volo.Authorization:010003": "Autorizácia zlyhala! Politika nebola udelená danému zdroju: {ResourceName}", + "Volo.Authorization:010004": "Autorizácia zlyhala! Daná požiadavka nebola udelená danému zdroju: {ResourceName}", + "Volo.Authorization:010005": "Autorizácia zlyhala! Dané požiadavky neboli udelené danému zdroju: {ResourceName}" + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/RolePermissionValueProvider.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/RolePermissionValueProvider.cs index 25dffced76..87878fc5d6 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/RolePermissionValueProvider.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/RolePermissionValueProvider.cs @@ -26,7 +26,7 @@ namespace Volo.Abp.Authorization.Permissions return PermissionGrantResult.Undefined; } - foreach (var role in roles) + foreach (var role in roles.Distinct()) { if (await PermissionStore.IsGrantedAsync(context.Permission.Name, Name, role)) { @@ -50,7 +50,7 @@ namespace Volo.Abp.Authorization.Permissions return result; } - foreach (var role in roles) + foreach (var role in roles.Distinct()) { var multipleResult = await PermissionStore.IsGrantedAsync(permissionNames.ToArray(), Name, role); diff --git a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs index 19ace62aca..ea7e2236ef 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs +++ b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs @@ -494,17 +494,17 @@ namespace Volo.Abp.BlazoriseUI await HandleErrorAsync(ex); } } - + protected virtual Task OnDeletingEntityAsync() { return Task.CompletedTask; } - + protected virtual async Task OnDeletedEntityAsync() { await GetEntitiesAsync(); await InvokeAsync(StateHasChanged); - } + } protected virtual string GetDeleteConfirmationMessage(TListViewModel entity) { @@ -596,7 +596,7 @@ namespace Volo.Abp.BlazoriseUI if (propertyInfo.Type.IsEnum) { column.ValueConverter = (val) => - EnumHelper.GetLocalizedMemberName(propertyInfo.Type, val, StringLocalizerFactory); + EnumHelper.GetLocalizedMemberName(propertyInfo.Type, val.As().ExtraProperties[propertyInfo.Name], StringLocalizerFactory); } yield return column; diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/AbpExtensibleDataGrid.razor b/framework/src/Volo.Abp.BlazoriseUI/Components/AbpExtensibleDataGrid.razor index e0b867492e..459e47f442 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Components/AbpExtensibleDataGrid.razor +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/AbpExtensibleDataGrid.razor @@ -10,6 +10,20 @@ ShowPager="@ShowPager" CurrentPage="@CurrentPage" PageSize="@PageSize"> + + + + + + + + + + + No data available + + + @if (Columns != null) { diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor index b8d4a9b715..f8aad7b5cd 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor @@ -4,26 +4,29 @@ @using Volo.Abp.Localization @using Volo.Abp.Data -@foreach (var propertyInfo in ObjectExtensionManager.Instance.GetProperties()) -{ - if (!propertyInfo.Name.EndsWith("_Text")) +@{ + Entity.SetDefaultsForExtraProperties(); + + foreach (var propertyInfo in ObjectExtensionManager.Instance.GetProperties()) { - if (propertyInfo.Type.IsEnum) - { - - } - else if (!propertyInfo.Lookup.Url.IsNullOrEmpty()) + if (!propertyInfo.Name.EndsWith("_Text")) { - - } - else - { - var inputType = propertyInfo.GetInputType(); - - __builder.OpenComponent(0, inputType.MakeGenericType(new[] { typeof(TEntityType), typeof(TResourceType) })); - __builder.AddAttribute(1, "PropertyInfo", propertyInfo); - __builder.AddAttribute(2, "Entity", Entity); - __builder.CloseComponent(); + if (propertyInfo.Type.IsEnum) + { + + } + else if (!propertyInfo.Lookup.Url.IsNullOrEmpty()) + { + + } + else + { + var inputType = propertyInfo.GetInputType(); + __builder.OpenComponent(0, inputType.MakeGenericType(new[] { typeof(TEntityType), typeof(TResourceType) })); + __builder.AddAttribute(1, "PropertyInfo", propertyInfo); + __builder.AddAttribute(2, "Entity", Entity); + __builder.CloseComponent(); + } } } } diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/RadarSpinner.razor b/framework/src/Volo.Abp.BlazoriseUI/Components/RadarSpinner.razor new file mode 100644 index 0000000000..0fd98cb98f --- /dev/null +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/RadarSpinner.razor @@ -0,0 +1,27 @@ +
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+
\ No newline at end of file diff --git a/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj b/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj index 2e4c93e8df..f8c8658e5f 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj +++ b/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj @@ -14,10 +14,10 @@ - - - - + + + + diff --git a/framework/src/Volo.Abp.Castle.Core/Volo.Abp.Castle.Core.csproj b/framework/src/Volo.Abp.Castle.Core/Volo.Abp.Castle.Core.csproj index 7e742c0467..036144e8c6 100644 --- a/framework/src/Volo.Abp.Castle.Core/Volo.Abp.Castle.Core.csproj +++ b/framework/src/Volo.Abp.Castle.Core/Volo.Abp.Castle.Core.csproj @@ -16,7 +16,7 @@ - + diff --git a/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj b/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj index 1b7603194a..4f28bad500 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj +++ b/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj @@ -13,6 +13,7 @@ + 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 b3de9d71ff..08da2e3d00 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 @@ -2,9 +2,11 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Cli.Commands; using Volo.Abp.Cli.Http; +using Volo.Abp.Cli.LIbs; using Volo.Abp.Domain; using Volo.Abp.IdentityModel; using Volo.Abp.Json; +using Volo.Abp.Json.SystemTextJson; using Volo.Abp.Minify; using Volo.Abp.Modularity; @@ -25,6 +27,11 @@ namespace Volo.Abp.Cli Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + Configure(options => + { + options.UnsupportedTypes.Add(typeof(ResourceMapping)); + }); + Configure(options => { //TODO: Define constants like done for GenerateProxyCommand.Name. @@ -49,6 +56,7 @@ namespace Volo.Abp.Cli options.Commands["build"] = typeof(BuildCommand); options.Commands["bundle"] = typeof(BundleCommand); options.Commands["create-migration-and-run-migrator"] = typeof(CreateMigrationAndRunMigratorCommand); + options.Commands["install-libs"] = typeof(InstallLibsCommand); }); } } 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 e276da7b50..c1abef5894 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 @@ -42,7 +42,7 @@ namespace Volo.Abp.Cli.Commands var newProTemplate = !string.IsNullOrEmpty(template) && template == ModuleProTemplate.TemplateName; var withSourceCode = newTemplate || newProTemplate || commandLineArgs.Options.ContainsKey(Options.SourceCode.Long); var addSourceCodeToSolutionFile = withSourceCode && commandLineArgs.Options.ContainsKey("add-to-solution-file"); - var skipDbMigrations = newTemplate || newProTemplate || Convert.ToBoolean(commandLineArgs.Options.GetOrNull(Options.DbMigrations.Skip) ?? "false"); + var skipDbMigrations = newTemplate || newProTemplate || commandLineArgs.Options.ContainsKey(Options.DbMigrations.Skip); var solutionFile = GetSolutionFile(commandLineArgs); var version = commandLineArgs.Options.GetOrNull(Options.Version.Short, Options.Version.Long); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateProxyCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateProxyCommand.cs index 4f4aec853c..b56dd19cb0 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateProxyCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateProxyCommand.cs @@ -12,5 +12,10 @@ namespace Volo.Abp.Cli.Commands : base(cliService) { } + + public override string GetShortDescription() + { + return "Generates Angular service proxies and DTOs to consume HTTP APIs."; + } } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/InstallLibsCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/InstallLibsCommand.cs new file mode 100644 index 0000000000..dc3e67fe29 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/InstallLibsCommand.cs @@ -0,0 +1,78 @@ +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.Cli.Args; +using Volo.Abp.Cli.LIbs; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Commands +{ + public class InstallLibsCommand : IConsoleCommand, ITransientDependency + { + public ILogger Logger { get; set; } + + protected IInstallLibsService InstallLibsService { get; } + + public InstallLibsCommand(IInstallLibsService installLibsService) + { + InstallLibsService = installLibsService; + Logger = NullLogger.Instance; + } + + public async Task ExecuteAsync(CommandLineArgs commandLineArgs) + { + var workingDirectoryArg = commandLineArgs.Options.GetOrNull( + Options.WorkingDirectory.Short, + Options.WorkingDirectory.Long + ); + + var workingDirectory = workingDirectoryArg ?? Directory.GetCurrentDirectory(); + + if (!Directory.Exists(workingDirectory)) + { + throw new CliUsageException( + "Specified directory does not exist." + + Environment.NewLine + Environment.NewLine + + GetUsageInfo() + ); + } + + await InstallLibsService.InstallLibsAsync(workingDirectory); + } + + public string GetUsageInfo() + { + var sb = new StringBuilder(); + + sb.AppendLine(""); + sb.AppendLine("Usage:"); + sb.AppendLine(""); + sb.AppendLine(" abp install-libs"); + sb.AppendLine(""); + sb.AppendLine("Options:"); + sb.AppendLine(""); + sb.AppendLine("-wd|--working-directory (default: empty)"); + 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 "Install NPM Packages for MVC / Razor Pages and Blazor Server UI types."; + } + + public static class Options + { + public static class WorkingDirectory + { + public const string Short = "wd"; + public const string Long = "working-directory"; + } + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProxyCommandBase.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProxyCommandBase.cs index 0ecd849ad1..1745db2fd2 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProxyCommandBase.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProxyCommandBase.cs @@ -140,10 +140,7 @@ namespace Volo.Abp.Cli.Commands return sb.ToString(); } - public string GetShortDescription() - { - return "Generates Angular service proxies and DTOs to consume HTTP APIs."; - } + public abstract string GetShortDescription(); public static class Options { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/RemoveProxyCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/RemoveProxyCommand.cs index 85bbc3e15f..7c55c0cf71 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/RemoveProxyCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/RemoveProxyCommand.cs @@ -12,5 +12,10 @@ : base(cliService) { } + + public override string GetShortDescription() + { + return "Remove Angular service proxies and DTOs to consume HTTP APIs."; + } } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/FileMatchResult.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/FileMatchResult.cs new file mode 100644 index 0000000000..cbaf9c52da --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/FileMatchResult.cs @@ -0,0 +1,15 @@ +namespace Volo.Abp.Cli.LIbs +{ + public class FileMatchResult + { + public string Path { get; } + + public string Stem { get; } + + public FileMatchResult(string path, string stem) + { + Path = path; + Stem = stem; + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/IInstallLibsService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/IInstallLibsService.cs new file mode 100644 index 0000000000..b0b05f35da --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/IInstallLibsService.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Cli.LIbs +{ + public interface IInstallLibsService + { + Task InstallLibsAsync(string directory); + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/InstallLibsService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/InstallLibsService.cs new file mode 100644 index 0000000000..5379c62aed --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/InstallLibsService.cs @@ -0,0 +1,230 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.FileSystemGlobbing; +using Microsoft.Extensions.FileSystemGlobbing.Abstractions; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using NuGet.Versioning; +using Volo.Abp.Cli.Utils; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Json; + +namespace Volo.Abp.Cli.LIbs +{ + public class InstallLibsService : IInstallLibsService, ITransientDependency + { + public const string LibsDirectory = "./wwwroot/libs"; + + public ILogger Logger { get; set; } + + private readonly IJsonSerializer _jsonSerializer; + + public InstallLibsService(IJsonSerializer jsonSerializer) + { + _jsonSerializer = jsonSerializer; + Logger = NullLogger.Instance; + } + + public async Task InstallLibsAsync(string directory) + { + var projectFiles = Directory.GetFiles(directory, "*.csproj"); + if (!projectFiles.Any()) + { + Logger.LogError("No project file found in the directory."); + return; + } + + if (!await CanInstallLibs(directory)) + { + Logger.LogWarning( + "abp install-libs command is available for MVC, Razor Page, and Blazor-Server UI types"); + return; + } + + if (!IsNpmInstalled()) + { + Logger.LogWarning("NPM is not installed, visit https://nodejs.org/en/download/ and install NPM"); + return; + } + + if (IsYarnAvailable()) + { + RunYarn(directory); + } + else + { + RunNpmInstall(directory); + } + + await CleanAndCopyResources(directory); + } + + private async Task CleanAndCopyResources(string fileDirectory) + { + var mappingFiles = Directory.GetFiles(fileDirectory, "abp.resourcemapping.js", SearchOption.AllDirectories); + var resourceMapping = new ResourceMapping + { + Clean = new List {LibsDirectory} + }; + + foreach (var mappingFile in mappingFiles) + { + using (var reader = File.OpenText(mappingFile)) + { + var mappingFileContent = await reader.ReadToEndAsync(); + + var mapping = _jsonSerializer.Deserialize(mappingFileContent + .Replace("module.exports", string.Empty) + .Replace("=", string.Empty).Trim().TrimEnd(';')); + + mapping.ReplaceAliases(); + + mapping.Clean.ForEach(c => resourceMapping.Clean.AddIfNotContains(c)); + mapping.Aliases.ToList().ForEach(x => + { + resourceMapping.Aliases.AddIfNotContains(new KeyValuePair(x.Key, x.Value)); + }); + mapping.Mappings.ToList().ForEach(x => + { + resourceMapping.Mappings.AddIfNotContains(new KeyValuePair(x.Key, x.Value)); + }); + } + } + + EnsureLibsFolderExists(fileDirectory, LibsDirectory); + + CleanDirsAndFiles(fileDirectory, resourceMapping); + CopyResourcesToLibs(fileDirectory, resourceMapping); + } + + private void EnsureLibsFolderExists(string fileDirectory, string libsDirectory) + { + Directory.CreateDirectory(Path.Combine(fileDirectory, libsDirectory)); + } + + private void CopyResourcesToLibs(string fileDirectory, ResourceMapping resourceMapping) + { + foreach (var mapping in resourceMapping.Mappings) + { + var destPath = Path.Combine(fileDirectory, mapping.Value); + var files = FindFiles(fileDirectory, mapping.Key); + + foreach (var file in files) + { + var destFilePath = Path.Combine(destPath, file.Stem); + if (File.Exists(destFilePath)) + { + continue; + } + + Directory.CreateDirectory(Path.GetDirectoryName(destFilePath)); + File.Copy(file.Path, destFilePath); + + } + } + } + + private async Task CanInstallLibs(string fileDirectory) + { + var projectFiles = Directory.GetFiles(fileDirectory, "*.csproj"); + + using (var reader = File.OpenText(projectFiles[0])) + { + var projectFileContent = await reader.ReadToEndAsync(); + + return projectFileContent.Contains("Microsoft.NET.Sdk.Web"); + } + } + + private void CleanDirsAndFiles(string directory, ResourceMapping resourceMapping) + { + var files = FindFiles(directory, resourceMapping.Clean.ToArray()); + + foreach (var file in files) + { + if (File.Exists(file.Path)) + { + File.Delete(file.Path); + } + } + + foreach (var directoryInfo in Directory.GetDirectories(Path.Combine(directory, resourceMapping.Clean.First()),"*", SearchOption.AllDirectories).Reverse()) + { + if (!Directory.EnumerateFileSystemEntries(directoryInfo).Any()) + { + Directory.Delete(directoryInfo); + } + } + } + + private List FindFiles(string directory, params string[] patterns) + { + var matcher = new Matcher(); + + foreach (var pattern in patterns) + { + if (pattern.StartsWith("!")) + { + matcher.AddExclude(NormalizeGlob(pattern).TrimStart('!')); + } + else + { + matcher.AddInclude(NormalizeGlob(pattern)); + } + } + + var result = matcher.Execute(new DirectoryInfoWrapper(new DirectoryInfo(directory))); + + return result.Files.Select(x => new FileMatchResult(Path.Combine(directory, x.Path), x.Stem)).ToList(); + } + + private string NormalizeGlob(string pattern) + { + pattern = pattern.Replace("//", "/"); + + if (!Path.HasExtension(pattern) && !pattern.EndsWith("*")) + { + return pattern.EnsureEndsWith('/') + "**"; + } + + return pattern; + } + + private void RunNpmInstall(string directory) + { + Logger.LogInformation($"Running npm install on {directory}"); + CmdHelper.RunCmd($"cd {directory} && npm install"); + } + + private void RunYarn(string directory) + { + Logger.LogInformation($"Running Yarn on {directory}"); + CmdHelper.RunCmd($"cd {directory} && yarn"); + } + + private bool IsNpmInstalled() + { + var output = CmdHelper.RunCmdAndGetOutput("npm -v").Trim(); + return SemanticVersion.TryParse(output, out _); + } + + private bool IsYarnAvailable() + { + var output = CmdHelper.RunCmdAndGetOutput("npm list yarn -g").Trim(); + if (output.Contains("empty")) + { + return false; + } + + if (!SemanticVersion.TryParse(output.Substring(output.IndexOf('@') + 1), out var version)) + { + return false; + } + + return version > SemanticVersion.Parse("1.20.0"); + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/ResourceMapping.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/ResourceMapping.cs new file mode 100644 index 0000000000..ed46b833ad --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/ResourceMapping.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Volo.Abp.Cli.LIbs +{ + public class ResourceMapping + { + public Dictionary Aliases { get; set; } + + public List Clean { get; set; } + + public Dictionary Mappings { get; set; } + + public ResourceMapping() + { + Aliases = new Dictionary + { + {"@node_modules", "./node_modules"}, + {"@libs", "./wwwroot/libs"}, + }; + Clean = new List(); + Mappings = new Dictionary(); + } + + public void ReplaceAliases() + { + for (var i = 0; i < Mappings.Count; i++) + { + var mapping = Mappings.ElementAt(i); + Mappings.Remove(mapping.Key); + + var key = mapping.Key; + var value = mapping.Value; + + foreach (var alias in Aliases) + { + key = key.Replace(alias.Key, alias.Value); + value = value.Replace(alias.Key, alias.Value); + } + + Mappings[key] = value; + } + + for (var i = 0; i < Clean.Count; i++) + { + foreach (var alias in Aliases) + { + Clean[i] = Clean[i].Replace(alias.Key, alias.Value); + } + } + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/CreateAppSettingsSecretsStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/CreateAppSettingsSecretsStep.cs new file mode 100644 index 0000000000..fd600e2c5e --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/CreateAppSettingsSecretsStep.cs @@ -0,0 +1,56 @@ +using System; +using System.Linq; +using Volo.Abp.Cli.ProjectBuilding.Files; + +namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps +{ + public class CreateAppSettingsSecretsStep : ProjectBuildPipelineStep + { + private const string FileName = "appsettings.secrets.json"; + private const string AppSettingsFileName = "appsettings.json"; + private const string AppSettingsPlaceholder = ""; + + public override void Execute(ProjectBuildContext context) + { + var appSettingsFiles = context.Files + .Where(x => + x.Name.EndsWith(AppSettingsFileName) && + NotBlazorWasmProject(x.Name)) + .ToList(); + + var content = context.Template.IsPro() + ? $"{{{Environment.NewLine} \"AbpLicenseCode\": \"\" {Environment.NewLine}}}" + : $"{{{Environment.NewLine}}}"; + + foreach (var appSettingsFile in appSettingsFiles) + { + context.Files.Add(new FileEntry( + appSettingsFile.Name.Replace(AppSettingsFileName, FileName), + content.GetBytes(), + false)); + } + + var projectFiles = context.Files.Where(x => x.Content.Contains(AppSettingsPlaceholder)).ToList(); + + foreach (var projectFile in projectFiles) + { + projectFile.SetContent(ReplaceAppSettingsSecretsPlaceholder(projectFile.Content)); + } + } + + private static bool NotBlazorWasmProject(string fileName) + { + return !fileName.Contains("Blazor/wwwroot") && !fileName.Contains("Blazor.Host/wwwroot"); + } + + private static string ReplaceAppSettingsSecretsPlaceholder(string content) + { + var replaceContent = $"{Environment.NewLine}" + + $" {Environment.NewLine}" + + $" PreserveNewest{Environment.NewLine}" + + $" Always{Environment.NewLine}" + + " "; + return content.Replace(AppSettingsPlaceholder, replaceContent); + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/DatabaseManagementSystemChangeStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/DatabaseManagementSystemChangeStep.cs index b1c7a095c5..436bb865fb 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/DatabaseManagementSystemChangeStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/DatabaseManagementSystemChangeStep.cs @@ -1,11 +1,19 @@ using System; using System.Linq; using Volo.Abp.Cli.ProjectBuilding.Files; +using Volo.Abp.Cli.ProjectBuilding.Templates.App; namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps { public class DatabaseManagementSystemChangeStep : ProjectBuildPipelineStep { + private readonly bool _hasDbMigrations; + + public DatabaseManagementSystemChangeStep(bool hasDbMigrations) + { + _hasDbMigrations = hasDbMigrations; + } + public override void Execute(ProjectBuildContext context) { switch (context.BuildArgs.DatabaseManagementSystem) @@ -55,19 +63,19 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps private void AdjustOracleDbContextOptionsBuilder(ProjectBuildContext context) { - var dbContextFactoryFile = context.Files.FirstOrDefault(f => f.Name.EndsWith("MigrationsDbContextFactoryBase.cs", StringComparison.OrdinalIgnoreCase)) - ?? context.Files.FirstOrDefault(f => f.Name.EndsWith("MigrationsDbContextFactory.cs", StringComparison.OrdinalIgnoreCase)); + var dbContextFactoryFile = context.Files.FirstOrDefault(f => f.Name.EndsWith($"{(_hasDbMigrations ? "Migrations" : string.Empty)}DbContextFactoryBase.cs", StringComparison.OrdinalIgnoreCase)) + ?? context.Files.FirstOrDefault(f => f.Name.EndsWith($"{(_hasDbMigrations ? "Migrations" : string.Empty)}DbContextFactory.cs", StringComparison.OrdinalIgnoreCase)); dbContextFactoryFile?.ReplaceText("new DbContextOptionsBuilder", - $"(DbContextOptionsBuilder<{context.BuildArgs.SolutionName.ProjectName}MigrationsDbContext>) new DbContextOptionsBuilder"); + $"(DbContextOptionsBuilder<{context.BuildArgs.SolutionName.ProjectName}{(_hasDbMigrations ? "Migrations" : string.Empty)}DbContext>) new DbContextOptionsBuilder"); } private void AddMySqlServerVersion(ProjectBuildContext context) { - var dbContextFactoryFile = context.Files.FirstOrDefault(f => f.Name.EndsWith("MigrationsDbContextFactoryBase.cs", StringComparison.OrdinalIgnoreCase)) - ?? context.Files.First(f => f.Name.EndsWith("MigrationsDbContextFactory.cs", StringComparison.OrdinalIgnoreCase)); + var dbContextFactoryFile = context.Files.FirstOrDefault(f => f.Name.EndsWith($"{(_hasDbMigrations ? "Migrations" : string.Empty)}DbContextFactoryBase.cs", StringComparison.OrdinalIgnoreCase)) + ?? context.Files.FirstOrDefault(f => f.Name.EndsWith($"{(_hasDbMigrations ? "Migrations" : string.Empty)}DbContextFactory.cs", StringComparison.OrdinalIgnoreCase)); - dbContextFactoryFile.ReplaceText("configuration.GetConnectionString(\"Default\")", + dbContextFactoryFile?.ReplaceText("configuration.GetConnectionString(\"Default\")", "configuration.GetConnectionString(\"Default\"), MySqlServerVersion.LatestSupportedServerVersion"); } @@ -93,9 +101,9 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps var efCoreModuleClass = context.Files.First(f => f.Name.EndsWith("EntityFrameworkCoreModule.cs", StringComparison.OrdinalIgnoreCase)); efCoreModuleClass.ReplaceText(oldUseMethod, newUseMethodForEfModule); - var dbContextFactoryFile = context.Files.FirstOrDefault(f => f.Name.EndsWith("MigrationsDbContextFactoryBase.cs", StringComparison.OrdinalIgnoreCase)) - ?? context.Files.First(f => f.Name.EndsWith("MigrationsDbContextFactory.cs", StringComparison.OrdinalIgnoreCase)); - dbContextFactoryFile.ReplaceText(oldUseMethod, newUseMethodForDbContext); + var dbContextFactoryFile = context.Files.FirstOrDefault(f => f.Name.EndsWith($"{(_hasDbMigrations ? "Migrations" : string.Empty)}DbContextFactoryBase.cs", StringComparison.OrdinalIgnoreCase)) + ?? context.Files.FirstOrDefault(f => f.Name.EndsWith($"{(_hasDbMigrations ? "Migrations" : string.Empty)}DbContextFactory.cs", StringComparison.OrdinalIgnoreCase)); + dbContextFactoryFile?.ReplaceText(oldUseMethod, newUseMethodForDbContext); } } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/LicenseCodeReplaceStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/LicenseCodeReplaceStep.cs index b2dee80867..1b2525efef 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/LicenseCodeReplaceStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/LicenseCodeReplaceStep.cs @@ -12,7 +12,8 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps var licenseCode = context.BuildArgs.ExtraProperties.GetOrDefault("license-code"); var appSettingsJsonFiles = context.Files.Where(f => - f.Name.EndsWith("appsettings.json", StringComparison.OrdinalIgnoreCase)); + f.Name.EndsWith("appsettings.json", StringComparison.OrdinalIgnoreCase) || + f.Name.EndsWith("appsettings.secrets.json", StringComparison.OrdinalIgnoreCase)); foreach (var appSettingsJson in appSettingsJsonFiles) { 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 1ea30fe783..7081a93b0e 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 @@ -78,11 +78,13 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps RenameHelper.RenameAll(_entries, _companyNamePlaceHolder, _companyName); RenameHelper.RenameAll(_entries, _companyNamePlaceHolder.ToCamelCase(), _companyName.ToCamelCase()); RenameHelper.RenameAll(_entries, _companyNamePlaceHolder.ToKebabCase(), _companyName.ToKebabCase()); + RenameHelper.RenameAll(_entries, _companyNamePlaceHolder.ToLowerInvariant(), _companyName.ToLowerInvariant()); } else { RenameHelper.RenameAll(_entries, _companyNamePlaceHolder + "." + _projectNamePlaceHolder, _projectNamePlaceHolder); RenameHelper.RenameAll(_entries, _companyNamePlaceHolder.ToCamelCase() + "." + _projectNamePlaceHolder.ToCamelCase(), _projectNamePlaceHolder.ToCamelCase()); + RenameHelper.RenameAll(_entries, _companyNamePlaceHolder.ToLowerInvariant() + "." + _projectNamePlaceHolder.ToLowerInvariant(), _projectNamePlaceHolder.ToLowerInvariant()); RenameHelper.RenameAll(_entries, _companyNamePlaceHolder.ToKebabCase() + "/" + _projectNamePlaceHolder.ToKebabCase(), _projectNamePlaceHolder.ToKebabCase()); } } @@ -90,6 +92,7 @@ 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.ToLowerInvariant(), _projectName.ToLowerInvariant()); RenameHelper.RenameAll(_entries, _projectNamePlaceHolder.ToSnakeCase().ToUpper(), _projectName.ToSnakeCase().ToUpper()); } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateInfo.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateInfo.cs index 137674dcc6..10970932f0 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateInfo.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateInfo.cs @@ -30,5 +30,10 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building { return Array.Empty(); } + + public bool IsPro() + { + return Name.EndsWith("pro", StringComparison.OrdinalIgnoreCase); + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateProjectBuildPipelineBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateProjectBuildPipelineBuilder.cs index 3785475e27..712de9e327 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateProjectBuildPipelineBuilder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateProjectBuildPipelineBuilder.cs @@ -1,4 +1,5 @@ -using Volo.Abp.Cli.ProjectBuilding.Building.Steps; +using System; +using Volo.Abp.Cli.ProjectBuilding.Building.Steps; using Volo.Abp.Cli.ProjectBuilding.Templates.App; using Volo.Abp.Cli.ProjectBuilding.Templates.Microservice; using Volo.Abp.Cli.ProjectBuilding.Templates.MvcModule; @@ -12,6 +13,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building var pipeline = new ProjectBuildPipeline(context); pipeline.Steps.Add(new FileEntryListReadStep()); + pipeline.Steps.Add(new CreateAppSettingsSecretsStep()); pipeline.Steps.AddRange(context.Template.GetCustomSteps(context)); @@ -30,7 +32,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building if (context.Template.Name == AppTemplate.TemplateName || context.Template.Name == AppProTemplate.TemplateName) { - pipeline.Steps.Add(new DatabaseManagementSystemChangeStep()); // todo: move to custom steps? + pipeline.Steps.Add(new DatabaseManagementSystemChangeStep(context.Template.As().HasDbMigrations)); // todo: move to custom steps? } if ((context.BuildArgs.UiFramework == UiFramework.Mvc || context.BuildArgs.UiFramework == UiFramework.Blazor || context.BuildArgs.UiFramework == UiFramework.BlazorServer) 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 0f224a20cb..7b4790bdad 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 @@ -5,6 +5,7 @@ using System; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; +using NuGet.Versioning; using Volo.Abp.Cli.Commands; using Volo.Abp.Cli.Licensing; using Volo.Abp.Cli.ProjectBuilding.Analyticses; @@ -111,6 +112,11 @@ namespace Volo.Abp.Cli.ProjectBuilding args ); + if (context.Template is AppTemplateBase appTemplateBase) + { + appTemplateBase.HasDbMigrations = SemanticVersion.Parse(templateFile.Version) < new SemanticVersion(4, 3, 99); + } + TemplateProjectBuildPipelineBuilder.Build(context).Execute(); if (!templateInfo.DocumentUrl.IsNullOrEmpty()) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs index d515253659..048addf59f 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs @@ -9,9 +9,12 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App { public abstract class AppTemplateBase : TemplateInfo { + public bool HasDbMigrations { get; set; } + protected AppTemplateBase(string templateName) : base(templateName, DatabaseProvider.EntityFrameworkCore, UiFramework.Mvc) { + } public static bool IsAppTemplate(string templateName) @@ -40,31 +43,54 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App return steps; } - private static void ConfigureTenantSchema(ProjectBuildContext context, List steps) + private void ConfigureTenantSchema(ProjectBuildContext context, List steps) { if (context.BuildArgs.ExtraProperties.ContainsKey("separate-tenant-schema")) { - steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations")); - steps.Add(new TemplateProjectRenameStep("MyCompanyName.MyProjectName.EntityFrameworkCore.SeparateDbMigrations", "MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations")); + if (HasDbMigrations) + { + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations")); + steps.Add(new TemplateProjectRenameStep("MyCompanyName.MyProjectName.EntityFrameworkCore.SeparateDbMigrations", "MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations")); + } + else + { + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCore")); + steps.Add(new TemplateProjectRenameStep("MyCompanyName.MyProjectName.EntityFrameworkCoreWithSeparateDbContext", "MyCompanyName.MyProjectName.EntityFrameworkCore")); + } } else { - steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCore.SeparateDbMigrations")); + if (HasDbMigrations) + { + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCore.SeparateDbMigrations")); + } + else + { + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCoreWithSeparateDbContext")); + } } } - private static void SwitchDatabaseProvider(ProjectBuildContext context, List steps) + private void SwitchDatabaseProvider(ProjectBuildContext context, List steps) { if (context.BuildArgs.DatabaseProvider == DatabaseProvider.MongoDb) { - steps.Add(new AppTemplateSwitchEntityFrameworkCoreToMongoDbStep()); + steps.Add(new AppTemplateSwitchEntityFrameworkCoreToMongoDbStep(HasDbMigrations)); } if (context.BuildArgs.DatabaseProvider != DatabaseProvider.EntityFrameworkCore) { - steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCore")); - steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations")); - steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCore.Tests", projectFolderPath: "/aspnet-core/test/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests")); + if (HasDbMigrations) + { + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCore")); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations")); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCore.Tests", projectFolderPath: "/aspnet-core/test/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests")); + } + else + { + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCore")); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCore.Tests", projectFolderPath: "/aspnet-core/test/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests")); + } } else { @@ -380,13 +406,21 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App steps.Add(new UpdateNuGetConfigStep("/aspnet-core/NuGet.Config")); } - private static void RemoveMigrations(ProjectBuildContext context, List steps) + private void RemoveMigrations(ProjectBuildContext context, List steps) { if (string.IsNullOrWhiteSpace(context.BuildArgs.Version) || SemanticVersion.Parse(context.BuildArgs.Version) > new SemanticVersion(4,1,99)) { - steps.Add(new RemoveFolderStep("/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations")); - steps.Add(new RemoveFolderStep("/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/TenantMigrations")); + if (HasDbMigrations) + { + steps.Add(new RemoveFolderStep("/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations")); + steps.Add(new RemoveFolderStep("/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/TenantMigrations")); + } + else + { + steps.Add(new RemoveFolderStep("/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/Migrations")); + steps.Add(new RemoveFolderStep("/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/TenantMigrations")); + } } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateSwitchEntityFrameworkCoreToMongoDbStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateSwitchEntityFrameworkCoreToMongoDbStep.cs index 309d0a1552..88dba26ab1 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateSwitchEntityFrameworkCoreToMongoDbStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateSwitchEntityFrameworkCoreToMongoDbStep.cs @@ -1,12 +1,17 @@ using System; -using System.Linq; -using Volo.Abp.Cli.Commands; using Volo.Abp.Cli.ProjectBuilding.Building; namespace Volo.Abp.Cli.ProjectBuilding.Templates.App { public class AppTemplateSwitchEntityFrameworkCoreToMongoDbStep : ProjectBuildPipelineStep { + private readonly bool _hasDbMigrations; + + public AppTemplateSwitchEntityFrameworkCoreToMongoDbStep(bool hasDbMigrations) + { + _hasDbMigrations = hasDbMigrations; + } + public override void Execute(ProjectBuildContext context) { //MyCompanyName.MyProjectName.Web @@ -14,7 +19,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App ChangeProjectReference( context, "/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj", - "EntityFrameworkCore.DbMigrations", + _hasDbMigrations ? "EntityFrameworkCore.DbMigrations" : "EntityFrameworkCore", "MongoDB" ); @@ -23,7 +28,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App "/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs", "MyCompanyName.MyProjectName.EntityFrameworkCore", "MyCompanyName.MyProjectName.MongoDB", - "MyProjectNameEntityFrameworkCoreDbMigrationsModule", + _hasDbMigrations ? "MyProjectNameEntityFrameworkCoreDbMigrationsModule" : "MyProjectNameEntityFrameworkCoreModule", "MyProjectNameMongoDbModule" ); @@ -37,7 +42,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App ChangeProjectReference( context, "/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/MyCompanyName.MyProjectName.IdentityServer.csproj", - "EntityFrameworkCore.DbMigrations", + _hasDbMigrations ? "EntityFrameworkCore.DbMigrations" : "EntityFrameworkCore", "MongoDB" ); @@ -46,7 +51,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App "/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/MyProjectNameIdentityServerModule.cs", "MyCompanyName.MyProjectName.EntityFrameworkCore", "MyCompanyName.MyProjectName.MongoDB", - "MyProjectNameEntityFrameworkCoreDbMigrationsModule", + _hasDbMigrations ? "MyProjectNameEntityFrameworkCoreDbMigrationsModule" : "MyProjectNameEntityFrameworkCoreModule", "MyProjectNameMongoDbModule" ); @@ -60,7 +65,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App ChangeProjectReference( context, "/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj", - "EntityFrameworkCore.DbMigrations", + _hasDbMigrations ? "EntityFrameworkCore.DbMigrations" : "EntityFrameworkCore", "MongoDB" ); @@ -69,7 +74,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App "/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs", "MyCompanyName.MyProjectName.EntityFrameworkCore", "MyCompanyName.MyProjectName.MongoDB", - "MyProjectNameEntityFrameworkCoreDbMigrationsModule", + _hasDbMigrations ? "MyProjectNameEntityFrameworkCoreDbMigrationsModule" : "MyProjectNameEntityFrameworkCoreModule", "MyProjectNameMongoDbModule" ); @@ -83,7 +88,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App ChangeProjectReference( context, "/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj", - "EntityFrameworkCore.DbMigrations", + _hasDbMigrations ? "EntityFrameworkCore.DbMigrations" : "EntityFrameworkCore", "MongoDB" ); @@ -92,7 +97,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App "/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs", "MyCompanyName.MyProjectName.EntityFrameworkCore", "MyCompanyName.MyProjectName.MongoDB", - "MyProjectNameEntityFrameworkCoreDbMigrationsModule", + _hasDbMigrations ? "MyProjectNameEntityFrameworkCoreDbMigrationsModule" : "MyProjectNameEntityFrameworkCoreModule", "MyProjectNameMongoDbModule" ); @@ -106,7 +111,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App ChangeProjectReference( context, "/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj", - "EntityFrameworkCore.DbMigrations", + _hasDbMigrations ? "EntityFrameworkCore.DbMigrations" : "EntityFrameworkCore", "MongoDB" ); @@ -115,7 +120,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App "/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs", "MyCompanyName.MyProjectName.EntityFrameworkCore", "MyCompanyName.MyProjectName.MongoDB", - "MyProjectNameEntityFrameworkCoreDbMigrationsModule", + _hasDbMigrations ? "MyProjectNameEntityFrameworkCoreDbMigrationsModule" : "MyProjectNameEntityFrameworkCoreModule", "MyProjectNameMongoDbModule" ); @@ -129,7 +134,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App ChangeProjectReference( context, "/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/MyCompanyName.MyProjectName.DbMigrator.csproj", - "EntityFrameworkCore.DbMigrations", + _hasDbMigrations ? "EntityFrameworkCore.DbMigrations" : "EntityFrameworkCore", "MongoDB" ); @@ -138,7 +143,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App "/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/MyProjectNameDbMigratorModule.cs", "MyCompanyName.MyProjectName.EntityFrameworkCore", "MyCompanyName.MyProjectName.MongoDB", - "MyProjectNameEntityFrameworkCoreDbMigrationsModule", + _hasDbMigrations ? "MyProjectNameEntityFrameworkCoreDbMigrationsModule" : "MyProjectNameEntityFrameworkCoreModule", "MyProjectNameMongoDbModule" ); @@ -201,7 +206,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App ChangeProjectReference( context, "/aspnet-core/src/MyCompanyName.MyProjectName.Web.Public/MyCompanyName.MyProjectName.Web.Public.csproj", - "EntityFrameworkCore.DbMigrations", + _hasDbMigrations ? "EntityFrameworkCore.DbMigrations" : "EntityFrameworkCore", "MongoDB" ); @@ -210,7 +215,7 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App "/aspnet-core/src/MyCompanyName.MyProjectName.Web.Public/MyProjectNameWebPublicModule.cs", "MyCompanyName.MyProjectName.EntityFrameworkCore", "MyCompanyName.MyProjectName.MongoDB", - "MyProjectNameEntityFrameworkCoreDbMigrationsModule", + _hasDbMigrations ? "MyProjectNameEntityFrameworkCoreDbMigrationsModule" : "MyProjectNameEntityFrameworkCoreModule", "MyProjectNameMongoDbModule" ); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Module/ModuleTemplateBase.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Module/ModuleTemplateBase.cs index e26e4a6d25..663d4e9bad 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Module/ModuleTemplateBase.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Module/ModuleTemplateBase.cs @@ -40,6 +40,14 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.Module "MyCompanyName.MyProjectName.Blazor" )); + steps.Add(new RemoveProjectFromSolutionStep( + "MyCompanyName.MyProjectName.Blazor.Server" + )); + + steps.Add(new RemoveProjectFromSolutionStep( + "MyCompanyName.MyProjectName.Blazor.WebAssembly" + )); + steps.Add(new RemoveProjectFromSolutionStep( "MyCompanyName.MyProjectName.Blazor.Host", projectFolderPath: "/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host" diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ModuleInfo.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ModuleInfo.cs index d5e7a4e15a..3f1e658525 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ModuleInfo.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ModuleInfo.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; namespace Volo.Abp.Cli.ProjectModification { @@ -10,8 +12,23 @@ namespace Volo.Abp.Cli.ProjectModification public string EfCoreConfigureMethodName { get; set; } + public string DocumentationLinks { get; set; } + public List NugetPackages { get; set; } public List NpmPackages { get; set; } + + public string GetFirstDocumentationLinkOrNull() + { + if (string.IsNullOrWhiteSpace(DocumentationLinks)) + { + return null; + } + + var docs = DocumentationLinks.Split(" ", StringSplitOptions.RemoveEmptyEntries); + return docs.Any() ? + docs.First() : + null; + } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs index 9a30fd7fd3..273f011f41 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs @@ -131,6 +131,12 @@ namespace Volo.Abp.Cli.ProjectModification await RunBundleForBlazorAsync(projectFiles, module); ModifyDbContext(projectFiles, module, skipDbMigrations); + + var documentationLink = module.GetFirstDocumentationLinkOrNull(); + if (documentationLink != null) + { + CmdHelper.OpenWebPage(documentationLink); + } } private ModuleWithMastersInfo RemoveIncompatiblePackages(ModuleWithMastersInfo module, string version) @@ -209,7 +215,7 @@ namespace Volo.Abp.Cli.ProjectModification { projectsToRemove.AddRange(await FindProjectsToRemoveByTarget(module, NuGetPackageTarget.BlazorWebAssembly, isProjectTiered)); - webPackagesWillBeAddedToBlazorServerProject = module.NugetPackages.All(np=> np.Target != NuGetPackageTarget.BlazorServer && np.TieredTarget != NuGetPackageTarget.BlazorServer); + webPackagesWillBeAddedToBlazorServerProject = module.NugetPackages.All(np => np.Target != NuGetPackageTarget.BlazorServer && np.TieredTarget != NuGetPackageTarget.BlazorServer); } else { @@ -250,7 +256,7 @@ namespace Volo.Abp.Cli.ProjectModification private bool IsReferencedByAnotherModuleProject(string moduleDirectory, List projectsToRemove, string projectToRemove) { var moduleProjects = Directory.GetFiles(moduleDirectory, "*.csproj", SearchOption.AllDirectories); - var projectsToKeep = moduleProjects.Where(mp=> !projectsToRemove.Contains(Path.GetFileName(mp).RemovePostFix(".csproj"))).ToList(); + var projectsToKeep = moduleProjects.Where(mp => !projectsToRemove.Contains(Path.GetFileName(mp).RemovePostFix(".csproj"))).ToList(); return projectsToKeep.Select(File.ReadAllText).Any(content => content.Contains($"\"{projectToRemove}\"")); } @@ -278,7 +284,7 @@ namespace Volo.Abp.Cli.ProjectModification return projectsToRemove; } - private async Task> FindProjectsToRemoveByPostFix(string moduleDirectory, string targetFolder, + private async Task> FindProjectsToRemoveByPostFix(string moduleDirectory, string targetFolder, string postFix) { var projectsToRemove = new List(); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/ProjectNameValidator.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/ProjectNameValidator.cs index 8430cd79a0..4606aa59c4 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/ProjectNameValidator.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/ProjectNameValidator.cs @@ -17,6 +17,11 @@ namespace Volo.Abp.Cli.Utils "LPT2" }; + private static readonly string[] IllegalKeywords = new[] + { + "Blazor" + }; + private static bool HasParentDirectoryString(string projectName) { return projectName.Contains(".."); @@ -40,6 +45,19 @@ namespace Volo.Abp.Cli.Utils return false; } + private static bool HasIllegalKeywords(string projectName) + { + foreach (var illegalKeyword in IllegalKeywords) + { + if (projectName.Contains(illegalKeyword)) + { + return true; + } + } + + return false; + } + public static bool IsValid(string projectName) { if (projectName == null) @@ -62,6 +80,11 @@ namespace Volo.Abp.Cli.Utils return false; } + if (HasIllegalKeywords(projectName)) + { + return false; + } + return true; } } diff --git a/framework/src/Volo.Abp.Core/Microsoft/Extensions/Hosting/AbpHostingHostBuilderExtensions.cs b/framework/src/Volo.Abp.Core/Microsoft/Extensions/Hosting/AbpHostingHostBuilderExtensions.cs new file mode 100644 index 0000000000..e2f55142e7 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Microsoft/Extensions/Hosting/AbpHostingHostBuilderExtensions.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Configuration; + +namespace Microsoft.Extensions.Hosting +{ + public static class AbpHostingHostBuilderExtensions + { + public const string AppSettingsSecretJsonPath = "appsettings.secrets.json"; + + public static IHostBuilder AddAppSettingsSecretsJson( + this IHostBuilder hostBuilder, + bool optional = true, + bool reloadOnChange = true, + string path = AppSettingsSecretJsonPath) + { + return hostBuilder.ConfigureAppConfiguration((_, builder) => + { + builder.AddJsonFile( + path: AppSettingsSecretJsonPath, + optional: optional, + reloadOnChange: reloadOnChange + ); + }); + } + } +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Content/IRemoteStreamContent.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Content/IRemoteStreamContent.cs index 4805983287..4cabd5b5e0 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Content/IRemoteStreamContent.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Content/IRemoteStreamContent.cs @@ -9,6 +9,8 @@ namespace Volo.Abp.Content long? ContentLength { get; } + string FileName { get; } + Stream GetStream(); } } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Content/RemoteStreamContent.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Content/RemoteStreamContent.cs index fc30b93f7d..f2e69f49ad 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Content/RemoteStreamContent.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Content/RemoteStreamContent.cs @@ -4,31 +4,36 @@ namespace Volo.Abp.Content { public class RemoteStreamContent : IRemoteStreamContent { - private readonly Stream _stream; - private readonly string _contentType; - private readonly long? _length; - private readonly bool _leaveOpen; - - public RemoteStreamContent(Stream stream, string contentType, long? readOnlylength = null, bool leaveOpen = false) - { - _stream = stream; - _contentType = contentType; - _length = readOnlylength ?? (stream.GetNullableLength() - stream.GetNullablePosition()); - _leaveOpen = leaveOpen; - } + private readonly Stream _stream; + private readonly string _fileName; + private readonly string _contentType; + private readonly long? _length; + private readonly bool _leaveOpen; + public virtual string FileName => _fileName; public virtual string ContentType => _contentType; public virtual long? ContentLength => _length; + public RemoteStreamContent(Stream stream, string fileName, string contentType = null, long? readOnlylength = null, bool leaveOpen = false) + { + _stream = stream; + _fileName = fileName; + _contentType = contentType ?? "application/octet-stream"; + _length = readOnlylength ?? (stream.GetNullableLength() - stream.GetNullablePosition()); + _leaveOpen = leaveOpen; + } + public virtual Stream GetStream() { return _stream; } - public virtual void Dispose() - { - if (!_leaveOpen) - _stream?.Dispose(); + public virtual void Dispose() + { + if (!_leaveOpen) + { + _stream?.Dispose(); + } } } } diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Localization/Resources/AbpDdd/sk.json b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Localization/Resources/AbpDdd/sk.json new file mode 100644 index 0000000000..16e95c988c --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Localization/Resources/AbpDdd/sk.json @@ -0,0 +1,6 @@ +{ + "culture": "sk", + "texts": { + "MaxResultCountExceededExceptionMessage": "{0} nemôže byť viac ako {1}! Zvýšte {2}.{3} na strane servera, aby ste zobrazili viac výsledkov." + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/DependencyInjection/AbpCommonDbContextRegistrationOptions.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/DependencyInjection/AbpCommonDbContextRegistrationOptions.cs index ee172b3409..886d8e020c 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/DependencyInjection/AbpCommonDbContextRegistrationOptions.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/DependencyInjection/AbpCommonDbContextRegistrationOptions.cs @@ -15,7 +15,7 @@ namespace Volo.Abp.DependencyInjection public IServiceCollection Services { get; } - public List ReplacedDbContextTypes { get; } + public Dictionary ReplacedDbContextTypes { get; } public Type DefaultRepositoryDbContextType { get; protected set; } @@ -39,7 +39,7 @@ namespace Volo.Abp.DependencyInjection Services = services; DefaultRepositoryDbContextType = originalDbContextType; CustomRepositories = new Dictionary(); - ReplacedDbContextTypes = new List(); + ReplacedDbContextTypes = new Dictionary(); SpecifiedDefaultRepositories = new List(); } @@ -47,15 +47,20 @@ namespace Volo.Abp.DependencyInjection { return ReplaceDbContext(typeof(TOtherDbContext)); } + + public IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext() + { + return ReplaceDbContext(typeof(TOtherDbContext), typeof(TTargetDbContext)); + } - public IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext(Type otherDbContextType) + public IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext(Type otherDbContextType, Type targetDbContextType = null) { if (!otherDbContextType.IsAssignableFrom(OriginalDbContextType)) { throw new AbpException($"{OriginalDbContextType.AssemblyQualifiedName} should inherit/implement {otherDbContextType.AssemblyQualifiedName}!"); } - ReplacedDbContextTypes.AddIfNotContains(otherDbContextType); + ReplacedDbContextTypes[otherDbContextType] = targetDbContextType; return this; } diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/DependencyInjection/IAbpCommonDbContextRegistrationOptionsBuilder.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/DependencyInjection/IAbpCommonDbContextRegistrationOptionsBuilder.cs index 34e74d90cd..248300ab62 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/DependencyInjection/IAbpCommonDbContextRegistrationOptionsBuilder.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/DependencyInjection/IAbpCommonDbContextRegistrationOptionsBuilder.cs @@ -75,9 +75,17 @@ namespace Volo.Abp.DependencyInjection IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext(); /// - /// Replaces given DbContext type with this DbContext type. + /// Replaces given DbContext type with the target DbContext type. + /// + /// The DbContext type to be replaced + /// The target DbContext type + IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext(); + + /// + /// Replaces given DbContext type with the given or this DbContext type. /// /// The DbContext type to be replaced - IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext(Type otherDbContextType); + /// The target DbContext type (optional, used this DbContext type if not provided) + IAbpCommonDbContextRegistrationOptionsBuilder ReplaceDbContext(Type otherDbContextType, Type targetDbContextType = null); } } diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/AutoEntityDistributedEventSelectorListExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/AutoEntityDistributedEventSelectorListExtensions.cs index d2a0622f54..a9247bc28b 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/AutoEntityDistributedEventSelectorListExtensions.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/AutoEntityDistributedEventSelectorListExtensions.cs @@ -26,7 +26,7 @@ namespace Volo.Abp.Domain.Entities.Events.Distributed ) ); } - + /// /// Adds a specific entity type and the types derived from that entity type. /// @@ -49,14 +49,27 @@ namespace Volo.Abp.Domain.Entities.Events.Distributed ) ); } - + + /// + /// Remove a specific entity type and the types derived from that entity type. + /// + /// Type of the entity + public static void Remove([NotNull] this IAutoEntityDistributedEventSelectorList selectors) + where TEntity : IEntity + { + Check.NotNull(selectors, nameof(selectors)); + + var selectorName = "Entity:" + typeof(TEntity).FullName; + selectors.RemoveAll(s => s.Name == selectorName); + } + /// /// Adds all entity types. /// public static void AddAll([NotNull] this IAutoEntityDistributedEventSelectorList selectors) { Check.NotNull(selectors, nameof(selectors)); - + if (selectors.Any(s => s.Name == AllEntitiesSelectorName)) { return; @@ -72,11 +85,11 @@ namespace Volo.Abp.Domain.Entities.Events.Distributed public static void Add( [NotNull] this IAutoEntityDistributedEventSelectorList selectors, - string selectorName, + string selectorName, Func predicate) { Check.NotNull(selectors, nameof(selectors)); - + if (selectors.Any(s => s.Name == selectorName)) { throw new AbpException($"There is already a selector added before with the same name: {selectorName}"); @@ -89,7 +102,7 @@ namespace Volo.Abp.Domain.Entities.Events.Distributed ) ); } - + public static void Add( [NotNull] this IAutoEntityDistributedEventSelectorList selectors, Func predicate) @@ -103,15 +116,15 @@ namespace Volo.Abp.Domain.Entities.Events.Distributed { Check.NotNull(selectors, nameof(selectors)); Check.NotNull(name, nameof(name)); - + return selectors.RemoveAll(s => s.Name == name).Count > 0; } - + public static bool IsMatch([NotNull] this IAutoEntityDistributedEventSelectorList selectors, Type entityType) { Check.NotNull(selectors, nameof(selectors)); return selectors.Any(s => s.Predicate(entityType)); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryAsyncExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryAsyncExtensions.cs index 87f119016d..4d64b08d70 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryAsyncExtensions.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryAsyncExtensions.cs @@ -26,6 +26,15 @@ namespace Volo.Abp.Domain.Repositories #region Any/All + public static async Task AnyAsync( + [NotNull] this IReadOnlyRepository repository, + CancellationToken cancellationToken = default) + where T : class, IEntity + { + var queryable = await repository.GetQueryableAsync(); + return await repository.AsyncExecuter.AnyAsync(queryable, cancellationToken); + } + public static async Task AnyAsync( [NotNull] this IReadOnlyRepository repository, [NotNull] Expression> predicate, diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/it.json b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/it.json index 34b5f573c5..3160703123 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/it.json +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/it.json @@ -1,17 +1,17 @@ { "culture": "it", "texts": { - "DisplayName:Abp.Mailing.DefaultFromAddress": "Indirizzo predefinito dall'indirizzo", - "DisplayName:Abp.Mailing.DefaultFromDisplayName": "Predefinito dal nome visualizzato", - "DisplayName:Abp.Mailing.Smtp.Host": "Ospite", + "DisplayName:Abp.Mailing.DefaultFromAddress": "Indirizzo mittente predefinito", + "DisplayName:Abp.Mailing.DefaultFromDisplayName": "Nome mittente visualizzato predefinito", + "DisplayName:Abp.Mailing.Smtp.Host": "Host", "DisplayName:Abp.Mailing.Smtp.Port": "Porta", "DisplayName:Abp.Mailing.Smtp.UserName": "Nome utente", - "DisplayName:Abp.Mailing.Smtp.Password": "Parola d'ordine", + "DisplayName:Abp.Mailing.Smtp.Password": "Password", "DisplayName:Abp.Mailing.Smtp.Domain": "Dominio", "DisplayName:Abp.Mailing.Smtp.EnableSsl": "Abilita SSL", "DisplayName:Abp.Mailing.Smtp.UseDefaultCredentials": "Usa credenziali predefinite", - "Description:Abp.Mailing.DefaultFromAddress": "L'indirizzo predefinito da", - "Description:Abp.Mailing.DefaultFromDisplayName": "Il valore predefinito dal nome visualizzato", + "Description:Abp.Mailing.DefaultFromAddress": "L'indirizzo predefinito mittente", + "Description:Abp.Mailing.DefaultFromDisplayName": "Il valore nome mittente visualizzato predefinito", "Description:Abp.Mailing.Smtp.Host": "Il nome o l'indirizzo IP dell'host utilizzato per le transazioni SMTP.", "Description:Abp.Mailing.Smtp.Port": "La porta utilizzata per le transazioni SMTP.", "Description:Abp.Mailing.Smtp.UserName": "Nome utente associato alle credenziali.", diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/sk.json b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/sk.json new file mode 100644 index 0000000000..8b5acf50af --- /dev/null +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Localization/sk.json @@ -0,0 +1,25 @@ +{ + "culture": "sk", + "texts": { + "DisplayName:Abp.Mailing.DefaultFromAddress": "Predvolená adresa Od", + "DisplayName:Abp.Mailing.DefaultFromDisplayName": "Predvolené meno Od", + "DisplayName:Abp.Mailing.Smtp.Host": "Host server", + "DisplayName:Abp.Mailing.Smtp.Port": "Port", + "DisplayName:Abp.Mailing.Smtp.UserName": "Meno používateľa", + "DisplayName:Abp.Mailing.Smtp.Password": "Heslo", + "DisplayName:Abp.Mailing.Smtp.Domain": "Doména", + "DisplayName:Abp.Mailing.Smtp.EnableSsl": "Povoliť SSL", + "DisplayName:Abp.Mailing.Smtp.UseDefaultCredentials": "Použiť predovlené prihlasovacie údaje", + "Description:Abp.Mailing.DefaultFromAddress": "Predvolené adresa Od", + "Description:Abp.Mailing.DefaultFromDisplayName": "Predvolené meno Od", + "Description:Abp.Mailing.Smtp.Host": "Názov alebo IP adresa host servera používaného pre SMTP transakcie.", + "Description:Abp.Mailing.Smtp.Port": "Port používaný pre SMTP transakcie.", + "Description:Abp.Mailing.Smtp.UserName": "Meno používateľa spojené s prihlasovacími údajmi.", + "Description:Abp.Mailing.Smtp.Password": "Heslo pre používateľské meno spojené s prihlasovacími údajmi.", + "Description:Abp.Mailing.Smtp.Domain": "Názov domény alebo počítača, ktorý overuje prihlasovacie údaje.", + "Description:Abp.Mailing.Smtp.EnableSsl": "Či SMTP klient používa na šifrovanie spojenia Secure Sockets Layer (SSL).", + "Description:Abp.Mailing.Smtp.UseDefaultCredentials": "Či sa spolu s požiadavkami posielajú aj predvolené prihlasovacie údaje.", + "TextTemplate:StandardEmailTemplates.Layout": "Predvolená šablóna rozloženia emailu", + "TextTemplate:StandardEmailTemplates.Message": "Jednoduchá šablóna správy pre emaily" + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreServiceCollectionExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreServiceCollectionExtensions.cs index a48575ddcc..658269a376 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreServiceCollectionExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreServiceCollectionExtensions.cs @@ -31,18 +31,21 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAddTransient(DbContextOptionsFactory.Create); - foreach (var dbContextType in options.ReplacedDbContextTypes) + foreach (var entry in options.ReplacedDbContextTypes) { + var originalDbContextType = entry.Key; + var targetDbContextType = entry.Value ?? typeof(TDbContext); + services.Replace( ServiceDescriptor.Transient( - dbContextType, - sp => sp.GetRequiredService(typeof(TDbContext)) + originalDbContextType, + sp => sp.GetRequiredService(targetDbContextType) ) ); services.Configure(opts => { - opts.DbContextReplacements[dbContextType] = typeof(TDbContext); + opts.DbContextReplacements[originalDbContextType] = targetDbContextType; }); } 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 cda8c02416..9b488924e8 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -86,7 +86,6 @@ namespace Volo.Abp.EntityFrameworkCore protected AbpDbContext(DbContextOptions options) : base(options) { - } protected override void OnModelCreating(ModelBuilder modelBuilder) @@ -372,17 +371,20 @@ namespace Volo.Abp.EntityFrameworkCore protected virtual void ApplyAbpConceptsForModifiedEntity(EntityEntry entry, EntityChangeReport changeReport) { - UpdateConcurrencyStamp(entry); - SetModificationAuditProperties(entry); - - if (entry.Entity is ISoftDelete && entry.Entity.As().IsDeleted) - { - SetDeletionAuditProperties(entry); - changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Deleted)); - } - else + if (entry.State == EntityState.Modified && entry.Properties.Any(x => x.IsModified && x.Metadata.ValueGenerated == ValueGenerated.Never)) { - changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Updated)); + UpdateConcurrencyStamp(entry); + SetModificationAuditProperties(entry); + + if (entry.Entity is ISoftDelete && entry.Entity.As().IsDeleted) + { + SetDeletionAuditProperties(entry); + changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Deleted)); + } + else + { + changeReport.ChangedEntities.Add(new EntityChangeEntry(entry.Entity, EntityChangeType.Updated)); + } } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EfCoreAsyncQueryableProvider.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EfCoreAsyncQueryableProvider.cs index 2a0400c578..ded3c5e1cd 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EfCoreAsyncQueryableProvider.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EfCoreAsyncQueryableProvider.cs @@ -23,6 +23,11 @@ namespace Volo.Abp.EntityFrameworkCore return queryable.ContainsAsync(item, cancellationToken); } + public Task AnyAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return queryable.AnyAsync(cancellationToken); + } + public Task AnyAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) { return queryable.AnyAsync(predicate, cancellationToken); 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 50de4f1b56..4ef6057943 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 @@ -9,6 +9,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Volo.Abp.Auditing; +using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Entities; using Volo.Abp.Json; @@ -26,8 +27,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory protected IJsonSerializer JsonSerializer { get; } protected AbpAuditingOptions Options { get; } protected IAuditingHelper AuditingHelper { get; } - - private readonly IClock _clock; + protected IClock Clock{ get; } public EntityHistoryHelper( IAuditingStore auditingStore, @@ -36,7 +36,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory IJsonSerializer jsonSerializer, IAuditingHelper auditingHelper) { - _clock = clock; + Clock = clock; AuditingStore = auditingStore; JsonSerializer = jsonSerializer; AuditingHelper = auditingHelper; @@ -69,7 +69,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory } [CanBeNull] - private EntityChangeInfo CreateEntityChangeOrNull(EntityEntry entityEntry) + protected virtual EntityChangeInfo CreateEntityChangeOrNull(EntityEntry entityEntry) { var entity = entityEntry.Entity; @@ -121,23 +121,23 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory return multiTenantEntity.TenantId; } - private DateTime GetChangeTime(EntityChangeInfo entityChange) + protected virtual DateTime GetChangeTime(EntityChangeInfo entityChange) { var entity = entityChange.EntityEntry.As().Entity; switch (entityChange.ChangeType) { case EntityChangeType.Created: - return (entity as IHasCreationTime)?.CreationTime ?? _clock.Now; + return (entity as IHasCreationTime)?.CreationTime ?? Clock.Now; case EntityChangeType.Deleted: - return (entity as IHasDeletionTime)?.DeletionTime ?? _clock.Now; + return (entity as IHasDeletionTime)?.DeletionTime ?? Clock.Now; case EntityChangeType.Updated: - return (entity as IHasModificationTime)?.LastModificationTime ?? _clock.Now; + return (entity as IHasModificationTime)?.LastModificationTime ?? Clock.Now; default: throw new AbpException($"Unknown {nameof(EntityChangeInfo)}: {entityChange}"); } } - private string GetEntityId(object entityAsObj) + protected virtual string GetEntityId(object entityAsObj) { if (!(entityAsObj is IEntity entity)) { @@ -156,7 +156,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory /// /// Gets the property changes for this entry. /// - private List GetPropertyChanges(EntityEntry entityEntry) + protected virtual List GetPropertyChanges(EntityEntry entityEntry) { var propertyChanges = new List(); var properties = entityEntry.Metadata.GetProperties(); @@ -181,12 +181,12 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory return propertyChanges; } - private bool IsCreated(EntityEntry entityEntry) + protected virtual bool IsCreated(EntityEntry entityEntry) { return entityEntry.State == EntityState.Added; } - private bool IsDeleted(EntityEntry entityEntry) + protected virtual bool IsDeleted(EntityEntry entityEntry) { if (entityEntry.State == EntityState.Deleted) { @@ -197,7 +197,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory return entity is ISoftDelete && entity.As().IsDeleted; } - private bool ShouldSaveEntityHistory(EntityEntry entityEntry, bool defaultValue = false) + protected virtual bool ShouldSaveEntityHistory(EntityEntry entityEntry, bool defaultValue = false) { if (entityEntry.State == EntityState.Detached || entityEntry.State == EntityState.Unchanged) @@ -220,7 +220,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory return defaultValue; } - private bool ShouldSavePropertyHistory(PropertyEntry propertyEntry, bool defaultValue) + protected virtual bool ShouldSavePropertyHistory(PropertyEntry propertyEntry, bool defaultValue) { if (propertyEntry.Metadata.Name == "Id") { @@ -247,6 +247,26 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory return false; } + if (propertyEntry.OriginalValue is ExtraPropertyDictionary originalValue && propertyEntry.CurrentValue is ExtraPropertyDictionary currentValue) + { + if (originalValue.IsNullOrEmpty() && currentValue.IsNullOrEmpty()) + { + return false; + } + + if (!originalValue.Select(x => x.Key).SequenceEqual(currentValue.Select(x => x.Key))) + { + return true; + } + + if (!originalValue.Select(x => x.Value).SequenceEqual(currentValue.Select(x => x.Value))) + { + return true; + } + + return defaultValue; + } + var isModified = !(propertyEntry.OriginalValue?.Equals(propertyEntry.CurrentValue) ?? propertyEntry.CurrentValue == null); if (isModified) { @@ -256,7 +276,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory return defaultValue; } - private bool IsBaseAuditProperty(PropertyInfo propertyInfo, Type entityType) + protected virtual bool IsBaseAuditProperty(PropertyInfo propertyInfo, Type entityType) { if (entityType.IsAssignableTo() && propertyInfo.Name == nameof(IHasCreationTime.CreationTime)) @@ -308,11 +328,11 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory return false; } - + /// /// Updates change time, entity id and foreign keys after SaveChanges is called. /// - public void UpdateChangeList(List entityChanges) + public virtual void UpdateChangeList(List entityChanges) { foreach (var entityChange in entityChanges) { @@ -372,4 +392,4 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs index a40c4d6254..44cad9c638 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs @@ -82,6 +82,11 @@ namespace Volo.Abp.EntityFrameworkCore.Modeling } } + public static void ApplyObjectExtensionMappings(this EntityTypeBuilder b) + { + ObjectExtensionManager.Instance.ConfigureEfCoreEntity(b); + } + public static void ConfigureSoftDelete(this EntityTypeBuilder b) where T : class, ISoftDelete { diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpModelBuilderObjectExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpModelBuilderObjectExtensions.cs new file mode 100644 index 0000000000..295f6ef7cf --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpModelBuilderObjectExtensions.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore; +using Volo.Abp.ObjectExtending; + +namespace Volo.Abp.EntityFrameworkCore.Modeling +{ + public static class AbpModelBuilderObjectExtensions + { + public static void TryConfigureObjectExtensions(this ModelBuilder modelBuilder) + where TDbContext : DbContext + { + ObjectExtensionManager.Instance.ConfigureEfCoreDbContext(modelBuilder); + } + } +} 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 17b495cdad..f27fc12d64 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionInfoExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionInfoExtensions.cs @@ -1,11 +1,17 @@ using System; +using System.Collections.Generic; +using System.Linq; using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; namespace Volo.Abp.ObjectExtending { public static class EfCoreObjectExtensionInfoExtensions { + public const string EfCoreDbContextConfigurationName = "EfCoreDbContextMapping"; + public const string EfCoreEntityConfigurationName = "EfCoreEntityMapping"; + [Obsolete("Use MapEfCoreProperty with EntityTypeAndPropertyBuildAction parameters.")] public static ObjectExtensionInfo MapEfCoreProperty( [NotNull] this ObjectExtensionInfo objectExtensionInfo, @@ -71,5 +77,69 @@ namespace Volo.Abp.ObjectExtending } ); } + + public static ObjectExtensionInfo MapEfCoreEntity( + [NotNull] this ObjectExtensionInfo objectExtensionInfo, + [NotNull] Action entityTypeBuildAction) + { + Check.NotNull(objectExtensionInfo, nameof(objectExtensionInfo)); + + var mappingOptionList = new List + { + new ObjectExtensionInfoEfCoreMappingOptions( + objectExtensionInfo, + entityTypeBuildAction) + }; + + objectExtensionInfo.Configuration.AddOrUpdate(EfCoreEntityConfigurationName, mappingOptionList, + (k, v) => + { + v.As>().Add(mappingOptionList.First()); + return v; + }); + + return objectExtensionInfo; + } + + public static ObjectExtensionInfo MapEfCoreDbContext( + [NotNull] this ObjectExtensionInfo objectExtensionInfo, + [NotNull] Action modelBuildAction) + { + Check.NotNull(objectExtensionInfo, nameof(objectExtensionInfo)); + + var mappingOptionList = new List + { + new ObjectExtensionInfoEfCoreMappingOptions( + objectExtensionInfo, + modelBuildAction) + }; + + objectExtensionInfo.Configuration.AddOrUpdate(EfCoreDbContextConfigurationName, mappingOptionList, + (k, v) => + { + v.As>().Add(mappingOptionList.First()); + return v; + }); + + return objectExtensionInfo; + } + + public static List GetEfCoreEntityMappings( + [NotNull] this ObjectExtensionInfo objectExtensionInfo) + { + Check.NotNull(objectExtensionInfo, nameof(objectExtensionInfo)); + + return !objectExtensionInfo.Configuration.TryGetValue(EfCoreEntityConfigurationName, out var options) ? + new List() : options.As>(); + } + + public static List GetEfCoreDbContextMappings( + [NotNull] this ObjectExtensionInfo objectExtensionInfo) + { + Check.NotNull(objectExtensionInfo, nameof(objectExtensionInfo)); + + return !objectExtensionInfo.Configuration.TryGetValue(EfCoreDbContextConfigurationName, out var options) ? + new List() : options.As>(); + } } } 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 54c1909bb3..86d4115421 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionManagerExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionManagerExtensions.cs @@ -1,5 +1,6 @@ using System; using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Volo.Abp.Data; using Volo.Abp.Domain.Entities; @@ -8,6 +9,45 @@ namespace Volo.Abp.ObjectExtending { public static class EfCoreObjectExtensionManagerExtensions { + public static ObjectExtensionManager MapEfCoreDbContext( + [NotNull] this ObjectExtensionManager objectExtensionManager, + [NotNull] Action modelBuilderAction) + where TDbContext : DbContext + { + return objectExtensionManager.AddOrUpdate( + typeof(TDbContext), + options => + { + options.MapEfCoreDbContext(modelBuilderAction); + }); + } + + public static ObjectExtensionManager MapEfCoreEntity( + [NotNull] this ObjectExtensionManager objectExtensionManager, + [NotNull] Action entityTypeBuildAction) + where TEntity : IEntity + { + return MapEfCoreEntity( + objectExtensionManager, + typeof(TEntity), + entityTypeBuildAction); + } + + public static ObjectExtensionManager MapEfCoreEntity( + [NotNull] this ObjectExtensionManager objectExtensionManager, + [NotNull] Type entityType, + [NotNull] Action entityTypeBuildAction) + { + Check.NotNull(objectExtensionManager, nameof(objectExtensionManager)); + + return objectExtensionManager.AddOrUpdate( + entityType, + options => + { + options.MapEfCoreEntity(entityTypeBuildAction); + }); + } + public static ObjectExtensionManager MapEfCoreProperty( [NotNull] this ObjectExtensionManager objectExtensionManager, [NotNull] string propertyName) @@ -32,10 +72,7 @@ namespace Volo.Abp.ObjectExtending entityType, propertyType, propertyName, - options => - { - options.MapEfCore(); - } + options => { options.MapEfCore(); } ); } @@ -126,6 +163,13 @@ namespace Volo.Abp.ObjectExtending return; } + var efCoreEntityMappings = objectExtension.GetEfCoreEntityMappings(); + + foreach (var efCoreEntityMapping in efCoreEntityMappings) + { + efCoreEntityMapping.EntityTypeBuildAction?.Invoke(typeBuilder); + } + foreach (var property in objectExtension.GetProperties()) { var efCoreMapping = property.GetEfCoreMappingOrNull(); @@ -148,5 +192,27 @@ namespace Volo.Abp.ObjectExtending #pragma warning restore 618 } } + + public static void ConfigureEfCoreDbContext( + [NotNull] this ObjectExtensionManager objectExtensionManager, + [NotNull] ModelBuilder modelBuilder) + where TDbContext : DbContext + { + Check.NotNull(objectExtensionManager, nameof(objectExtensionManager)); + Check.NotNull(modelBuilder, nameof(modelBuilder)); + + var objectExtension = objectExtensionManager.GetOrNull(typeof(TDbContext)); + if (objectExtension == null) + { + return; + } + + var efCoreDbContextMappings = objectExtension.GetEfCoreDbContextMappings(); + + foreach (var efCoreDbContextMapping in efCoreDbContextMappings) + { + efCoreDbContextMapping.ModelBuildAction?.Invoke(modelBuilder); + } + } } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/ObjectExtensionInfoEfCoreMappingOptions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/ObjectExtensionInfoEfCoreMappingOptions.cs new file mode 100644 index 0000000000..5510e138e4 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/ObjectExtensionInfoEfCoreMappingOptions.cs @@ -0,0 +1,39 @@ +using System; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Volo.Abp.ObjectExtending +{ + public class ObjectExtensionInfoEfCoreMappingOptions + { + [NotNull] + public ObjectExtensionInfo ObjectExtension { get; } + + [CanBeNull] + public Action EntityTypeBuildAction { get; set; } + + [CanBeNull] + public Action ModelBuildAction { get; set; } + + public ObjectExtensionInfoEfCoreMappingOptions( + [NotNull] ObjectExtensionInfo objectExtension, + [NotNull] Action entityTypeBuildAction) + { + ObjectExtension = Check.NotNull(objectExtension, nameof(objectExtension)); + EntityTypeBuildAction = Check.NotNull(entityTypeBuildAction, nameof(entityTypeBuildAction)); + + EntityTypeBuildAction = entityTypeBuildAction; + } + + public ObjectExtensionInfoEfCoreMappingOptions( + [NotNull] ObjectExtensionInfo objectExtension, + [NotNull] Action modelBuildAction) + { + ObjectExtension = Check.NotNull(objectExtension, nameof(objectExtension)); + ModelBuildAction = Check.NotNull(modelBuildAction, nameof(modelBuildAction)); + + ModelBuildAction = modelBuildAction; + } + } +} diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs index fd6d773952..0b228d676c 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Confluent.Kafka; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus.Distributed; using Volo.Abp.Kafka; @@ -18,6 +19,7 @@ namespace Volo.Abp.EventBus.Kafka [ExposeServices(typeof(IDistributedEventBus), typeof(KafkaDistributedEventBus))] public class KafkaDistributedEventBus : EventBusBase, IDistributedEventBus, ISingletonDependency { + protected AbpEventBusOptions AbpEventBusOptions { get; } protected AbpKafkaEventBusOptions AbpKafkaEventBusOptions { get; } protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; } protected IKafkaMessageConsumerFactory MessageConsumerFactory { get; } @@ -26,6 +28,7 @@ namespace Volo.Abp.EventBus.Kafka protected ConcurrentDictionary> HandlerFactories { get; } protected ConcurrentDictionary EventTypes { get; } protected IKafkaMessageConsumer Consumer { get; private set; } + protected string DeadLetterTopicName { get; } public KafkaDistributedEventBus( IServiceScopeFactory serviceScopeFactory, @@ -34,14 +37,19 @@ namespace Volo.Abp.EventBus.Kafka IKafkaMessageConsumerFactory messageConsumerFactory, IOptions abpDistributedEventBusOptions, IKafkaSerializer serializer, - IProducerPool producerPool) - : base(serviceScopeFactory, currentTenant) + IProducerPool producerPool, + IEventErrorHandler errorHandler, + IOptions abpEventBusOptions) + : base(serviceScopeFactory, currentTenant, errorHandler) { AbpKafkaEventBusOptions = abpKafkaEventBusOptions.Value; AbpDistributedEventBusOptions = abpDistributedEventBusOptions.Value; + AbpEventBusOptions = abpEventBusOptions.Value; MessageConsumerFactory = messageConsumerFactory; Serializer = serializer; ProducerPool = producerPool; + DeadLetterTopicName = + AbpEventBusOptions.DeadLetterName ?? AbpKafkaEventBusOptions.TopicName + "_dead_letter"; HandlerFactories = new ConcurrentDictionary>(); EventTypes = new ConcurrentDictionary(); @@ -51,9 +59,9 @@ namespace Volo.Abp.EventBus.Kafka { Consumer = MessageConsumerFactory.Create( AbpKafkaEventBusOptions.TopicName, + DeadLetterTopicName, AbpKafkaEventBusOptions.GroupId, AbpKafkaEventBusOptions.ConnectionName); - Consumer.OnMessageReceived(ProcessEventAsync); SubscribeHandlers(AbpDistributedEventBusOptions.Handlers); @@ -70,7 +78,18 @@ namespace Volo.Abp.EventBus.Kafka var eventData = Serializer.Deserialize(message.Value, eventType); - await TriggerHandlersAsync(eventType, eventData); + await TriggerHandlersAsync(eventType, eventData, errorContext => + { + var retryAttempt = 0; + if (message.Headers.TryGetLastBytes(EventErrorHandlerBase.RetryAttemptKey, out var retryAttemptBytes)) + { + retryAttempt = Serializer.Deserialize(retryAttemptBytes); + } + + errorContext.EventData = Serializer.Deserialize(message.Value, eventType); + errorContext.SetProperty(EventErrorHandlerBase.HeadersKey, message.Headers); + errorContext.SetProperty(EventErrorHandlerBase.RetryAttemptKey, retryAttempt); + }); } public IDisposable Subscribe(IDistributedEventHandler handler) where TEvent : class @@ -147,20 +166,51 @@ namespace Volo.Abp.EventBus.Kafka } public override async Task PublishAsync(Type eventType, object eventData) + { + await PublishAsync(eventType, eventData, new Headers {{"messageId", Serializer.Serialize(Guid.NewGuid())}}, null); + } + + public virtual async Task PublishAsync(Type eventType, object eventData, Headers headers, Dictionary headersArguments) + { + await PublishAsync(AbpKafkaEventBusOptions.TopicName, eventType, eventData, headers, headersArguments); + } + + public virtual async Task PublishToDeadLetterAsync(Type eventType, object eventData, Headers headers, Dictionary headersArguments) + { + await PublishAsync(DeadLetterTopicName, eventType, eventData, headers, headersArguments); + } + + private async Task PublishAsync(string topicName, Type eventType, object eventData, Headers headers, Dictionary headersArguments) { var eventName = EventNameAttribute.GetNameOrDefault(eventType); var body = Serializer.Serialize(eventData); var producer = ProducerPool.Get(AbpKafkaEventBusOptions.ConnectionName); + SetEventMessageHeaders(headers, headersArguments); + await producer.ProduceAsync( - AbpKafkaEventBusOptions.TopicName, + topicName, new Message { - Key = eventName, Value = body + Key = eventName, Value = body, Headers = headers }); } + private void SetEventMessageHeaders(Headers headers, Dictionary headersArguments) + { + if (headersArguments == null) + { + return; + } + + foreach (var header in headersArguments) + { + headers.Remove(header.Key); + headers.Add(header.Key, Serializer.Serialize(header.Value)); + } + } + private List GetOrCreateHandlerFactories(Type eventType) { return HandlerFactories.GetOrAdd( diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs new file mode 100644 index 0000000000..aee21f75a9 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaEventErrorHandler.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Confluent.Kafka; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.EventBus.Kafka +{ + public class KafkaEventErrorHandler : EventErrorHandlerBase, ISingletonDependency + { + protected ILogger Logger { get; set; } + + public KafkaEventErrorHandler( + IOptions options) : base(options) + { + Logger = NullLogger.Instance; + } + + protected override async Task RetryAsync(EventExecutionErrorContext context) + { + if (Options.RetryStrategyOptions.IntervalMillisecond > 0) + { + await Task.Delay(Options.RetryStrategyOptions.IntervalMillisecond); + } + + context.TryGetRetryAttempt(out var retryAttempt); + + await context.EventBus.As().PublishAsync( + context.EventType, + context.EventData, + context.GetProperty(HeadersKey).As(), + new Dictionary {{RetryAttemptKey, ++retryAttempt}}); + } + + protected override async Task MoveToDeadLetterAsync(EventExecutionErrorContext context) + { + Logger.LogException( + context.Exceptions.Count == 1 ? context.Exceptions.First() : new AggregateException(context.Exceptions), + LogLevel.Error); + + await context.EventBus.As().PublishToDeadLetterAsync( + context.EventType, + context.EventData, + context.GetProperty(HeadersKey).As(), + new Dictionary {{"exceptions", context.Exceptions.Select(x => x.ToString()).ToList()}}); + } + } +} 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 e8c7fc6c5b..541fce1334 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 @@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using RabbitMQ.Client; using RabbitMQ.Client.Events; +using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus.Distributed; using Volo.Abp.MultiTenancy; @@ -25,6 +26,7 @@ namespace Volo.Abp.EventBus.RabbitMq { protected AbpRabbitMqEventBusOptions AbpRabbitMqEventBusOptions { get; } protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; } + protected AbpEventBusOptions AbpEventBusOptions { get; } protected IConnectionPool ConnectionPool { get; } protected IRabbitMqSerializer Serializer { get; } @@ -41,12 +43,15 @@ namespace Volo.Abp.EventBus.RabbitMq IServiceScopeFactory serviceScopeFactory, IOptions distributedEventBusOptions, IRabbitMqMessageConsumerFactory messageConsumerFactory, - ICurrentTenant currentTenant) - : base(serviceScopeFactory, currentTenant) + ICurrentTenant currentTenant, + IEventErrorHandler errorHandler, + IOptions abpEventBusOptions) + : base(serviceScopeFactory, currentTenant, errorHandler) { ConnectionPool = connectionPool; Serializer = serializer; MessageConsumerFactory = messageConsumerFactory; + AbpEventBusOptions = abpEventBusOptions.Value; AbpDistributedEventBusOptions = distributedEventBusOptions.Value; AbpRabbitMqEventBusOptions = options.Value; @@ -56,17 +61,21 @@ namespace Volo.Abp.EventBus.RabbitMq public void Initialize() { + const string suffix = "_dead_letter"; + Consumer = MessageConsumerFactory.Create( new ExchangeDeclareConfiguration( AbpRabbitMqEventBusOptions.ExchangeName, type: "direct", - durable: true + durable: true, + deadLetterExchangeName: AbpRabbitMqEventBusOptions.ExchangeName + suffix ), new QueueDeclareConfiguration( AbpRabbitMqEventBusOptions.ClientName, durable: true, exclusive: false, - autoDelete: false + autoDelete: false, + AbpEventBusOptions.DeadLetterName ?? AbpRabbitMqEventBusOptions.ClientName + suffix ), AbpRabbitMqEventBusOptions.ConnectionName ); @@ -87,7 +96,19 @@ namespace Volo.Abp.EventBus.RabbitMq var eventData = Serializer.Deserialize(ea.Body.ToArray(), eventType); - await TriggerHandlersAsync(eventType, eventData); + await TriggerHandlersAsync(eventType, eventData, errorContext => + { + var retryAttempt = 0; + if (ea.BasicProperties.Headers != null && + ea.BasicProperties.Headers.ContainsKey(EventErrorHandlerBase.RetryAttemptKey)) + { + retryAttempt = (int)ea.BasicProperties.Headers[EventErrorHandlerBase.RetryAttemptKey]; + } + + errorContext.EventData = Serializer.Deserialize(ea.Body.ToArray(), eventType); + errorContext.SetProperty(EventErrorHandlerBase.HeadersKey, ea.BasicProperties); + errorContext.SetProperty(EventErrorHandlerBase.RetryAttemptKey, retryAttempt); + }); } public IDisposable Subscribe(IDistributedEventHandler handler) where TEvent : class @@ -168,8 +189,14 @@ namespace Volo.Abp.EventBus.RabbitMq GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Clear()); } - public override Task PublishAsync(Type eventType, object eventData) + public override async Task PublishAsync(Type eventType, object eventData) + { + await PublishAsync(eventType, eventData, null); + } + + public Task PublishAsync(Type eventType, object eventData, IBasicProperties properties, Dictionary headersArguments = null) { + var eventName = EventNameAttribute.GetNameOrDefault(eventType); var body = Serializer.Serialize(eventData); @@ -181,11 +208,17 @@ namespace Volo.Abp.EventBus.RabbitMq durable: true ); - var properties = channel.CreateBasicProperties(); - properties.DeliveryMode = RabbitMqConsts.DeliveryModes.Persistent; + if (properties == null) + { + properties = channel.CreateBasicProperties(); + properties.DeliveryMode = RabbitMqConsts.DeliveryModes.Persistent; + properties.MessageId = Guid.NewGuid().ToString("N"); + } + + SetEventMessageHeaders(properties, headersArguments); channel.BasicPublish( - exchange: AbpRabbitMqEventBusOptions.ExchangeName, + exchange: AbpRabbitMqEventBusOptions.ExchangeName, routingKey: eventName, mandatory: true, basicProperties: properties, @@ -196,6 +229,21 @@ namespace Volo.Abp.EventBus.RabbitMq return Task.CompletedTask; } + private void SetEventMessageHeaders(IBasicProperties properties, Dictionary headersArguments) + { + if (headersArguments == null) + { + return; + } + + properties.Headers ??= new Dictionary(); + + foreach (var header in headersArguments) + { + properties.Headers[header.Key] = header.Value; + } + } + private List GetOrCreateHandlerFactories(Type eventType) { return HandlerFactories.GetOrAdd( @@ -213,9 +261,11 @@ namespace Volo.Abp.EventBus.RabbitMq { var handlerFactoryList = new List(); - foreach (var handlerFactory in HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key))) + foreach (var handlerFactory in + HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key))) { - handlerFactoryList.Add(new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value)); + handlerFactoryList.Add( + new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value)); } return handlerFactoryList.ToArray(); diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs new file mode 100644 index 0000000000..e8848bee72 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqEventErrorHandler.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using RabbitMQ.Client; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.EventBus.RabbitMq +{ + public class RabbitMqEventErrorHandler : EventErrorHandlerBase, ISingletonDependency + { + public RabbitMqEventErrorHandler( + IOptions options) + : base(options) + { + } + + protected override async Task RetryAsync(EventExecutionErrorContext context) + { + if (Options.RetryStrategyOptions.IntervalMillisecond > 0) + { + await Task.Delay(Options.RetryStrategyOptions.IntervalMillisecond); + } + + context.TryGetRetryAttempt(out var retryAttempt); + + await context.EventBus.As().PublishAsync( + context.EventType, + context.EventData, + context.GetProperty(HeadersKey).As(), + new Dictionary + { + {RetryAttemptKey, ++retryAttempt}, + {"exceptions", context.Exceptions.Select(x => x.ToString()).ToList()} + }); + } + + protected override Task MoveToDeadLetterAsync(EventExecutionErrorContext context) + { + ThrowOriginalExceptions(context); + + return Task.CompletedTask; + } + } +} diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpEventBusRebusModule.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpEventBusRebusModule.cs index 9ebd91a6a5..a13f964d3f 100644 --- a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpEventBusRebusModule.cs +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpEventBusRebusModule.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Rebus.Handlers; +using Rebus.Retry.Simple; using Rebus.ServiceProvider; using Volo.Abp.Modularity; @@ -11,23 +12,29 @@ namespace Volo.Abp.EventBus.Rebus { public override void ConfigureServices(ServiceConfigurationContext context) { - var options = context.Services.ExecutePreConfiguredActions(); + var abpEventBusOptions = context.Services.ExecutePreConfiguredActions(); + var options = context.Services.ExecutePreConfiguredActions();; context.Services.AddTransient(typeof(IHandleMessages<>), typeof(RebusDistributedEventHandlerAdapter<>)); Configure(rebusOptions => { - rebusOptions.Configurer = options.Configurer; - rebusOptions.Publish = options.Publish; - rebusOptions.InputQueueName = options.InputQueueName; + context.Services.ExecutePreConfiguredActions(rebusOptions); }); - context.Services.AddRebus(configurer => + context.Services.AddRebus(configure => { - options.Configurer?.Invoke(configurer); - return configurer; - }); + if (abpEventBusOptions.RetryStrategyOptions != null) + { + configure.Options(b => + b.SimpleRetryStrategy( + errorQueueAddress: abpEventBusOptions.DeadLetterName ?? options.InputQueueName + "_dead_letter", + maxDeliveryAttempts: abpEventBusOptions.RetryStrategyOptions.MaxRetryAttempts)); + } + options.Configurer?.Invoke(configure); + return configure; + }); } public override void OnApplicationInitialization(ApplicationInitializationContext context) diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpRebusEventBusOptions.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpRebusEventBusOptions.cs index 8aaee7bd63..b6343204c0 100644 --- a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpRebusEventBusOptions.cs +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpRebusEventBusOptions.cs @@ -32,7 +32,7 @@ namespace Volo.Abp.EventBus.Rebus public AbpRebusEventBusOptions() { _publish = DefaultPublish; - _configurer = DefaultConfigurer; + _configurer = DefaultConfigure; } private async Task DefaultPublish(IBus bus, Type eventType, object eventData) @@ -40,10 +40,10 @@ namespace Volo.Abp.EventBus.Rebus await bus.Advanced.Routing.Send(InputQueueName, eventData); } - private void DefaultConfigurer(RebusConfigurer configurer) + private void DefaultConfigure(RebusConfigurer configure) { - configurer.Subscriptions(s => s.StoreInMemory()); - configurer.Transport(t => t.UseInMemoryTransport(new InMemNetwork(), InputQueueName)); + configure.Subscriptions(s => s.StoreInMemory()); + configure.Transport(t => t.UseInMemoryTransport(new InMemNetwork(), InputQueueName)); } } } diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs index ce3549646e..97bedaf8fb 100644 --- a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs @@ -30,8 +30,9 @@ namespace Volo.Abp.EventBus.Rebus ICurrentTenant currentTenant, IBus rebus, IOptions abpDistributedEventBusOptions, - IOptions abpEventBusRebusOptions) : - base(serviceScopeFactory, currentTenant) + IOptions abpEventBusRebusOptions, + IEventErrorHandler errorHandler) : + base(serviceScopeFactory, currentTenant, errorHandler) { Rebus = rebus; AbpRebusEventBusOptions = abpEventBusRebusOptions.Value; diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusEventErrorHandler.cs new file mode 100644 index 0000000000..8fe6a53dbb --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusEventErrorHandler.cs @@ -0,0 +1,32 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.EventBus.Rebus +{ + /// + /// Rebus will automatic retries and error handling: https://github.com/rebus-org/Rebus/wiki/Automatic-retries-and-error-handling + /// + public class RebusEventErrorHandler : EventErrorHandlerBase, ISingletonDependency + { + public RebusEventErrorHandler( + IOptions options) + : base(options) + { + } + + protected override Task RetryAsync(EventExecutionErrorContext context) + { + ThrowOriginalExceptions(context); + + return Task.CompletedTask; + } + + protected override Task MoveToDeadLetterAsync(EventExecutionErrorContext context) + { + ThrowOriginalExceptions(context); + + return Task.CompletedTask; + } + } +} diff --git a/framework/src/Volo.Abp.EventBus/Volo.Abp.EventBus.csproj b/framework/src/Volo.Abp.EventBus/Volo.Abp.EventBus.csproj index 221454bed3..261b10d8ae 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 eb9c17b129..329c26c93f 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using Volo.Abp.EventBus.Abstractions; using Volo.Abp.EventBus.Distributed; using Volo.Abp.EventBus.Local; +using Volo.Abp.Json; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; using Volo.Abp.Reflection; @@ -12,7 +13,8 @@ namespace Volo.Abp.EventBus { [DependsOn( typeof(AbpEventBusAbstractionsModule), - typeof(AbpMultiTenancyModule))] + typeof(AbpMultiTenancyModule), + typeof(AbpJsonModule))] public class AbpEventBusModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) @@ -20,6 +22,14 @@ namespace Volo.Abp.EventBus AddEventHandlers(context.Services); } + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + context.Services.ExecutePreConfiguredActions(options); + }); + } + private static void AddEventHandlers(IServiceCollection services) { var localHandlers = new List(); diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusOptions.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusOptions.cs new file mode 100644 index 0000000000..39631c7e18 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusOptions.cs @@ -0,0 +1,22 @@ +using System; + +namespace Volo.Abp.EventBus +{ + public class AbpEventBusOptions + { + public bool EnabledErrorHandle { get; set; } + + public Func ErrorHandleSelector { get; set; } + + public string DeadLetterName { get; set; } + + public AbpEventBusRetryStrategyOptions RetryStrategyOptions { get; set; } + + public void UseRetryStrategy(Action action = null) + { + EnabledErrorHandle = true; + RetryStrategyOptions = new AbpEventBusRetryStrategyOptions(); + action?.Invoke(RetryStrategyOptions); + } + } +} diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusRetryStrategyOptions.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusRetryStrategyOptions.cs new file mode 100644 index 0000000000..4b5b722e96 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusRetryStrategyOptions.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.EventBus +{ + public class AbpEventBusRetryStrategyOptions + { + public int IntervalMillisecond { get; set; } = 3000; + + public int MaxRetryAttempts { get; set; } = 3; + } +} 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 80d4134db2..78d226538e 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs @@ -19,10 +19,16 @@ namespace Volo.Abp.EventBus protected ICurrentTenant CurrentTenant { get; } - protected EventBusBase(IServiceScopeFactory serviceScopeFactory, ICurrentTenant currentTenant) + protected IEventErrorHandler ErrorHandler { get; } + + protected EventBusBase( + IServiceScopeFactory serviceScopeFactory, + ICurrentTenant currentTenant, + IEventErrorHandler errorHandler) { ServiceScopeFactory = serviceScopeFactory; CurrentTenant = currentTenant; + ErrorHandler = errorHandler; } /// @@ -89,7 +95,7 @@ namespace Volo.Abp.EventBus /// public abstract Task PublishAsync(Type eventType, object eventData); - public virtual async Task TriggerHandlersAsync(Type eventType, object eventData) + public virtual async Task TriggerHandlersAsync(Type eventType, object eventData, Action onErrorAction = null) { var exceptions = new List(); @@ -97,16 +103,13 @@ namespace Volo.Abp.EventBus if (exceptions.Any()) { - if (exceptions.Count == 1) - { - exceptions[0].ReThrow(); - } - - throw new AggregateException("More than one error has occurred while triggering the event: " + eventType, exceptions); + var context = new EventExecutionErrorContext(exceptions, eventType, this); + onErrorAction?.Invoke(context); + await ErrorHandler.HandleAsync(context); } } - protected virtual async Task TriggerHandlersAsync(Type eventType, object eventData, List exceptions) + protected virtual async Task TriggerHandlersAsync(Type eventType, object eventData , List exceptions) { await new SynchronizationContextRemover(); diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs new file mode 100644 index 0000000000..ba4527fc70 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventErrorHandlerBase.cs @@ -0,0 +1,76 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; + +namespace Volo.Abp.EventBus +{ + public abstract class EventErrorHandlerBase : IEventErrorHandler + { + public const string HeadersKey = "headers"; + public const string RetryAttemptKey = "retryAttempt"; + + protected AbpEventBusOptions Options { get; } + + protected EventErrorHandlerBase(IOptions options) + { + Options = options.Value; + } + + public virtual async Task HandleAsync(EventExecutionErrorContext context) + { + if (!await ShouldHandleAsync(context)) + { + ThrowOriginalExceptions(context); + } + + if (await ShouldRetryAsync(context)) + { + await RetryAsync(context); + return; + } + + await MoveToDeadLetterAsync(context); + } + + protected abstract Task RetryAsync(EventExecutionErrorContext context); + + protected abstract Task MoveToDeadLetterAsync(EventExecutionErrorContext context); + + protected virtual Task ShouldHandleAsync(EventExecutionErrorContext context) + { + if (!Options.EnabledErrorHandle) + { + return Task.FromResult(false); + } + + return Task.FromResult(Options.ErrorHandleSelector == null || Options.ErrorHandleSelector.Invoke(context.EventType)); + } + + protected virtual Task ShouldRetryAsync(EventExecutionErrorContext context) + { + if (Options.RetryStrategyOptions == null) + { + return Task.FromResult(false); + } + + if (!context.TryGetRetryAttempt(out var retryAttempt)) + { + return Task.FromResult(false); + } + + return Task.FromResult(Options.RetryStrategyOptions.MaxRetryAttempts > retryAttempt); + } + + protected virtual void ThrowOriginalExceptions(EventExecutionErrorContext context) + { + if (context.Exceptions.Count == 1) + { + context.Exceptions[0].ReThrow(); + } + + throw new AggregateException( + "More than one error has occurred while triggering the event: " + context.EventType, + context.Exceptions); + } + } +} diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventExecutionErrorContext.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventExecutionErrorContext.cs new file mode 100644 index 0000000000..e192f61cbe --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventExecutionErrorContext.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using Volo.Abp.Data; +using Volo.Abp.ObjectExtending; + +namespace Volo.Abp.EventBus +{ + public class EventExecutionErrorContext : ExtensibleObject + { + public IReadOnlyList Exceptions { get; } + + public object EventData { get; set; } + + public Type EventType { get; } + + public IEventBus EventBus { get; } + + public EventExecutionErrorContext(List exceptions, Type eventType, IEventBus eventBus) + { + Exceptions = exceptions; + EventType = eventType; + EventBus = eventBus; + } + + public bool TryGetRetryAttempt(out int retryAttempt) + { + retryAttempt = 0; + if (!this.HasProperty(EventErrorHandlerBase.RetryAttemptKey)) + { + return false; + } + + retryAttempt = this.GetProperty(EventErrorHandlerBase.RetryAttemptKey); + return true; + + } + } +} diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/IEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/IEventErrorHandler.cs new file mode 100644 index 0000000000..f1b4a40f15 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/IEventErrorHandler.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.EventBus +{ + public interface IEventErrorHandler + { + Task HandleAsync(EventExecutionErrorContext context); + } +} 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 8c7bef6f2d..16246a892f 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 @@ -7,9 +7,11 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; +using Volo.Abp.Json; namespace Volo.Abp.EventBus.Local { @@ -28,12 +30,17 @@ namespace Volo.Abp.EventBus.Local protected ConcurrentDictionary> HandlerFactories { get; } + protected IJsonSerializer Serializer { get; } + public LocalEventBus( IOptions options, IServiceScopeFactory serviceScopeFactory, - ICurrentTenant currentTenant) - : base(serviceScopeFactory, currentTenant) + ICurrentTenant currentTenant, + IEventErrorHandler errorHandler, + IJsonSerializer serializer) + : base(serviceScopeFactory, currentTenant, errorHandler) { + Serializer = serializer; Options = options.Value; Logger = NullLogger.Instance; @@ -119,19 +126,17 @@ namespace Volo.Abp.EventBus.Local public override async Task PublishAsync(Type eventType, object eventData) { - var exceptions = new List(); - - await TriggerHandlersAsync(eventType, eventData, exceptions); + await PublishAsync(new LocalEventMessage(Guid.NewGuid(), eventData, eventType)); + } - if (exceptions.Any()) + public virtual async Task PublishAsync(LocalEventMessage localEventMessage) + { + var rawEventData = Serializer.Serialize(localEventMessage.EventData); + await TriggerHandlersAsync(localEventMessage.EventType, localEventMessage.EventData, errorContext => { - if (exceptions.Count == 1) - { - exceptions[0].ReThrow(); - } - - throw new AggregateException("More than one error has occurred while triggering the event: " + eventType, exceptions); - } + errorContext.EventData = Serializer.Deserialize(localEventMessage.EventType, rawEventData); + errorContext.SetProperty(nameof(LocalEventMessage.MessageId), localEventMessage.MessageId); + }); } protected override IEnumerable GetHandlerFactories(Type eventType) diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs new file mode 100644 index 0000000000..c08bca019f --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventErrorHandler.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.EventBus.Local +{ + [ExposeServices(typeof(LocalEventErrorHandler), typeof(IEventErrorHandler))] + public class LocalEventErrorHandler : EventErrorHandlerBase, ISingletonDependency + { + protected Dictionary RetryTracking { get; } + + public LocalEventErrorHandler( + IOptions options) + : base(options) + { + RetryTracking = new Dictionary(); + } + + protected override async Task RetryAsync(EventExecutionErrorContext context) + { + if (Options.RetryStrategyOptions.IntervalMillisecond > 0) + { + await Task.Delay(Options.RetryStrategyOptions.IntervalMillisecond); + } + + var messageId = context.GetProperty(nameof(LocalEventMessage.MessageId)); + + context.TryGetRetryAttempt(out var retryAttempt); + RetryTracking[messageId] = ++retryAttempt; + + await context.EventBus.As().PublishAsync(new LocalEventMessage(messageId, context.EventData, context.EventType)); + + RetryTracking.Remove(messageId); + } + + protected override Task MoveToDeadLetterAsync(EventExecutionErrorContext context) + { + ThrowOriginalExceptions(context); + + return Task.CompletedTask; + } + + protected override async Task ShouldRetryAsync(EventExecutionErrorContext context) + { + var messageId = context.GetProperty(nameof(LocalEventMessage.MessageId)); + context.SetProperty(RetryAttemptKey, RetryTracking.GetOrDefault(messageId)); + + if (await base.ShouldRetryAsync(context)) + { + return true; + } + + RetryTracking.Remove(messageId); + return false; + } + } +} diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventMessage.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventMessage.cs new file mode 100644 index 0000000000..550de897bd --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventMessage.cs @@ -0,0 +1,20 @@ +using System; + +namespace Volo.Abp.EventBus.Local +{ + public class LocalEventMessage + { + public Guid MessageId { get; } + + public object EventData { get; } + + public Type EventType { get; } + + public LocalEventMessage(Guid messageId, object eventData, Type eventType) + { + MessageId = messageId; + EventData = eventData; + EventType = eventType; + } + } +} diff --git a/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs b/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs index 7d6da59e81..a96797ce40 100644 --- a/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs +++ b/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Options; using Volo.Abp.Authorization; +using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Entities; using Volo.Abp.ExceptionHandling; @@ -58,14 +59,19 @@ namespace Volo.Abp.AspNetCore.ExceptionHandling exception = TryToGetActualException(exception); - if (exception is EntityNotFoundException) + if (exception is AbpRemoteCallException remoteCallException) { - return CreateEntityNotFoundError(exception as EntityNotFoundException); + return remoteCallException.Error; } - if (exception is AbpRemoteCallException remoteCallException) + if (exception is AbpDbConcurrencyException) { - return remoteCallException.Error; + return new RemoteServiceErrorInfo(L["AbpDbConcurrencyErrorMessage"]); + } + + if (exception is EntityNotFoundException) + { + return CreateEntityNotFoundError(exception as EntityNotFoundException); } var errorInfo = new RemoteServiceErrorInfo(); @@ -169,7 +175,7 @@ namespace Volo.Abp.AspNetCore.ExceptionHandling return new RemoteServiceErrorInfo(exception.Message); } - + protected virtual Exception TryToGetActualException(Exception exception) { if (exception is AggregateException && exception.InnerException != null) diff --git a/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/Domain/Entities/EntityNotFoundException.cs b/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/Domain/Entities/EntityNotFoundException.cs index 85466ccf0e..c920e1e475 100644 --- a/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/Domain/Entities/EntityNotFoundException.cs +++ b/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/Domain/Entities/EntityNotFoundException.cs @@ -49,7 +49,7 @@ namespace Volo.Abp.Domain.Entities public EntityNotFoundException(Type entityType, object id, Exception innerException) : base( id == null - ? $"There is no such an entity given given id. Entity type: {entityType.FullName}" + ? $"There is no such an entity given id. Entity type: {entityType.FullName}" : $"There is no such an entity. Entity type: {entityType.FullName}, id: {id}", innerException) { diff --git a/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/en.json b/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/en.json index 62b599b7d1..5b50d19b3a 100644 --- a/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/en.json +++ b/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/en.json @@ -13,6 +13,7 @@ "DefaultErrorMessage404": "Resource not found!", "DefaultErrorMessage404Detail": "The resource requested could not found on the server!", "EntityNotFoundErrorMessage": "There is no entity {0} with id = {1}!", + "AbpDbConcurrencyErrorMessage": "The data you have submitted has already changed by another user/client. Please discard the changes you've done and try from the beginning.", "Error": "Error", "UnhandledException": "Unhandled exception!", "401Message": "Unauthorized", diff --git a/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/it.json b/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/it.json index b2025559fe..c70e01cfd4 100644 --- a/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/it.json +++ b/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/it.json @@ -13,6 +13,7 @@ "DefaultErrorMessage404": "Risorsa non trovata!", "DefaultErrorMessage404Detail": "La risorsa richiesta non è stata trovata sul server!", "EntityNotFoundErrorMessage": "Non esiste un'entità {0} con id = {1}!", + "AbpDbConcurrencyErrorMessage": "I dati che hai inviato sono già stati modificati da un altro utente/cliente. Per favore scarta le modifiche che hai fatto e riprova dall'inizio.", "Error": "Errore", "UnhandledException": "Eccezione non gestita!", "401Message": "Non autorizzato", diff --git a/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/sk.json b/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/sk.json new file mode 100644 index 0000000000..50f91c6d5e --- /dev/null +++ b/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/sk.json @@ -0,0 +1,25 @@ +{ + "culture": "sk", + "texts": { + "InternalServerErrorMessage": "Počas vašej požiadavky došlo k internej chybe!", + "ValidationErrorMessage": "Vaša žiadosť nie je platná!", + "ValidationNarrativeErrorMessageTitle": "Počas overovania boli zistené tieto chyby.", + "DefaultErrorMessage": "Vyskytla sa chyba!", + "DefaultErrorMessageDetail": "Server neodoslal detail chyby.", + "DefaultErrorMessage401": "Nie ste autentifikovaný!", + "DefaultErrorMessage401Detail": "Ak chcete vykonať túto operáciu, mali by ste sa prihlásiť.", + "DefaultErrorMessage403": "Nie ste autorizovaný!", + "DefaultErrorMessage403Detail": "Nemáte povolenie vykonať túto operáciu!", + "DefaultErrorMessage404": "Zdroj nebol nájdený!", + "DefaultErrorMessage404Detail": "Požadovaný zdroj sa na serveri nenašiel!", + "EntityNotFoundErrorMessage": "Entita {0} s id = {1} neexistuje!", + "Error": "Error", + "UnhandledException": "Neošetrená výnimka!", + "401Message": "Neoprávnený", + "403Message": "Zakázané", + "404Message": "Stránka nebola nájdená", + "500Message": "Interná chyba servera", + "403MessageDetail": "Nie ste oprávnený vykonávať túto operáciu!", + "404MessageDetail": "Je nám ľúto, ale na tejto adrese sa nič nenachádza." + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/tr.json b/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/tr.json index da199b2707..a1c10f3d13 100644 --- a/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/tr.json +++ b/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/tr.json @@ -13,6 +13,7 @@ "DefaultErrorMessage404": "Kaynak bulunamadı!", "DefaultErrorMessage404Detail": "İstenilen kaynak sunucuda bulunamadı.", "EntityNotFoundErrorMessage": "Id değeri {1} olan {0} türünden bir nesne bulunamadı!", + "AbpDbConcurrencyErrorMessage": "Gönderdiğiniz veri başka bir kullanıcı/istemci tarafından değiştirilmiş. Lütfen işlemi iptal edip baştan deneyin.", "Error": "Hata", "UnhandledException": "Yakalanmamış hata!", "401Message": "Yetkisiz İşlem", diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/Localization/sk.json b/framework/src/Volo.Abp.Features/Volo/Abp/Features/Localization/sk.json new file mode 100644 index 0000000000..6e341929d1 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/Localization/sk.json @@ -0,0 +1,8 @@ +{ + "culture": "sk", + "texts": { + "Volo.Feature:010001": "Funkcia nie je povolená: {FeatureName}", + "Volo.Feature:010002": "Požadované funkcie nie sú povolené. Všetky tieto funkcie musia byť povolené: {FeatureNames}.", + "Volo.Feature:010003": "Požadované funkcie nie sú povolené. Aspoň jedna z týchto funkcií musí byť povolená: {FeatureNames}" + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/Localization/sk.json b/framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/Localization/sk.json new file mode 100644 index 0000000000..69887e390c --- /dev/null +++ b/framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/Localization/sk.json @@ -0,0 +1,6 @@ +{ + "culture": "sk", + "texts": { + "Volo.GlobalFeature:010001": "Služba '{ServiceName}' musí povoliť funkciu '{GlobalFeatureName}'." + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs index 7e90bd4fe7..fa963aef5e 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Modeling; using Volo.Abp.MultiTenancy; +using Volo.Abp.Reflection; using Volo.Abp.Threading; using Volo.Abp.Tracing; @@ -142,17 +143,8 @@ namespace Volo.Abp.Http.Client.DynamicProxying protected virtual bool TypeMatches(MethodParameterApiDescriptionModel actionParameter, ParameterInfo methodParameter) { - return NormalizeTypeName(actionParameter.TypeAsString) == - NormalizeTypeName(methodParameter.ParameterType.GetFullNameWithAssemblyName()); + return actionParameter.Type.ToUpper() == TypeHelper.GetFullNameHandlingNullableAndGenerics(methodParameter.ParameterType).ToUpper(); } - protected virtual string NormalizeTypeName(string typeName) - { - const string placeholder = "%COREFX%"; - const string netCoreLib = "System.Private.CoreLib"; - const string netFxLib = "mscorlib"; - - return typeName.Replace(netCoreLib, placeholder).Replace(netFxLib, placeholder); - } } } 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 78773cc364..9f41142e15 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 @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; using Volo.Abp.Content; using Volo.Abp.DependencyInjection; using Volo.Abp.DynamicProxy; @@ -34,7 +35,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying protected AbpCorrelationIdOptions AbpCorrelationIdOptions { get; } protected IDynamicProxyHttpClientFactory HttpClientFactory { get; } protected IApiDescriptionFinder ApiDescriptionFinder { get; } - protected AbpRemoteServiceOptions AbpRemoteServiceOptions { get; } + protected IRemoteServiceConfigurationProvider RemoteServiceConfigurationProvider { get; } protected AbpHttpClientOptions ClientOptions { get; } protected IJsonSerializer JsonSerializer { get; } protected IRemoteServiceHttpClientAuthenticator ClientAuthenticator { get; } @@ -51,25 +52,25 @@ namespace Volo.Abp.Http.Client.DynamicProxying public DynamicHttpProxyInterceptor( IDynamicProxyHttpClientFactory httpClientFactory, IOptions clientOptions, - IOptionsSnapshot remoteServiceOptions, IApiDescriptionFinder apiDescriptionFinder, IJsonSerializer jsonSerializer, IRemoteServiceHttpClientAuthenticator clientAuthenticator, ICancellationTokenProvider cancellationTokenProvider, ICorrelationIdProvider correlationIdProvider, IOptions correlationIdOptions, - ICurrentTenant currentTenant) + ICurrentTenant currentTenant, + IRemoteServiceConfigurationProvider remoteServiceConfigurationProvider) { CancellationTokenProvider = cancellationTokenProvider; CorrelationIdProvider = correlationIdProvider; CurrentTenant = currentTenant; + RemoteServiceConfigurationProvider = remoteServiceConfigurationProvider; AbpCorrelationIdOptions = correlationIdOptions.Value; HttpClientFactory = httpClientFactory; ApiDescriptionFinder = apiDescriptionFinder; JsonSerializer = jsonSerializer; ClientAuthenticator = clientAuthenticator; ClientOptions = clientOptions.Value; - AbpRemoteServiceOptions = remoteServiceOptions.Value; Logger = NullLogger>.Instance; } @@ -112,7 +113,11 @@ namespace Volo.Abp.Http.Client.DynamicProxying /* returning a class that holds a reference to response * content just to be sure that GC does not dispose of * it before we finish doing our work with the stream */ - return (T)(object)new RemoteStreamContent(await responseContent.ReadAsStreamAsync(), responseContent.Headers.ContentType?.ToString(), responseContent.Headers.ContentLength); + return (T)(object)new RemoteStreamContent( + await responseContent.ReadAsStreamAsync(), + responseContent.Headers?.ContentDisposition?.FileNameStar ?? RemoveQuotes(responseContent.Headers?.ContentDisposition?.FileName).ToString(), + responseContent.Headers.ContentType?.ToString(), + responseContent.Headers.ContentLength); } var stringContent = await responseContent.ReadAsStringAsync(); @@ -132,7 +137,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying private async Task MakeRequestAsync(IAbpMethodInvocation invocation) { var clientConfig = ClientOptions.HttpClientProxies.GetOrDefault(typeof(TService)) ?? throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {typeof(TService).FullName}."); - var remoteServiceConfig = AbpRemoteServiceOptions.RemoteServices.GetConfigurationOrDefault(clientConfig.RemoteServiceName); + var remoteServiceConfig = await RemoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync(clientConfig.RemoteServiceName); var client = HttpClientFactory.Create(clientConfig.RemoteServiceName); @@ -143,7 +148,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying invocation.Method ); - var apiVersion = GetApiVersionInfo(action); + var apiVersion = await GetApiVersionInfoAsync(action); var url = remoteServiceConfig.BaseUrl.EnsureEndsWith('/') + UrlBuilder.GenerateUrlWithParameters(action, invocation.ArgumentsDictionary, apiVersion); var requestMessage = new HttpRequestMessage(action.GetHttpMethod(), url) @@ -153,14 +158,17 @@ namespace Volo.Abp.Http.Client.DynamicProxying AddHeaders(invocation, action, requestMessage, apiVersion); - await ClientAuthenticator.Authenticate( - new RemoteServiceHttpClientAuthenticateContext( - client, - requestMessage, - remoteServiceConfig, - clientConfig.RemoteServiceName - ) - ); + if (action.AllowAnonymous != true) + { + await ClientAuthenticator.Authenticate( + new RemoteServiceHttpClientAuthenticateContext( + client, + requestMessage, + remoteServiceConfig, + clientConfig.RemoteServiceName + ) + ); + } var response = await client.SendAsync( requestMessage, @@ -176,9 +184,9 @@ namespace Volo.Abp.Http.Client.DynamicProxying return response.Content; } - private ApiVersionInfo GetApiVersionInfo(ActionApiDescriptionModel action) + private async Task GetApiVersionInfoAsync(ActionApiDescriptionModel action) { - var apiVersion = FindBestApiVersion(action); + var apiVersion = await FindBestApiVersionAsync(action); //TODO: Make names configurable? var versionParam = action.Parameters.FirstOrDefault(p => p.Name == "apiVersion" && p.BindingSourceId == ParameterBindingSources.Path) ?? @@ -187,9 +195,9 @@ namespace Volo.Abp.Http.Client.DynamicProxying return new ApiVersionInfo(versionParam?.BindingSourceId, apiVersion); } - private string FindBestApiVersion(ActionApiDescriptionModel action) + private async Task FindBestApiVersionAsync(ActionApiDescriptionModel action) { - var configuredVersion = GetConfiguredApiVersion(); + var configuredVersion = await GetConfiguredApiVersionAsync(); if (action.SupportedVersions.IsNullOrEmpty()) { @@ -252,13 +260,13 @@ namespace Volo.Abp.Http.Client.DynamicProxying requestMessage.Headers.Add("X-Requested-With", "XMLHttpRequest"); } - private string GetConfiguredApiVersion() + private async Task GetConfiguredApiVersionAsync() { var clientConfig = ClientOptions.HttpClientProxies.GetOrDefault(typeof(TService)) ?? throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {typeof(TService).FullName}."); - return AbpRemoteServiceOptions.RemoteServices.GetOrDefault(clientConfig.RemoteServiceName)?.Version - ?? AbpRemoteServiceOptions.RemoteServices.Default?.Version; + return (await RemoteServiceConfigurationProvider + .GetConfigurationOrDefaultOrNullAsync(clientConfig.RemoteServiceName))?.Version; } private async Task ThrowExceptionForResponseAsync(HttpResponseMessage response) @@ -287,6 +295,16 @@ namespace Volo.Abp.Http.Client.DynamicProxying }; } + protected virtual StringSegment RemoveQuotes(StringSegment input) + { + if (!StringSegment.IsNullOrEmpty(input) && input.Length >= 2 && input[0] == '"' && input[input.Length - 1] == '"') + { + input = input.Subsegment(1, input.Length - 2); + } + + return input; + } + protected virtual CancellationToken GetCancellationToken() { return CancellationTokenProvider.Token; diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/RequestPayloadBuilder.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/RequestPayloadBuilder.cs index 47b2558f4c..7893073442 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/RequestPayloadBuilder.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/RequestPayloadBuilder.cs @@ -86,9 +86,9 @@ namespace Volo.Abp.Http.Client.DynamicProxying if (!remoteStreamContent.ContentType.IsNullOrWhiteSpace()) { streamContent.Headers.ContentType = new MediaTypeHeaderValue(remoteStreamContent.ContentType); - } - streamContent.Headers.ContentLength = stream.GetNullableLength() - stream.GetNullablePosition(); - formData.Add(streamContent, parameter.Name, parameter.Name); + } + streamContent.Headers.ContentLength = stream.GetNullableLength() - stream.GetNullablePosition(); + formData.Add(streamContent, parameter.Name, remoteStreamContent.FileName ?? parameter.Name); } else if (value is IEnumerable remoteStreamContents) { @@ -99,9 +99,9 @@ namespace Volo.Abp.Http.Client.DynamicProxying if (!content.ContentType.IsNullOrWhiteSpace()) { streamContent.Headers.ContentType = new MediaTypeHeaderValue(content.ContentType); - } - streamContent.Headers.ContentLength = stream.GetNullableLength() - stream.GetNullablePosition(); - formData.Add(streamContent, parameter.Name, parameter.Name); + } + streamContent.Headers.ContentLength = stream.GetNullableLength() - stream.GetNullablePosition(); + formData.Add(streamContent, parameter.Name, content.FileName ?? parameter.Name); } } else diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/IRemoteServiceConfigurationProvider.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/IRemoteServiceConfigurationProvider.cs new file mode 100644 index 0000000000..b6a2b040f9 --- /dev/null +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/IRemoteServiceConfigurationProvider.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Volo.Abp.Http.Client +{ + public interface IRemoteServiceConfigurationProvider + { + [ItemNotNull] + Task GetConfigurationOrDefaultAsync(string name); + + [ItemCanBeNull] + Task GetConfigurationOrDefaultOrNullAsync(string name); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/RemoteServiceConfigurationDictionary.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/RemoteServiceConfigurationDictionary.cs index 248285d748..fa76ec76b1 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/RemoteServiceConfigurationDictionary.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/RemoteServiceConfigurationDictionary.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using JetBrains.Annotations; namespace Volo.Abp.Http.Client { @@ -12,11 +13,19 @@ namespace Volo.Abp.Http.Client set => this[DefaultName] = value; } + [NotNull] public RemoteServiceConfiguration GetConfigurationOrDefault(string name) { return this.GetOrDefault(name) ?? Default ?? throw new AbpException($"Remote service '{name}' was not found and there is no default configuration."); } + + [CanBeNull] + public RemoteServiceConfiguration GetConfigurationOrDefaultOrNull(string name) + { + return this.GetOrDefault(name) + ?? Default; + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/RemoteServiceConfigurationProvider.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/RemoteServiceConfigurationProvider.cs new file mode 100644 index 0000000000..c14ea40aa3 --- /dev/null +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/RemoteServiceConfigurationProvider.cs @@ -0,0 +1,26 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Http.Client +{ + public class RemoteServiceConfigurationProvider : IRemoteServiceConfigurationProvider, IScopedDependency + { + protected AbpRemoteServiceOptions Options { get; } + + public RemoteServiceConfigurationProvider(IOptionsSnapshot options) + { + Options = options.Value; + } + + public Task GetConfigurationOrDefaultAsync(string name) + { + return Task.FromResult(Options.RemoteServices.GetConfigurationOrDefault(name)); + } + + public Task GetConfigurationOrDefaultOrNullAsync(string name) + { + return Task.FromResult(Options.RemoteServices.GetConfigurationOrDefaultOrNull(name)); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ActionApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ActionApiDescriptionModel.cs index 1ca0c28aa1..2901161259 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ActionApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ActionApiDescriptionModel.cs @@ -26,12 +26,14 @@ namespace Volo.Abp.Http.Modeling public ReturnValueApiDescriptionModel ReturnValue { get; set; } + public bool? AllowAnonymous { get; set; } + public ActionApiDescriptionModel() { } - public static ActionApiDescriptionModel Create([NotNull] string uniqueName, [NotNull] MethodInfo method, [NotNull] string url, [CanBeNull] string httpMethod, [NotNull] IList supportedVersions) + public static ActionApiDescriptionModel Create([NotNull] string uniqueName, [NotNull] MethodInfo method, [NotNull] string url, [CanBeNull] string httpMethod, [NotNull] IList supportedVersions, bool? allowAnonymous = null) { Check.NotNull(uniqueName, nameof(uniqueName)); Check.NotNull(method, nameof(method)); @@ -50,7 +52,8 @@ namespace Volo.Abp.Http.Modeling .GetParameters() .Select(MethodParameterApiDescriptionModel.Create) .ToList(), - SupportedVersions = supportedVersions + SupportedVersions = supportedVersions, + AllowAnonymous = allowAnonymous }; } diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/ProxyScriptManager.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/ProxyScriptManager.cs index c619e5483c..c5d66f0a49 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/ProxyScriptManager.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/ProxyScriptManager.cs @@ -64,7 +64,10 @@ namespace Volo.Abp.Http.ProxyScripting using (var scope = _serviceProvider.CreateScope()) { - return scope.ServiceProvider.GetRequiredService(generatorType).As().CreateScript(apiModel); + return scope.ServiceProvider + .GetRequiredService(generatorType) + .As() + .CreateScript(apiModel); } } diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/AbpKafkaOptions.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/AbpKafkaOptions.cs index 26d15ce818..f6679aec3c 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/AbpKafkaOptions.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/AbpKafkaOptions.cs @@ -14,8 +14,6 @@ namespace Volo.Abp.Kafka public Action ConfigureTopic { get; set; } - public bool ReQueue { get; set; } = true; - public AbpKafkaOptions() { Connections = new KafkaConnections(); diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaMessageConsumerFactory.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaMessageConsumerFactory.cs index 2b01b5a935..96ec753dc2 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaMessageConsumerFactory.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaMessageConsumerFactory.cs @@ -8,11 +8,13 @@ /// not disposed until end of the application. /// /// + /// /// /// /// IKafkaMessageConsumer Create( string topicName, + string deadLetterTopicName, string groupId, string connectionName = null); } diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaSerializer.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaSerializer.cs index 58e718831c..a283eb7e50 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaSerializer.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/IKafkaSerializer.cs @@ -7,5 +7,7 @@ namespace Volo.Abp.Kafka byte[] Serialize(object obj); object Deserialize(byte[] value, Type type); + + T Deserialize(byte[] value); } } diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs index 3b8f022012..d1bad00533 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Confluent.Kafka; @@ -26,6 +27,8 @@ namespace Volo.Abp.Kafka protected AbpKafkaOptions Options { get; } + protected AbpAsyncTimer Timer { get; } + protected ConcurrentBag, Task>> Callbacks { get; } protected IConsumer Consumer { get; private set; } @@ -36,34 +39,43 @@ namespace Volo.Abp.Kafka protected string TopicName { get; private set; } + protected string DeadLetterTopicName { get; private set; } + public KafkaMessageConsumer( IConsumerPool consumerPool, IExceptionNotifier exceptionNotifier, IOptions options, - IProducerPool producerPool) + IProducerPool producerPool, + AbpAsyncTimer timer) { ConsumerPool = consumerPool; ExceptionNotifier = exceptionNotifier; ProducerPool = producerPool; + Timer = timer; Options = options.Value; Logger = NullLogger.Instance; Callbacks = new ConcurrentBag, Task>>(); + + Timer.Period = 5000; //5 sec. + Timer.Elapsed = Timer_Elapsed; + Timer.RunOnStart = true; } public virtual void Initialize( [NotNull] string topicName, + [NotNull] string deadLetterTopicName, [NotNull] string groupId, string connectionName = null) { Check.NotNull(topicName, nameof(topicName)); + Check.NotNull(deadLetterTopicName, nameof(deadLetterTopicName)); Check.NotNull(groupId, nameof(groupId)); TopicName = topicName; + DeadLetterTopicName = deadLetterTopicName; ConnectionName = connectionName ?? KafkaConnections.DefaultConnectionName; GroupId = groupId; - - AsyncHelper.RunSync(CreateTopicAsync); - Consume(); + Timer.Start(); } public virtual void OnMessageReceived(Func, Task> callback) @@ -71,26 +83,45 @@ namespace Volo.Abp.Kafka Callbacks.Add(callback); } + protected virtual async Task Timer_Elapsed(AbpAsyncTimer timer) + { + await CreateTopicAsync(); + Consume(); + Timer.Stop(); + } + protected virtual async Task CreateTopicAsync() { using (var adminClient = new AdminClientBuilder(Options.Connections.GetOrDefault(ConnectionName)).Build()) { - var topic = new TopicSpecification + var topics = new List { - Name = TopicName, - NumPartitions = 1, - ReplicationFactor = 1 + new() + { + Name = TopicName, + NumPartitions = 1, + ReplicationFactor = 1 + }, + new() + { + Name = DeadLetterTopicName, + NumPartitions = 1, + ReplicationFactor = 1 + } }; - Options.ConfigureTopic?.Invoke(topic); + topics.ForEach(topic => + { + Options.ConfigureTopic?.Invoke(topic); + }); try { - await adminClient.CreateTopicsAsync(new[] {topic}); + await adminClient.CreateTopicsAsync(topics); } catch (CreateTopicsException e) { - if(e.Results.First().Error.Code != ErrorCode.TopicAlreadyExists) + if(e.Results.Any(x => x.Error.Code != ErrorCode.TopicAlreadyExists)) { throw; } @@ -139,8 +170,6 @@ namespace Volo.Abp.Kafka } catch (Exception ex) { - await RequeueAsync(consumeResult); - Logger.LogException(ex); await ExceptionNotifier.NotifyAsync(ex); } @@ -150,17 +179,6 @@ namespace Volo.Abp.Kafka } } - protected virtual async Task RequeueAsync(ConsumeResult consumeResult) - { - if (!Options.ReQueue) - { - return; - } - - var producer = ProducerPool.Get(ConnectionName); - await producer.ProduceAsync(consumeResult.Topic, consumeResult.Message); - } - public virtual void Dispose() { if (Consumer == null) diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumerFactory.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumerFactory.cs index fb08aa4144..4a22fd04f6 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumerFactory.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumerFactory.cs @@ -16,11 +16,12 @@ namespace Volo.Abp.Kafka public IKafkaMessageConsumer Create( string topicName, + string deadLetterTopicName, string groupId, string connectionName = null) { var consumer = ServiceScope.ServiceProvider.GetRequiredService(); - consumer.Initialize(topicName, groupId, connectionName); + consumer.Initialize(topicName, deadLetterTopicName, groupId, connectionName); return consumer; } diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/Utf8JsonKafkaSerializer.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/Utf8JsonKafkaSerializer.cs index a04125f8a6..a8a199c140 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/Utf8JsonKafkaSerializer.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/Utf8JsonKafkaSerializer.cs @@ -23,5 +23,10 @@ namespace Volo.Abp.Kafka { return _jsonSerializer.Deserialize(type, Encoding.UTF8.GetString(value)); } + + public T Deserialize(byte[] value) + { + return _jsonSerializer.Deserialize(Encoding.UTF8.GetString(value)); + } } } 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 7a8d06746a..c9be1063d1 100644 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapManager.cs +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapManager.cs @@ -12,11 +12,11 @@ namespace Volo.Abp.Ldap public class LdapManager : ILdapManager, ITransientDependency { public ILogger Logger { get; set; } - protected AbpLdapOptions LdapOptions { get; } + protected IOptions LdapOptions { get; } - public LdapManager(IOptions ldapSettingsOptions) + public LdapManager(IOptions ldapOptions) { - LdapOptions = ldapSettingsOptions.Value; + LdapOptions = ldapOptions; Logger = NullLogger.Instance; } @@ -50,11 +50,11 @@ namespace Volo.Abp.Ldap return Task.CompletedTask; } - protected virtual Task ConnectAsync(ILdapConnection ldapConnection) + protected virtual async Task ConnectAsync(ILdapConnection ldapConnection) { - ldapConnection.Connect(LdapOptions.ServerHost, LdapOptions.ServerPort); + await LdapOptions.SetAsync(); - return Task.CompletedTask; + ldapConnection.Connect(LdapOptions.Value.ServerHost, LdapOptions.Value.ServerPort); } protected virtual async Task AuthenticateLdapConnectionAsync(ILdapConnection connection, string username, string password) diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/it.json b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/it.json index 61fd573b09..c9055d35ef 100644 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/it.json +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/it.json @@ -1,15 +1,15 @@ { "culture": "it", "texts": { - "DisplayName:Abp.Ldap.ServerHost": "Host del server", - "Description:Abp.Ldap.ServerHost": "Host del server", + "DisplayName:Abp.Ldap.ServerHost": "Server host", + "Description:Abp.Ldap.ServerHost": "Server host", "DisplayName:Abp.Ldap.ServerPort": "Porta del server", "Description:Abp.Ldap.ServerPort": "Porta del server", "DisplayName:Abp.Ldap.BaseDc": "Componente del dominio di base", "Description:Abp.Ldap.BaseDc": "Componente del dominio di base", "DisplayName:Abp.Ldap.UserName": "Nome utente", "Description:Abp.Ldap.UserName": "Nome utente", - "DisplayName:Abp.Ldap.Password": "Parola d'ordine", - "Description:Abp.Ldap.Password": "Parola d'ordine" + "DisplayName:Abp.Ldap.Password": "Password", + "Description:Abp.Ldap.Password": "Password" } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/sk.json b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/sk.json new file mode 100644 index 0000000000..9c9195c4eb --- /dev/null +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/Localization/sk.json @@ -0,0 +1,15 @@ +{ + "culture": "sk", + "texts": { + "DisplayName:Abp.Ldap.ServerHost": "Server host", + "Description:Abp.Ldap.ServerHost": "Server host", + "DisplayName:Abp.Ldap.ServerPort": "Server port", + "Description:Abp.Ldap.ServerPort": "Server post", + "DisplayName:Abp.Ldap.BaseDc": "Základný komponent domény", + "Description:Abp.Ldap.BaseDc": "Základný komponent domény", + "DisplayName:Abp.Ldap.UserName": "Používateľské meno", + "Description:Abp.Ldap.UserName": "Používateľské meno", + "DisplayName:Abp.Ldap.Password": "Heslo", + "Description:Abp.Ldap.Password": "Heslo" + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Resources/AbpLocalization/sk.json b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Resources/AbpLocalization/sk.json new file mode 100644 index 0000000000..1425db11bf --- /dev/null +++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Resources/AbpLocalization/sk.json @@ -0,0 +1,7 @@ +{ + "culture": "sk", + "texts": { + "DisplayName:Abp.Localization.DefaultLanguage": "Predvolený jazyk", + "Description:Abp.Localization.DefaultLanguage": "Predvolený jazyk aplikácie." + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.MemoryDb/Microsoft/Extensions/DependencyInjection/AbpMemoryDbServiceCollectionExtensions.cs b/framework/src/Volo.Abp.MemoryDb/Microsoft/Extensions/DependencyInjection/AbpMemoryDbServiceCollectionExtensions.cs index 3cab95cb03..99ef1bd445 100644 --- a/framework/src/Volo.Abp.MemoryDb/Microsoft/Extensions/DependencyInjection/AbpMemoryDbServiceCollectionExtensions.cs +++ b/framework/src/Volo.Abp.MemoryDb/Microsoft/Extensions/DependencyInjection/AbpMemoryDbServiceCollectionExtensions.cs @@ -18,9 +18,17 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAddSingleton(options.DefaultRepositoryDbContextType, sp => sp.GetRequiredService()); } - foreach (var dbContextType in options.ReplacedDbContextTypes) + foreach (var entry in options.ReplacedDbContextTypes) { - services.Replace(ServiceDescriptor.Singleton(dbContextType, sp => sp.GetRequiredService())); + var originalDbContextType = entry.Key; + var targetDbContextType = entry.Value ?? typeof(TMemoryDbContext); + + services.Replace( + ServiceDescriptor.Singleton( + originalDbContextType, + sp => sp.GetRequiredService(targetDbContextType) + ) + ); } new MemoryDbRepositoryRegistrar(options).AddRepositories(); diff --git a/framework/src/Volo.Abp.MongoDB/Microsoft/Extensions/DependencyInjection/AbpMongoDbServiceCollectionExtensions.cs b/framework/src/Volo.Abp.MongoDB/Microsoft/Extensions/DependencyInjection/AbpMongoDbServiceCollectionExtensions.cs index 7f9c6b8d42..c05dbe5b3a 100644 --- a/framework/src/Volo.Abp.MongoDB/Microsoft/Extensions/DependencyInjection/AbpMongoDbServiceCollectionExtensions.cs +++ b/framework/src/Volo.Abp.MongoDB/Microsoft/Extensions/DependencyInjection/AbpMongoDbServiceCollectionExtensions.cs @@ -25,23 +25,21 @@ namespace Microsoft.Extensions.DependencyInjection optionsBuilder?.Invoke(options); - foreach (var dbContextType in options.ReplacedDbContextTypes) - { - services.Replace(ServiceDescriptor.Transient(dbContextType, typeof(TMongoDbContext))); - } - - foreach (var dbContextType in options.ReplacedDbContextTypes) + foreach (var entry in options.ReplacedDbContextTypes) { + var originalDbContextType = entry.Key; + var targetDbContextType = entry.Value ?? typeof(TMongoDbContext); + services.Replace( ServiceDescriptor.Transient( - dbContextType, - sp => sp.GetRequiredService(typeof(TMongoDbContext)) + originalDbContextType, + sp => sp.GetRequiredService(targetDbContextType) ) ); services.Configure(opts => { - opts.DbContextReplacements[dbContextType] = typeof(TMongoDbContext); + opts.DbContextReplacements[originalDbContextType] = targetDbContextType; }); } diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoDbAsyncQueryableProvider.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoDbAsyncQueryableProvider.cs index 52c5edd1d3..9e404ac863 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoDbAsyncQueryableProvider.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoDbAsyncQueryableProvider.cs @@ -29,6 +29,11 @@ namespace Volo.Abp.MongoDB return Task.FromResult(GetMongoQueryable(queryable).Contains(item)); } + public Task AnyAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + return GetMongoQueryable(queryable).AnyAsync(cancellationToken); + } + public Task AnyAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) { return GetMongoQueryable(queryable).AnyAsync(predicate, cancellationToken); diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/MultiTenantConnectionStringResolver.cs b/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/MultiTenantConnectionStringResolver.cs index 41ba7873ac..631646eaca 100644 --- a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/MultiTenantConnectionStringResolver.cs +++ b/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/MultiTenantConnectionStringResolver.cs @@ -41,7 +41,7 @@ namespace Volo.Abp.MultiTenancy } var tenantDefaultConnectionString = tenant.ConnectionStrings.Default; - + //Requesting default connection string... if (connectionStringName == null || connectionStringName == ConnectionStrings.DefaultConnectionStringName) @@ -59,10 +59,10 @@ namespace Volo.Abp.MultiTenancy //Found for the tenant return connString; } - + //Fallback to the mapped database for the specific connection string var database = Options.Databases.GetMappedDatabaseOrNull(connectionStringName); - if (database != null) + if (database != null && database.IsUsedByTenants) { connString = tenant.ConnectionStrings.GetOrDefault(database.DatabaseName); if (!connString.IsNullOrWhiteSpace()) @@ -166,4 +166,4 @@ namespace Volo.Abp.MultiTenancy } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/ExtraPropertyDictionaryExtensions.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/ExtraPropertyDictionaryExtensions.cs new file mode 100644 index 0000000000..0c6b9dff67 --- /dev/null +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/ExtraPropertyDictionaryExtensions.cs @@ -0,0 +1,30 @@ +using System; + +namespace Volo.Abp.Data +{ + public static class ExtraPropertyDictionaryExtensions + { + public static T ToEnum(this ExtraPropertyDictionary extraPropertyDictionary, string key) + where T : Enum + { + if (extraPropertyDictionary[key].GetType() == typeof(T)) + { + return (T)extraPropertyDictionary[key]; + } + + extraPropertyDictionary[key] = Enum.Parse(typeof(T), extraPropertyDictionary[key].ToString(), ignoreCase: true); + return (T)extraPropertyDictionary[key]; + } + + public static object ToEnum(this ExtraPropertyDictionary extraPropertyDictionary, string key, Type enumType) + { + if (!enumType.IsEnum || extraPropertyDictionary[key].GetType() == enumType) + { + return extraPropertyDictionary[key]; + } + + extraPropertyDictionary[key] = Enum.Parse(enumType, extraPropertyDictionary[key].ToString(), ignoreCase: true); + return extraPropertyDictionary[key]; + } + } +} diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs index 237953db4a..7b71386474 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs @@ -24,7 +24,7 @@ namespace Volo.Abp.ObjectExtending public ObjectExtensionInfo([NotNull] Type type) { - Type = Check.AssignableTo(type, nameof(type)); + Type = Check.NotNull(type, nameof(type)); Properties = new ConcurrentDictionary(); Configuration = new ConcurrentDictionary(); Validators = new List>(); diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionManager.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionManager.cs index 4cbc3a3fb8..85a60a2824 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionManager.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionManager.cs @@ -25,7 +25,6 @@ namespace Volo.Abp.ObjectExtending [NotNull] public virtual ObjectExtensionManager AddOrUpdate( [CanBeNull] Action configureAction = null) - where TObject : IHasExtraProperties { return AddOrUpdate(typeof(TObject), configureAction); } @@ -50,8 +49,6 @@ namespace Volo.Abp.ObjectExtending [NotNull] Type type, [CanBeNull] Action configureAction = null) { - Check.AssignableTo(type, nameof(type)); - var extensionInfo = ObjectsExtensions.GetOrAdd( type, _ => new ObjectExtensionInfo(type) @@ -64,7 +61,6 @@ namespace Volo.Abp.ObjectExtending [CanBeNull] public virtual ObjectExtensionInfo GetOrNull() - where TObject : IHasExtraProperties { return GetOrNull(typeof(TObject)); } @@ -72,8 +68,6 @@ namespace Volo.Abp.ObjectExtending [CanBeNull] public virtual ObjectExtensionInfo GetOrNull([NotNull] Type type) { - Check.AssignableTo(type, nameof(type)); - return ObjectsExtensions.GetOrDefault(type); } diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ExchangeDeclareConfiguration.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ExchangeDeclareConfiguration.cs index 67f234aec4..b9e762abbe 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ExchangeDeclareConfiguration.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ExchangeDeclareConfiguration.cs @@ -6,6 +6,8 @@ namespace Volo.Abp.RabbitMQ { public string ExchangeName { get; } + public string DeadLetterExchangeName { get; set; } + public string Type { get; } public bool Durable { get; set; } @@ -15,16 +17,18 @@ namespace Volo.Abp.RabbitMQ public IDictionary Arguments { get; } public ExchangeDeclareConfiguration( - string exchangeName, - string type, - bool durable = false, - bool autoDelete = false) + string exchangeName, + string type, + bool durable = false, + bool autoDelete = false, + string deadLetterExchangeName = null) { ExchangeName = exchangeName; + DeadLetterExchangeName = deadLetterExchangeName; Type = type; Durable = durable; AutoDelete = autoDelete; Arguments = new Dictionary(); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IRabbitMqSerializer.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IRabbitMqSerializer.cs index 771d1a05a6..2de5cd2191 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IRabbitMqSerializer.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/IRabbitMqSerializer.cs @@ -7,5 +7,7 @@ namespace Volo.Abp.RabbitMQ byte[] Serialize(object obj); object Deserialize(byte[] value, Type type); + + T Deserialize(byte[] value); } } diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs index 8cc07b7bb9..b84f08ec42 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/QueueDeclareConfiguration.cs @@ -6,8 +6,9 @@ namespace Volo.Abp.RabbitMQ { public class QueueDeclareConfiguration { - [NotNull] - public string QueueName { get; } + [NotNull] public string QueueName { get; } + + public string DeadLetterQueueName { get; set; } public bool Durable { get; set; } @@ -18,12 +19,14 @@ namespace Volo.Abp.RabbitMQ public IDictionary Arguments { get; } public QueueDeclareConfiguration( - [NotNull] string queueName, - bool durable = true, - bool exclusive = false, - bool autoDelete = false) + [NotNull] string queueName, + bool durable = true, + bool exclusive = false, + bool autoDelete = false, + string deadLetterQueueName = null) { QueueName = queueName; + DeadLetterQueueName = deadLetterQueueName; Durable = durable; Exclusive = exclusive; AutoDelete = autoDelete; @@ -41,4 +44,4 @@ namespace Volo.Abp.RabbitMQ ); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs index 894f68d097..671445e00d 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs @@ -6,6 +6,7 @@ using RabbitMQ.Client.Events; using System; using System.Collections.Concurrent; using System.Threading.Tasks; +using RabbitMQ.Client.Exceptions; using Volo.Abp.DependencyInjection; using Volo.Abp.ExceptionHandling; using Volo.Abp.Threading; @@ -147,6 +148,7 @@ namespace Volo.Abp.RabbitMQ Channel = ConnectionPool .Get(ConnectionName) .CreateModel(); + Channel.ExchangeDeclare( exchange: Exchange.ExchangeName, type: Exchange.Type, @@ -155,7 +157,29 @@ namespace Volo.Abp.RabbitMQ arguments: Exchange.Arguments ); - Channel.QueueDeclare( + if (!Exchange.DeadLetterExchangeName.IsNullOrWhiteSpace() && + !Queue.DeadLetterQueueName.IsNullOrWhiteSpace()) + { + Channel.ExchangeDeclare( + Exchange.DeadLetterExchangeName, + Exchange.Type, + Exchange.Durable, + Exchange.AutoDelete + ); + + Channel.QueueDeclare( + Queue.DeadLetterQueueName, + Queue.Durable, + Queue.Exclusive, + Queue.AutoDelete); + + Queue.Arguments["x-dead-letter-exchange"] = Exchange.DeadLetterExchangeName; + Queue.Arguments["x-dead-letter-routing-key"] = Queue.DeadLetterQueueName; + + Channel.QueueBind(Queue.DeadLetterQueueName, Exchange.DeadLetterExchangeName, Queue.DeadLetterQueueName); + } + + var result = Channel.QueueDeclare( queue: Queue.QueueName, durable: Queue.Durable, exclusive: Queue.Exclusive, @@ -174,6 +198,17 @@ namespace Volo.Abp.RabbitMQ } catch (Exception ex) { + if (ex is OperationInterruptedException operationInterruptedException && + operationInterruptedException.ShutdownReason.ReplyCode == 406 && + operationInterruptedException.Message.Contains("arg 'x-dead-letter-exchange'")) + { + Exchange.DeadLetterExchangeName = null; + Queue.DeadLetterQueueName = null; + Queue.Arguments.Remove("x-dead-letter-exchange"); + Queue.Arguments.Remove("x-dead-letter-routing-key"); + Logger.LogWarning("Unable to bind the dead letter queue to an existing queue. You can delete the queue or add policy. See: https://www.rabbitmq.com/parameters.html"); + } + Logger.LogException(ex, LogLevel.Warning); await ExceptionNotifier.NotifyAsync(ex, logLevel: LogLevel.Warning); } @@ -194,14 +229,10 @@ namespace Volo.Abp.RabbitMQ { try { - Channel.BasicNack( - basicDeliverEventArgs.DeliveryTag, - multiple: false, - requeue: true - ); + Channel.BasicReject(basicDeliverEventArgs.DeliveryTag, false); } catch { } - + Logger.LogException(ex); await ExceptionNotifier.NotifyAsync(ex); } diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/Utf8JsonRabbitMqSerializer.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/Utf8JsonRabbitMqSerializer.cs index c179193fdd..cc815f686a 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/Utf8JsonRabbitMqSerializer.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/Utf8JsonRabbitMqSerializer.cs @@ -23,5 +23,10 @@ namespace Volo.Abp.RabbitMQ { return _jsonSerializer.Deserialize(type, Encoding.UTF8.GetString(value)); } + + public T Deserialize(byte[] value) + { + return _jsonSerializer.Deserialize(Encoding.UTF8.GetString(value)); + } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs b/framework/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs index cf2169883c..83a587cc25 100644 --- a/framework/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs +++ b/framework/src/Volo.Abp.Security/System/Security/Principal/AbpClaimsIdentityExtensions.cs @@ -17,10 +17,12 @@ namespace System.Security.Principal { return null; } - if (Guid.TryParse(userIdOrNull.Value, out Guid result)) + + if (Guid.TryParse(userIdOrNull.Value, out Guid guid)) { - return result; + return guid; } + return null; } @@ -36,7 +38,12 @@ namespace System.Security.Principal return null; } - return Guid.Parse(userIdOrNull.Value); + if (Guid.TryParse(userIdOrNull.Value, out var guid)) + { + return guid; + } + + return null; } public static Guid? FindTenantId([NotNull] this ClaimsPrincipal principal) @@ -49,7 +56,12 @@ namespace System.Security.Principal return null; } - return Guid.Parse(tenantIdOrNull.Value); + if (Guid.TryParse(tenantIdOrNull.Value, out var guid)) + { + return guid; + } + + return null; } public static Guid? FindTenantId([NotNull] this IIdentity identity) @@ -64,7 +76,12 @@ namespace System.Security.Principal return null; } - return Guid.Parse(tenantIdOrNull.Value); + if (Guid.TryParse(tenantIdOrNull.Value, out var guid)) + { + return guid; + } + + return null; } public static string FindClientId([NotNull] this ClaimsPrincipal principal) @@ -105,7 +122,12 @@ namespace System.Security.Principal return null; } - return Guid.Parse(editionIdOrNull.Value); + if (Guid.TryParse(editionIdOrNull.Value, out var guid)) + { + return guid; + } + + return null; } public static Guid? FindEditionId([NotNull] this IIdentity identity) @@ -120,7 +142,88 @@ namespace System.Security.Principal return null; } - return Guid.Parse(editionIdOrNull.Value); + if (Guid.TryParse(editionIdOrNull.Value, out var guid)) + { + return guid; + } + + return null; + } + + public static Guid? FindImpersonatorTenantId([NotNull] this ClaimsPrincipal principal) + { + Check.NotNull(principal, nameof(principal)); + + var impersonatorTenantIdOrNull = principal.Claims?.FirstOrDefault(c => c.Type == AbpClaimTypes.ImpersonatorTenantId); + if (impersonatorTenantIdOrNull == null || impersonatorTenantIdOrNull.Value.IsNullOrWhiteSpace()) + { + return null; + } + + if (Guid.TryParse(impersonatorTenantIdOrNull.Value, out var guid)) + { + return guid; + } + + return null; + } + + public static Guid? FindImpersonatorTenantId([NotNull] this IIdentity identity) + { + Check.NotNull(identity, nameof(identity)); + + var claimsIdentity = identity as ClaimsIdentity; + + var impersonatorTenantIdOrNull = claimsIdentity?.Claims?.FirstOrDefault(c => c.Type == AbpClaimTypes.ImpersonatorTenantId); + if (impersonatorTenantIdOrNull == null || impersonatorTenantIdOrNull.Value.IsNullOrWhiteSpace()) + { + return null; + } + + if (Guid.TryParse(impersonatorTenantIdOrNull.Value, out var guid)) + { + return guid; + } + + return null; + } + + public static Guid? FindImpersonatorUserId([NotNull] this ClaimsPrincipal principal) + { + Check.NotNull(principal, nameof(principal)); + + var impersonatorUserIdOrNull = principal.Claims?.FirstOrDefault(c => c.Type == AbpClaimTypes.ImpersonatorUserId); + if (impersonatorUserIdOrNull == null || impersonatorUserIdOrNull.Value.IsNullOrWhiteSpace()) + { + return null; + } + + if (Guid.TryParse(impersonatorUserIdOrNull.Value, out var guid)) + { + return guid; + } + + return null; + } + + public static Guid? FindImpersonatorUserId([NotNull] this IIdentity identity) + { + Check.NotNull(identity, nameof(identity)); + + var claimsIdentity = identity as ClaimsIdentity; + + var impersonatorUserIdOrNull = claimsIdentity?.Claims?.FirstOrDefault(c => c.Type == AbpClaimTypes.ImpersonatorUserId); + if (impersonatorUserIdOrNull == null || impersonatorUserIdOrNull.Value.IsNullOrWhiteSpace()) + { + return null; + } + + if (Guid.TryParse(impersonatorUserIdOrNull.Value, out var guid)) + { + return guid; + } + + return null; } public static ClaimsIdentity AddIfNotContains(this ClaimsIdentity claimsIdentity, Claim claim) diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs index 271d5d3d94..da87e89946 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs @@ -67,5 +67,15 @@ namespace Volo.Abp.Security.Claims /// Default: "client_id". /// public static string ClientId { get; set; } = "client_id"; + + /// + /// Default: "impersonator_tenantid". + /// + public static string ImpersonatorTenantId { get; set; } = "impersonator_tenantid"; + + /// + /// Default: "impersonator_userid". + /// + public static string ImpersonatorUserId { get; set; } = "impersonator_userid"; } } diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Users/CurrentUser.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Users/CurrentUser.cs index e769b9ec11..f0cfb4c284 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Users/CurrentUser.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Users/CurrentUser.cs @@ -31,7 +31,7 @@ namespace Volo.Abp.Users public virtual Guid? TenantId => _principalAccessor.Principal?.FindTenantId(); - public virtual string[] Roles => FindClaims(AbpClaimTypes.Role).Select(c => c.Value).ToArray(); + public virtual string[] Roles => FindClaims(AbpClaimTypes.Role).Select(c => c.Value).Distinct().ToArray(); private readonly ICurrentPrincipalAccessor _principalAccessor; diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Users/CurrentUserExtensions.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Users/CurrentUserExtensions.cs index 43032fe214..d1d75bf16d 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Users/CurrentUserExtensions.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Users/CurrentUserExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using JetBrains.Annotations; +using Volo.Abp.Security.Claims; namespace Volo.Abp.Users { @@ -30,5 +31,35 @@ namespace Volo.Abp.Users return currentUser.Id.Value; } + + public static Guid? FindImpersonatorTenantId([NotNull] this ICurrentUser currentUser) + { + var impersonatorTenantId = currentUser.FindClaimValue(AbpClaimTypes.ImpersonatorTenantId); + if (impersonatorTenantId == null || impersonatorTenantId.IsNullOrWhiteSpace()) + { + return null; + } + if (Guid.TryParse(impersonatorTenantId, out var guid)) + { + return guid; + } + + return null; + } + + public static Guid? FindImpersonatorUserId([NotNull] this ICurrentUser currentUser) + { + var impersonatorUserId = currentUser.FindClaimValue(AbpClaimTypes.ImpersonatorUserId); + if (impersonatorUserId == null || impersonatorUserId.IsNullOrWhiteSpace()) + { + return null; + } + if (Guid.TryParse(impersonatorUserId, out var guid)) + { + return guid; + } + + return null; + } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Threading/Volo/Abp/Linq/AsyncQueryableExecuter.cs b/framework/src/Volo.Abp.Threading/Volo/Abp/Linq/AsyncQueryableExecuter.cs index b7ac83dc5f..f4fd10718d 100644 --- a/framework/src/Volo.Abp.Threading/Volo/Abp/Linq/AsyncQueryableExecuter.cs +++ b/framework/src/Volo.Abp.Threading/Volo/Abp/Linq/AsyncQueryableExecuter.cs @@ -30,6 +30,14 @@ namespace Volo.Abp.Linq : Task.FromResult(queryable.Contains(item)); } + public Task AnyAsync(IQueryable queryable, CancellationToken cancellationToken = default) + { + var provider = FindProvider(queryable); + return provider != null + ? provider.AnyAsync(queryable, cancellationToken) + : Task.FromResult(queryable.Any()); + } + public Task AnyAsync(IQueryable queryable, Expression> predicate, CancellationToken cancellationToken = default) { var provider = FindProvider(queryable); diff --git a/framework/src/Volo.Abp.Threading/Volo/Abp/Linq/IAsyncQueryableExecuter.cs b/framework/src/Volo.Abp.Threading/Volo/Abp/Linq/IAsyncQueryableExecuter.cs index 7273754b77..d585122733 100644 --- a/framework/src/Volo.Abp.Threading/Volo/Abp/Linq/IAsyncQueryableExecuter.cs +++ b/framework/src/Volo.Abp.Threading/Volo/Abp/Linq/IAsyncQueryableExecuter.cs @@ -21,6 +21,10 @@ namespace Volo.Abp.Linq #region Any/All + Task AnyAsync( + [NotNull] IQueryable queryable, + CancellationToken cancellationToken = default); + Task AnyAsync( [NotNull] IQueryable queryable, [NotNull] Expression> predicate, diff --git a/framework/src/Volo.Abp.Threading/Volo/Abp/Linq/IAsyncQueryableProvider.cs b/framework/src/Volo.Abp.Threading/Volo/Abp/Linq/IAsyncQueryableProvider.cs index ce62ea738c..bb0ce7f950 100644 --- a/framework/src/Volo.Abp.Threading/Volo/Abp/Linq/IAsyncQueryableProvider.cs +++ b/framework/src/Volo.Abp.Threading/Volo/Abp/Linq/IAsyncQueryableProvider.cs @@ -23,6 +23,10 @@ namespace Volo.Abp.Linq #region Any/All + Task AnyAsync( + [NotNull] IQueryable queryable, + CancellationToken cancellationToken = default); + Task AnyAsync( [NotNull] IQueryable queryable, [NotNull] Expression> predicate, diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/sk.json b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/sk.json new file mode 100644 index 0000000000..df39414750 --- /dev/null +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/sk.json @@ -0,0 +1,7 @@ +{ + "culture": "sk", + "texts": { + "DisplayName:Abp.Timing.Timezone": "Časové pásmo", + "Description:Abp.Timing.Timezone": "Časové pásmo aplikácie" + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/AbpNavigationOptions.cs b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/AbpNavigationOptions.cs index 75c2d426d9..2306778f26 100644 --- a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/AbpNavigationOptions.cs +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/AbpNavigationOptions.cs @@ -7,10 +7,20 @@ namespace Volo.Abp.UI.Navigation { [NotNull] public List MenuContributors { get; } + + /// + /// Includes the by default. + /// + public List MainMenuNames { get; } public AbpNavigationOptions() { MenuContributors = new List(); + + MainMenuNames = new List + { + StandardMenus.Main + }; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/IMenuManager.cs b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/IMenuManager.cs index c22c771e7b..250b77eef0 100644 --- a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/IMenuManager.cs +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/IMenuManager.cs @@ -5,5 +5,7 @@ namespace Volo.Abp.UI.Navigation public interface IMenuManager { Task GetAsync(string name); + + Task GetMainMenuAsync(); } } diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/sk.json b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/sk.json new file mode 100644 index 0000000000..bb3fc23ae4 --- /dev/null +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/sk.json @@ -0,0 +1,6 @@ +{ + "culture": "sk", + "texts": { + "Menu:Administration": "Administratíva" + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/MenuManager.cs b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/MenuManager.cs index 0b52aff7b8..166928b315 100644 --- a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/MenuManager.cs +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/MenuManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Options; @@ -24,7 +25,57 @@ namespace Volo.Abp.UI.Navigation SimpleStateCheckerManager = simpleStateCheckerManager; } - public async Task GetAsync(string name) + public Task GetAsync(string name) + { + return GetInternalAsync(name); + } + + public Task GetMainMenuAsync() + { + return GetAsync(Options.MainMenuNames.ToArray()); + } + + protected virtual async Task GetAsync(params string[] menuNames) + { + if (menuNames.IsNullOrEmpty()) + { + return new ApplicationMenu(StandardMenus.Main); + } + + var menus = new List(); + + foreach (var menuName in Options.MainMenuNames) + { + menus.Add(await GetInternalAsync(menuName)); + } + + return MergeMenus(menus); + } + + protected virtual ApplicationMenu MergeMenus(List menus) + { + Check.NotNullOrEmpty(menus, nameof(menus)); + + if (menus.Count == 1) + { + return menus[0]; + } + + var firstMenu = menus[0]; + + for (int i = 1; i < menus.Count; i++) + { + var currentMenu = menus[i]; + foreach (var menuItem in currentMenu.Items) + { + firstMenu.AddItem(menuItem); + } + } + + return firstMenu; + } + + protected virtual async Task GetInternalAsync(string name) { var menu = new ApplicationMenu(name); diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json index 844752e093..77ff9247ce 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/fi.json @@ -17,7 +17,7 @@ "Refresh": "virkistää", "Language": "Kieli", "LoadMore": "Lataa lisää", - "ProcessingWithThreeDot": "Käsitellään ...", + "ProcessingWithThreeDot": "Käsitellään...", "LoadingWithThreeDot": "Ladataan...", "Welcome": "Tervetuloa", "Login": "Kirjaudu sisään", diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json index 7fbdb1375a..6ccee95a76 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/hi.json @@ -10,14 +10,14 @@ "Ok": "ठीक है", "Close": "बंद करे", "Save": "सहेजें", - "SavingWithThreeDot": "सहेजा जा रहा है ...", + "SavingWithThreeDot": "सहेजा जा रहा है...", "Actions": "कार्रवाई", "Delete": "हटाएं", "Edit": "संपादित करें", "Refresh": "ताज़ा करना", "Language": "भाषा: हिन्दी", "LoadMore": "और लोड करें", - "ProcessingWithThreeDot": "प्रसंस्करण ...", + "ProcessingWithThreeDot": "प्रसंस्करण...", "LoadingWithThreeDot": "लोड हो रहा है...", "Welcome": "स्वागत हे", "Login": "लॉग इन करें", diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json index 6a66828c6e..d4b37bbbb1 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/it.json @@ -1,42 +1,42 @@ { "culture": "it", "texts": { - "Languages": "Le lingue", + "Languages": "Linguaggi", "AreYouSure": "Sei sicuro?", "Cancel": "Annulla", - "Clear": "Chiaro", - "Yes": "sì", + "Clear": "Pulisci", + "Yes": "Sì", "No": "No", "Ok": "Ok", - "Close": "Vicino", + "Close": "Chiudi", "Save": "Salva", "SavingWithThreeDot": "Salvataggio...", "Actions": "Azioni", "Delete": "Elimina", - "Edit": "modificare", - "Refresh": "ricaricare", - "Language": "linguaggio", - "LoadMore": "Carica di più", + "Edit": "Modifica", + "Refresh": "Ricarica", + "Language": "Lingua", + "LoadMore": "Carica altro", "ProcessingWithThreeDot": "In lavorazione...", "LoadingWithThreeDot": "Caricamento in corso...", - "Welcome": "benvenuto", - "Login": "Login", + "Welcome": "Benvenuto", + "Login": "Accedi", "Register": "Registrati", - "Logout": "Disconnettersi", + "Logout": "Disconnettiti", "Submit": "Invia", "Back": "Indietro", "PagerSearch": "Ricerca", - "PagerNext": "Il prossimo", + "PagerNext": "Successivo", "PagerPrevious": "Precedente", "PagerFirst": "Primo", - "PagerLast": "Scorso", + "PagerLast": "Ultimo", "PagerInfo": "Visualizzazione da _START_ a _END_ di _TOTAL_ voci", "PagerInfo{0}{1}{2}": "Visualizzazione da {0} a {1} di {2} voci", "PagerInfoEmpty": "Mostra da 0 a 0 di 0 voci", "PagerInfoFiltered": "(filtrato da _MAX_ voci totali)", "NoDataAvailableInDatatable": "Nessun dato disponibile", - "Total": "totale", - "Selected": "selezionato", + "Total": "Totale", + "Selected": "Selezionato", "PagerShowMenuEntries": "Mostra voci _MENU_", "DatatableActionDropdownDefaultText": "Azioni", "ChangePassword": "Cambia la password", @@ -46,7 +46,7 @@ "GoBack": "Torna indietro", "Search": "Ricerca", "ItemWillBeDeletedMessageWithFormat": "{0} verrà eliminato!", - "ItemWillBeDeletedMessage": "Questo articolo sarà eliminato!", - "ManageYourAccount": "Gestire il tuo account" + "ItemWillBeDeletedMessage": "Questo elemento sarà eliminato!", + "ManageYourAccount": "Gestisci il tuo account" } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json new file mode 100644 index 0000000000..e904e8c8cc --- /dev/null +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/sk.json @@ -0,0 +1,52 @@ +{ + "culture": "sk", + "texts": { + "Languages": "Jazyky", + "AreYouSure": "Ste si istý?", + "Cancel": "Zrušiť", + "Clear": "Vyčistiť", + "Yes": "Áno", + "No": "Nie", + "Ok": "Ok", + "Close": "Zatvoriť", + "Save": "Uložiť", + "SavingWithThreeDot": "Úkladanie...", + "Actions": "Akcie", + "Delete": "Odstrániť", + "Edit": "Upraviť", + "Refresh": "Obnoviť", + "Language": "Jazyk", + "LoadMore": "Načítať viac", + "ProcessingWithThreeDot": "Spracovanie...", + "LoadingWithThreeDot": "Načítanie...", + "Welcome": "Vitajte", + "Login": "Prihlásiť sa", + "Register": "Registrovať sa", + "Logout": "Odhlásiť sa", + "Submit": "Odoslať", + "Back": "Späť", + "PagerSearch": "Vyhľadať", + "PagerNext": "Ďalšia", + "PagerPrevious": "Predchádzajúca", + "PagerFirst": "Prvá", + "PagerLast": "Posledná", + "PagerInfo": "Zobrazených _START_ až _END_ z _TOTAL_ záznamov", + "PagerInfo{0}{1}{2}": "Zobrazených {0} až {1} z {2} záznamov", + "PagerInfoEmpty": "Zobrazených 0 až 0 z 0 záznamov", + "PagerInfoFiltered": "(filtrované z celkom _MAX_ záznamov)", + "NoDataAvailableInDatatable": "Nie sú k dispozícii žiadne údaje", + "Total": "celkom", + "Selected": "vybrané", + "PagerShowMenuEntries": "Zobraziť _MENU_ záznamov", + "DatatableActionDropdownDefaultText": "Akcie", + "ChangePassword": "Zmeniť heslo", + "PersonalInfo": "Môj profil", + "AreYouSureYouWantToCancelEditingWarningMessage": "Máte neuložené zmeny.", + "GoHomePage": "Prejsť na domovskú stránku", + "GoBack": "Vrátiť sa späť", + "Search": "Hľadať", + "ItemWillBeDeletedMessageWithFormat": "{0} sa vymaže!", + "ItemWillBeDeletedMessage": "Táto položka bude vymazaná!", + "ManageYourAccount": "Spravovať svoje konto" + } +} \ No newline at end of file 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 878c03d629..7b4f8fda33 100644 --- a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs +++ b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWork.cs @@ -89,6 +89,11 @@ namespace Volo.Abp.Uow public virtual async Task SaveChangesAsync(CancellationToken cancellationToken = default) { + if (_isRolledback) + { + return; + } + foreach (var databaseApi in GetAllActiveDatabaseApis()) { if (databaseApi is ISupportsSavingChanges) diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/DataAnnotationObjectValidationContributor.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/DataAnnotationObjectValidationContributor.cs index ae016e6954..912f68e4af 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/DataAnnotationObjectValidationContributor.cs +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/DataAnnotationObjectValidationContributor.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using Volo.Abp.Reflection; @@ -120,9 +121,10 @@ namespace Volo.Abp.Validation MemberName = property.Name }; + var attributeValidationResultProvider = ServiceProvider.GetRequiredService(); foreach (var attribute in validationAttributes) { - var result = attribute.GetValidationResult(property.GetValue(validatingObject), validationContext); + var result = attributeValidationResultProvider.GetOrDefault(attribute, property.GetValue(validatingObject), validationContext); if (result != null) { errors.Add(result); diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/DefaultAttributeValidationResultProvider.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/DefaultAttributeValidationResultProvider.cs new file mode 100644 index 0000000000..f60a695b65 --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/DefaultAttributeValidationResultProvider.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Validation +{ + public class DefaultAttributeValidationResultProvider : IAttributeValidationResultProvider, ITransientDependency + { + public virtual ValidationResult GetOrDefault(ValidationAttribute validationAttribute, object validatingObject, ValidationContext validationContext) + { + return validationAttribute.GetValidationResult(validatingObject, validationContext); + } + } +} diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IAttributeValidationResultProvider.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IAttributeValidationResultProvider.cs new file mode 100644 index 0000000000..a5fa367092 --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IAttributeValidationResultProvider.cs @@ -0,0 +1,9 @@ +using System.ComponentModel.DataAnnotations; + +namespace Volo.Abp.Validation +{ + public interface IAttributeValidationResultProvider + { + ValidationResult GetOrDefault(ValidationAttribute validationAttribute, object validatingObject, ValidationContext validationContext); + } +} diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/it.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/it.json index 162e186cde..bad68c117f 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/it.json +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/it.json @@ -1,13 +1,13 @@ { "culture": "it", "texts": { - "'{0}' and '{1}' do not match.": "\"{0}\" e \"{1}\" non corrispondono.", + "'{0}' and '{1}' do not match.": "'{0}' e '{1}' non corrispondono.", "The {0} field is not a valid credit card number.": "Il campo {0} non è un numero di carta di credito valido.", "{0} is not valid.": "{0} non è valido.", "The {0} field is not a valid e-mail address.": "Il campo {0} non è un indirizzo e-mail valido.", "The {0} field only accepts files with the following extensions: {1}": "Il campo {0} accetta solo file con le seguenti estensioni: {1}", - "The field {0} must be a string or array type with a maximum length of '{1}'.": "Il campo {0} deve essere una stringa o un tipo di matrice con una lunghezza massima di \"{1}\".", - "The field {0} must be a string or array type with a minimum length of '{1}'.": "Il campo {0} deve essere una stringa o un tipo di matrice con una lunghezza minima di \"{1}\".", + "The field {0} must be a string or array type with a maximum length of '{1}'.": "Il campo {0} deve essere una stringa o un tipo di matrice con una lunghezza massima di '{1}'.", + "The field {0} must be a string or array type with a minimum length of '{1}'.": "Il campo {0} deve essere una stringa o un tipo di matrice con una lunghezza minima di '{1}'.", "The {0} field is not a valid phone number.": "Il campo {0} non è un numero di telefono valido.", "The field {0} must be between {1} and {2}.": "Il campo {0} deve essere compreso tra {1} e {2}.", "The field {0} must match the regular expression '{1}'.": "Il campo {0} non corrisponde al formato richiesto.", @@ -20,11 +20,11 @@ "ThisFieldIsNotValid.": "Questo campo non è valido.", "ThisFieldIsNotAValidEmailAddress.": "Questo campo non è un indirizzo e-mail valido.", "ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "Questo campo accetta solo file con le seguenti estensioni: {0}", - "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "Questo campo deve essere un tipo stringa o matrice con una lunghezza massima di \"{0}\".", - "ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "Questo campo deve essere una stringa o un tipo di matrice con una lunghezza minima di \"{0}\".", + "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "Questo campo deve essere un tipo stringa o matrice con una lunghezza massima di '{0}'.", + "ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "Questo campo deve essere una stringa o un tipo di matrice con una lunghezza minima di '{0}'.", "ThisFieldIsNotAValidPhoneNumber.": "Questo campo non è un numero di telefono valido.", "ThisFieldMustBeBetween{0}And{1}": "Questo campo deve essere compreso tra {0} e {1}.", - "ThisFieldMustMatchTheRegularExpression{0}": "Questo campo deve corrispondere all'espressione regolare \"{0}\".", + "ThisFieldMustMatchTheRegularExpression{0}": "Questo campo deve corrispondere all'espressione regolare '{0}'.", "ThisFieldIsRequired.": "Questo campo è obbligatorio.", "ThisFieldMustBeAStringWithAMaximumLengthOf{0}": "Questo campo deve essere una stringa con una lunghezza massima di {0}.", "ThisFieldMustBeAStringWithAMinimumLengthOf{1}AndAMaximumLengthOf{0}": "Questo campo deve essere una stringa con una lunghezza minima di {1} e una lunghezza massima di {0}.", diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/sk.json b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/sk.json new file mode 100644 index 0000000000..d5f310aa66 --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/sk.json @@ -0,0 +1,34 @@ +{ + "culture": "sk", + "texts": { + "'{0}' and '{1}' do not match.": "'{0}' a '{1}' sa nezhodujú.", + "The {0} field is not a valid credit card number.": "Pole {0} nie je platné číslo kreditnej karty.", + "{0} is not valid.": "{0} nie je platný.", + "The {0} field is not a valid e-mail address.": "Pole {0} nie je platná e-mailová adresa.", + "The {0} field only accepts files with the following extensions: {1}": "Pole {0} akceptuje iba súbory s nasledujúcimi príponami: {1}", + "The field {0} must be a string or array type with a maximum length of '{1}'.": "Pole {0} musí byť typu reťazec alebo pole s maximálnou dĺžkou '{1}'.", + "The field {0} must be a string or array type with a minimum length of '{1}'.": "Pole {0} musí byť typu reťazec alebo pole s minimálnou dĺžkou '{1}'.", + "The {0} field is not a valid phone number.": "Pole {0} nie je platné telefónne číslo.", + "The field {0} must be between {1} and {2}.": "Pole {0} musí byť medzi {1} a {2}.", + "The field {0} must match the regular expression '{1}'.": "Pole {0} nezodpovedá požadovanému formátu.", + "The {0} field is required.": "Pole {0} je povinné.", + "The field {0} must be a string with a maximum length of {1}.": "Pole {0} musí byť reťazec s maximálnou dĺžkou {1}.", + "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Pole {0} musí byť reťazec s minimálnou dĺžkou {2} a maximálnou dĺžkou {1}.", + "The {0} field is not a valid fully-qualified http, https, or ftp URL.": "V poli {0} nie je platná plne kvalifikovaná adresa http, https alebo ftp URL.", + "The field {0} is invalid.": "Pole {0} je neplatné.", + "ThisFieldIsNotAValidCreditCardNumber.": "V tomto poli nie je platné číslo kreditnej karty.", + "ThisFieldIsNotValid.": "Toto pole nie je platné.", + "ThisFieldIsNotAValidEmailAddress.": "V tomto poli nie je platná e-mailová adresa.", + "ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "Toto pole akceptuje iba súbory s nasledujúcimi príponami: {0}", + "ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "Toto pole musí byť typu reťazec alebo pole s maximálnou dĺžkou '{0}'.", + "ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "Toto pole musí byť typu reťazec alebo pole s minimálnou dĺžkou '{0}'.", + "ThisFieldIsNotAValidPhoneNumber.": "V tomto poli nie je platné telefónne číslo.", + "ThisFieldMustBeBetween{0}And{1}": "Toto pole musí byť medzi {0} a {1}.", + "ThisFieldMustMatchTheRegularExpression{0}": "Toto pole musí zodpovedať regulárnemu výrazu '{0}'.", + "ThisFieldIsRequired.": "Toto pole je povinné.", + "ThisFieldMustBeAStringWithAMaximumLengthOf{0}": "Toto pole musí byť typu reťazec s maximálnou dĺžkou {0}.", + "ThisFieldMustBeAStringWithAMinimumLengthOf{1}AndAMaximumLengthOf{0}": "Toto pole musí byť typu reťazec s minimálnou dĺžkou {1} a maximálnou dĺžkou {0}.", + "ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl": "V tomto poli nie je platná plne kvalifikovaná http, https alebo ftp URL.", + "ThisFieldIsInvalid.": "Toto pole je neplatné." + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/LocalizationTestController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/LocalizationTestController.cs index 1defcf4809..ff26083a83 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/LocalizationTestController.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/LocalizationTestController.cs @@ -21,12 +21,20 @@ namespace Volo.Abp.AspNetCore.App public class PersonModel { - //[Display(Name = nameof(BirthDate))] public string BirthDate { get; set; } + public string BirthDate1 { get; set; } + + public string BirthDate2 { get; set; } + + public string BirthDate3 { get; set; } + public PersonModel() { BirthDate = DateTime.Now.ToString("yyyy-MM-dd"); + BirthDate1 = BirthDate; + BirthDate2 = BirthDate; + BirthDate3 = BirthDate; } } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/Views/LocalizationTest/PersonForm.cshtml b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/Views/LocalizationTest/PersonForm.cshtml index 7f0a0d470f..31dee8bf83 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/Views/LocalizationTest/PersonForm.cshtml +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/Views/LocalizationTest/PersonForm.cshtml @@ -5,4 +5,13 @@
-
\ No newline at end of file + + + + + + + + + + diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs index 4bcd430a8e..0fe6fb7cd9 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs @@ -88,6 +88,7 @@ namespace Volo.Abp.AspNetCore.Mvc options.Languages.Add(new LanguageInfo("en", "en", "English")); options.Languages.Add(new LanguageInfo("hu", "hu", "Magyar")); + options.Languages.Add(new LanguageInfo("sk", "sk", "Slovak")); options.Languages.Add(new LanguageInfo("tr", "tr", "Türkçe")); }); diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentTestController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentTestController.cs index de7cc8d5cc..c9183d41d7 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentTestController.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentTestController.cs @@ -17,7 +17,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters var memoryStream = new MemoryStream(); await memoryStream.WriteAsync(Encoding.UTF8.GetBytes("DownloadAsync")); memoryStream.Position = 0; - return new RemoteStreamContent(memoryStream, "application/rtf"); + return new RemoteStreamContent(memoryStream, "download.rtf", "application/rtf"); } [HttpPost] @@ -26,14 +26,14 @@ namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters { using (var reader = new StreamReader(file.GetStream())) { - return await reader.ReadToEndAsync() + ":" + file.ContentType; + return await reader.ReadToEndAsync() + ":" + file.ContentType + ":" + file.FileName; } } [HttpPost] [Route("Upload-Raw")] public async Task UploadRawAsync(IRemoteStreamContent file) - { + { using (var reader = new StreamReader(file.GetStream())) { return await reader.ReadToEndAsync() + ":" + file.ContentType; diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentTestController_Tests.cs index a901c131a2..14d755e1b2 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentTestController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentTestController_Tests.cs @@ -15,9 +15,10 @@ namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters { var result = await GetResponseAsync("/api/remote-stream-content-test/download"); result.Content.Headers.ContentType?.ToString().ShouldBe("application/rtf"); + result.Content.Headers.ContentDisposition?.FileName.ShouldBe("download.rtf"); (await result.Content.ReadAsStringAsync()).ShouldBe("DownloadAsync"); - } - + } + [Fact] public async Task UploadAsync() { @@ -29,12 +30,11 @@ namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters var streamContent = new StreamContent(memoryStream); streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/rtf"); - - requestMessage.Content = new MultipartFormDataContent { { streamContent, "file", "file" } }; + requestMessage.Content = new MultipartFormDataContent {{ streamContent, "file", "upload.rtf" }}; var response = await Client.SendAsync(requestMessage); - (await response.Content.ReadAsStringAsync()).ShouldBe("UploadAsync:application/rtf"); + (await response.Content.ReadAsStringAsync()).ShouldBe("UploadAsync:application/rtf:upload.rtf"); } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/MvcLocalization_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/MvcLocalization_Tests.cs index 4d9a4f4624..fe7912951c 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/MvcLocalization_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/MvcLocalization_Tests.cs @@ -30,7 +30,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Localization var result = await GetResponseAsStringAsync("/LocalizationTest/HelloJohn"); result.ShouldBe("Hello John."); } - + [Fact] public async Task Should_Localize_Display_Attribute() { @@ -38,12 +38,18 @@ namespace Volo.Abp.AspNetCore.Mvc.Localization { var result = await GetResponseAsStringAsync("/LocalizationTest/PersonForm"); result.ShouldContain(""); + result.ShouldContain(""); + result.ShouldContain(""); + result.ShouldContain(""); } using (CultureHelper.Use("tr")) { var result = await GetResponseAsStringAsync("/LocalizationTest/PersonForm"); result.ShouldContain(""); + result.ShouldContain(""); + result.ShouldContain(""); + result.ShouldContain(""); } } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/en.json b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/en.json index 2451c1b294..f11c5458db 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/en.json +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/en.json @@ -1,7 +1,10 @@ { "culture": "en", "texts": { + "DisplayName:PersonModel:BirthDate1": "Birth date1", + "DisplayName:BirthDate2": "Birth date2", + "PersonModel:BirthDate3": "Birth date3", "BirthDate": "Birth date", - "Value1": "Value One" + "Value1": "Value One" } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/it.json b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/it.json index 954ab19de0..ee0c21fa75 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/it.json +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/it.json @@ -2,6 +2,6 @@ "culture": "it", "texts": { "BirthDate": "Data di nascita", - "Value1": "Valore uno" + "Value1": "Valore Uno" } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/sk.json b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/sk.json new file mode 100644 index 0000000000..96c025171b --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/sk.json @@ -0,0 +1,7 @@ +{ + "culture": "sk", + "texts": { + "BirthDate": "Dátum narodenia", + "Value1": "Hodnota jedna" + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/tr.json b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/tr.json index 546e672ede..b99e81bec0 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/tr.json +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/tr.json @@ -1,7 +1,10 @@ { "culture": "tr", "texts": { + "DisplayName:PersonModel:BirthDate1": "Dogum gunu1", + "DisplayName:BirthDate2": "Dogum gunu2", + "PersonModel:BirthDate3": "Dogum gunu3", "BirthDate": "Dogum gunu", - "Value1": "Değer Bir" + "Value1": "Değer Bir" } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Tests/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Tests.csproj b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Tests/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Tests.csproj deleted file mode 100644 index fcde227f3a..0000000000 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Tests/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Tests.csproj +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - net5.0 - $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; - Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Tests - Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Tests - true - true - true - true - - - - - - - - - - - - - - - - - - PreserveNewest - - - - - - - - - - - - - diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Popovers.cshtml b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Popovers.cshtml deleted file mode 100644 index 779494d70f..0000000000 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Popovers.cshtml +++ /dev/null @@ -1,88 +0,0 @@ -@page -@model Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components.PopoversModel -@{ - ViewData["Title"] = "Popovers"; -} - -@section styles { - - - -} - -@section scripts { - - @* - *@ - -} - - - - - - -

Popovers

- -

Based on Bootstrap Popovers.

- - -

Example

- -
-
- - - Popover Default - - - Popover With Title - - - Dismissible Popover - - - Disabled Popover - - - Disabled Popover - -
-
- - -

-<abp-button abp-popover="Hi, i'm popover content!">
-       Popover Default
-</abp-button>
-<abp-button abp-popover-top="Hi, i'm popover content!" title="Popover Title">
-       Popover With Title
-</abp-button>
-<abp-button abp-popover-right="Hi, i'm popover content!" title="Popover Title" dismissible="true">
-       Dismissible Popover
-</abp-button>
-<abp-button abp-popover-left="Hi, i'm popover content!" title="Popover Title" disabled="true">
-       Disabled Popover
-</abp-button>
-
-
- -

-<button class="btn" type="button" data-busy-text="Processing..." data-toggle="popover" data-placement="bottom" data-content="Hi, i'm popover content!" data-original-title="" title="">
-      Popover Default
-</button>
-<button title="" class="btn" type="button" data-busy-text="Processing..." data-toggle="popover" data-placement="top" data-content="Hi, i'm popover content!" data-original-title="Popover Title">
-      Popover With Title
-</button>
-<button title="" class="btn" type="button" data-busy-text="Processing..." data-toggle="popover" data-placement="right" data-content="Hi, i'm popover content!" data-trigger="focus" data-original-title="Popover Title">
-      Dismissible Popover
-</button>
-<span class="d-inline-block" title="" data-placement="left" data-toggle="popover" data-content="Hi, i'm popover content!" data-original-title="Popover Title"><button title="Popover Title" class="btn" type="button" data-busy-text="Processing..." style="pointer-events: none;">
-      Disabled Popover
-</button></span>
-
-
-
-
-
diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.csproj b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.csproj deleted file mode 100644 index b0cd1e1aeb..0000000000 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - net5.0 - true - - - - - - - - - - - - - - diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/package.json b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/package.json deleted file mode 100644 index da243d78d3..0000000000 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "version": "1.0.0", - "name": "asp.net", - "private": true, - "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.shared": "^4.3.2", - "highlight.js": "^9.13.1" - }, - "devDependencies": {} -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/abp/jquery/abp.jquery.js b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/abp/jquery/abp.jquery.js deleted file mode 100644 index 6f8f5b20ce..0000000000 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/abp/jquery/abp.jquery.js +++ /dev/null @@ -1,404 +0,0 @@ -var abp = abp || {}; -(function($) { - - if (!$) { - throw "abp/jquery library requires the jquery library included to the page!"; - } - - // ABP CORE OVERRIDES ///////////////////////////////////////////////////// - - abp.message._showMessage = function (message, title) { - alert((title || '') + ' ' + message); - - return $.Deferred(function ($dfd) { - $dfd.resolve(); - }); - }; - - abp.message.confirm = function (message, titleOrCallback, callback) { - if (titleOrCallback && !(typeof titleOrCallback == 'string')) { - callback = titleOrCallback; - } - - var result = confirm(message); - callback && callback(result); - - return $.Deferred(function ($dfd) { - $dfd.resolve(result); - }); - }; - - abp.utils.isFunction = function (obj) { - return $.isFunction(obj); - }; - - // JQUERY EXTENSIONS ////////////////////////////////////////////////////// - - $.fn.findWithSelf = function (selector) { - return this.filter(selector).add(this.find(selector)); - }; - - // DOM //////////////////////////////////////////////////////////////////// - - abp.dom = abp.dom || {}; - - abp.dom.onNodeAdded = function (callback) { - abp.event.on('abp.dom.nodeAdded', callback); - }; - - abp.dom.onNodeRemoved = function (callback) { - abp.event.on('abp.dom.nodeRemoved', callback); - }; - - var mutationObserverCallback = function (mutationsList) { - for (var i = 0; i < mutationsList.length; i++) { - var mutation = mutationsList[i]; - if (mutation.type === 'childList') { - if (mutation.addedNodes && mutation.removedNodes.length) { - for (var k = 0; k < mutation.removedNodes.length; k++) { - abp.event.trigger( - 'abp.dom.nodeRemoved', - { - $el: $(mutation.removedNodes[k]) - } - ); - } - } - - if (mutation.addedNodes && mutation.addedNodes.length) { - for (var j = 0; j < mutation.addedNodes.length; j++) { - abp.event.trigger( - 'abp.dom.nodeAdded', - { - $el: $(mutation.addedNodes[j]) - } - ); - } - } - } - } - }; - - $(function(){ - new MutationObserver(mutationObserverCallback).observe( - $('body')[0], - { - subtree: true, - childList: true - } - ); - }); - - // AJAX /////////////////////////////////////////////////////////////////// - - abp.ajax = function (userOptions) { - userOptions = userOptions || {}; - - var options = $.extend(true, {}, abp.ajax.defaultOpts, userOptions); - - options.success = undefined; - options.error = undefined; - - var xhr = null; - var promise = $.Deferred(function ($dfd) { - xhr = $.ajax(options) - .done(function (data, textStatus, jqXHR) { - $dfd.resolve(data); - userOptions.success && userOptions.success(data); - }).fail(function (jqXHR) { - if(jqXHR.status === 0 || jqXHR.statusText === 'abort') { - //ajax request is abort, ignore error handle. - return; - } - if (jqXHR.getResponseHeader('_AbpErrorFormat') === 'true') { - abp.ajax.handleAbpErrorResponse(jqXHR, userOptions, $dfd); - } else { - abp.ajax.handleNonAbpErrorResponse(jqXHR, userOptions, $dfd); - } - }); - }).promise(); - - promise['jqXHR'] = xhr; - - return promise; - }; - - $.extend(abp.ajax, { - defaultOpts: { - dataType: 'json', - type: 'POST', - contentType: 'application/json', - headers: { - 'X-Requested-With': 'XMLHttpRequest' - } - }, - - defaultError: { - message: 'An error has occurred!', - details: 'Error detail not sent by server.' - }, - - defaultError401: { - message: 'You are not authenticated!', - details: 'You should be authenticated (sign in) in order to perform this operation.' - }, - - defaultError403: { - message: 'You are not authorized!', - details: 'You are not allowed to perform this operation.' - }, - - defaultError404: { - message: 'Resource not found!', - details: 'The resource requested could not found on the server.' - }, - - logError: function (error) { - abp.log.error(error); - }, - - showError: function (error) { - if (error.details) { - return abp.message.error(error.details, error.message); - } else { - return abp.message.error(error.message || abp.ajax.defaultError.message); - } - }, - - handleTargetUrl: function (targetUrl) { - if (!targetUrl) { - location.href = abp.appPath; - } else { - location.href = targetUrl; - } - }, - - handleErrorStatusCode: function (status) { - switch (status) { - case 401: - abp.ajax.handleUnAuthorizedRequest( - abp.ajax.showError(abp.ajax.defaultError401), - abp.appPath - ); - break; - case 403: - abp.ajax.showError(abp.ajax.defaultError403); - break; - case 404: - abp.ajax.showError(abp.ajax.defaultError404); - break; - default: - abp.ajax.showError(abp.ajax.defaultError); - break; - } - }, - - handleNonAbpErrorResponse: function (jqXHR, userOptions, $dfd) { - if (userOptions.abpHandleError !== false) { - abp.ajax.handleErrorStatusCode(jqXHR.status); - } - - $dfd.reject.apply(this, arguments); - userOptions.error && userOptions.error.apply(this, arguments); - }, - - handleAbpErrorResponse: function (jqXHR, userOptions, $dfd) { - var messagePromise = null; - - if (userOptions.abpHandleError !== false) { - messagePromise = abp.ajax.showError(jqXHR.responseJSON.error); - } - - abp.ajax.logError(jqXHR.responseJSON.error); - - $dfd && $dfd.reject(jqXHR.responseJSON.error, jqXHR); - userOptions.error && userOptions.error(jqXHR.responseJSON.error, jqXHR); - - if (jqXHR.status === 401 && userOptions.abpHandleError !== false) { - abp.ajax.handleUnAuthorizedRequest(messagePromise); - } - }, - - handleUnAuthorizedRequest: function (messagePromise, targetUrl) { - if (messagePromise) { - messagePromise.done(function () { - abp.ajax.handleTargetUrl(targetUrl); - }); - } else { - abp.ajax.handleTargetUrl(targetUrl); - } - }, - - blockUI: function (options) { - if (options.blockUI) { - if (options.blockUI === true) { //block whole page - abp.ui.setBusy(); - } else { //block an element - abp.ui.setBusy(options.blockUI); - } - } - }, - - unblockUI: function (options) { - if (options.blockUI) { - if (options.blockUI === true) { //unblock whole page - abp.ui.clearBusy(); - } else { //unblock an element - abp.ui.clearBusy(options.blockUI); - } - } - }, - - ajaxSendHandler: function (event, request, settings) { - var token = abp.security.antiForgery.getToken(); - if (!token) { - return; - } - - if (!settings.headers || settings.headers[abp.security.antiForgery.tokenHeaderName] === undefined) { - request.setRequestHeader(abp.security.antiForgery.tokenHeaderName, token); - } - } - }); - - $(document).ajaxSend(function (event, request, settings) { - return abp.ajax.ajaxSendHandler(event, request, settings); - }); - - abp.event.on('abp.configurationInitialized', function () { - var l = abp.localization.getResource('AbpUi'); - - abp.ajax.defaultError.message = l('DefaultErrorMessage'); - abp.ajax.defaultError.details = l('DefaultErrorMessageDetail'); - abp.ajax.defaultError401.message = l('DefaultErrorMessage401'); - abp.ajax.defaultError401.details = l('DefaultErrorMessage401Detail'); - abp.ajax.defaultError403.message = l('DefaultErrorMessage403'); - abp.ajax.defaultError403.details = l('DefaultErrorMessage403Detail'); - abp.ajax.defaultError404.message = l('DefaultErrorMessage404'); - abp.ajax.defaultError404.details = l('DefaultErrorMessage404Detail'); - }); - - // RESOURCE LOADER //////////////////////////////////////////////////////// - - /* UrlStates enum */ - var UrlStates = { - LOADING: 'LOADING', - LOADED: 'LOADED', - FAILED: 'FAILED' - }; - - /* UrlInfo class */ - function UrlInfo(url) { - this.url = url; - this.state = UrlStates.LOADING; - this.loadCallbacks = []; - this.failCallbacks = []; - } - - UrlInfo.prototype.succeed = function () { - this.state = UrlStates.LOADED; - for (var i = 0; i < this.loadCallbacks.length; i++) { - this.loadCallbacks[i](); - } - }; - - UrlInfo.prototype.failed = function () { - this.state = UrlStates.FAILED; - for (var i = 0; i < this.failCallbacks.length; i++) { - this.failCallbacks[i](); - } - }; - - UrlInfo.prototype.handleCallbacks = function (loadCallback, failCallback) { - switch (this.state) { - case UrlStates.LOADED: - loadCallback && loadCallback(); - break; - case UrlStates.FAILED: - failCallback && failCallback(); - break; - case UrlStates.LOADING: - this.addCallbacks(loadCallback, failCallback); - break; - } - }; - - UrlInfo.prototype.addCallbacks = function (loadCallback, failCallback) { - loadCallback && this.loadCallbacks.push(loadCallback); - failCallback && this.failCallbacks.push(failCallback); - }; - - /* ResourceLoader API */ - - abp.ResourceLoader = (function () { - - var _urlInfos = {}; - - function getCacheKey(url) { - return url; - } - - function appendTimeToUrl(url) { - - if (url.indexOf('?') < 0) { - url += '?'; - } else { - url += '&'; - } - - url += '_=' + new Date().getTime(); - - return url; - } - - var _loadFromUrl = function (url, loadCallback, failCallback, serverLoader) { - - var cacheKey = getCacheKey(url); - - var urlInfo = _urlInfos[cacheKey]; - - if (urlInfo) { - urlInfo.handleCallbacks(loadCallback, failCallback); - return; - } - - _urlInfos[cacheKey] = urlInfo = new UrlInfo(url); - urlInfo.addCallbacks(loadCallback, failCallback); - - serverLoader(urlInfo); - }; - - var _loadScript = function (url, loadCallback, failCallback) { - _loadFromUrl(url, loadCallback, failCallback, function (urlInfo) { - $.get({ - url: url, - dataType: 'text' - }) - .done(function (script) { - $.globalEval(script); - urlInfo.succeed(); - }) - .fail(function () { - urlInfo.failed(); - }); - }); - }; - - var _loadStyle = function (url) { - _loadFromUrl(url, undefined, undefined, function (urlInfo) { - - $('', { - rel: 'stylesheet', - type: 'text/css', - href: appendTimeToUrl(url) - }).appendTo('head'); - }); - }; - - return { - loadScript: _loadScript, - loadStyle: _loadStyle - } - })(); - -})(jQuery); \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/abp/utils/abp-utils.umd.js b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/abp/utils/abp-utils.umd.js deleted file mode 100644 index 359d2f7247..0000000000 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/abp/utils/abp-utils.umd.js +++ /dev/null @@ -1,694 +0,0 @@ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('just-compare')) : - typeof define === 'function' && define.amd ? define('@abp/utils', ['exports', 'just-compare'], factory) : - (global = global || self, factory((global.abp = global.abp || {}, global.abp.utils = global.abp.utils || {}, global.abp.utils.common = {}), global.compare)); -}(this, (function (exports, compare) { 'use strict'; - - compare = compare && Object.prototype.hasOwnProperty.call(compare, 'default') ? compare['default'] : compare; - - /*! ***************************************************************************** - Copyright (c) Microsoft Corporation. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR - OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - PERFORMANCE OF THIS SOFTWARE. - ***************************************************************************** */ - /* global Reflect, Promise */ - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) - if (b.hasOwnProperty(p)) - d[p] = b[p]; }; - return extendStatics(d, b); - }; - function __extends(d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - } - var __assign = function () { - __assign = Object.assign || function __assign(t) { - for (var s, i = 1, n = arguments.length; i < n; i++) { - s = arguments[i]; - for (var p in s) - if (Object.prototype.hasOwnProperty.call(s, p)) - t[p] = s[p]; - } - return t; - }; - return __assign.apply(this, arguments); - }; - function __rest(s, e) { - var t = {}; - for (var p in s) - if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) - t[p[i]] = s[p[i]]; - } - return t; - } - function __decorate(decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") - r = Reflect.decorate(decorators, target, key, desc); - else - for (var i = decorators.length - 1; i >= 0; i--) - if (d = decorators[i]) - r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - } - function __param(paramIndex, decorator) { - return function (target, key) { decorator(target, key, paramIndex); }; - } - function __metadata(metadataKey, metadataValue) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") - return Reflect.metadata(metadataKey, metadataValue); - } - function __awaiter(thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { - step(generator.next(value)); - } - catch (e) { - reject(e); - } } - function rejected(value) { try { - step(generator["throw"](value)); - } - catch (e) { - reject(e); - } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); - } - function __generator(thisArg, body) { - var _ = { label: 0, sent: function () { if (t[0] & 1) - throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function () { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) - throw new TypeError("Generator is already executing."); - while (_) - try { - if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) - return t; - if (y = 0, t) - op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: - case 1: - t = op; - break; - case 4: - _.label++; - return { value: op[1], done: false }; - case 5: - _.label++; - y = op[1]; - op = [0]; - continue; - case 7: - op = _.ops.pop(); - _.trys.pop(); - continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { - _ = 0; - continue; - } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { - _.label = op[1]; - break; - } - if (op[0] === 6 && _.label < t[1]) { - _.label = t[1]; - t = op; - break; - } - if (t && _.label < t[2]) { - _.label = t[2]; - _.ops.push(op); - break; - } - if (t[2]) - _.ops.pop(); - _.trys.pop(); - continue; - } - op = body.call(thisArg, _); - } - catch (e) { - op = [6, e]; - y = 0; - } - finally { - f = t = 0; - } - if (op[0] & 5) - throw op[1]; - return { value: op[0] ? op[1] : void 0, done: true }; - } - } - var __createBinding = Object.create ? (function (o, m, k, k2) { - if (k2 === undefined) - k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function () { return m[k]; } }); - }) : (function (o, m, k, k2) { - if (k2 === undefined) - k2 = k; - o[k2] = m[k]; - }); - function __exportStar(m, exports) { - for (var p in m) - if (p !== "default" && !exports.hasOwnProperty(p)) - __createBinding(exports, m, p); - } - function __values(o) { - var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; - if (m) - return m.call(o); - if (o && typeof o.length === "number") - return { - next: function () { - if (o && i >= o.length) - o = void 0; - return { value: o && o[i++], done: !o }; - } - }; - throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); - } - function __read(o, n) { - var m = typeof Symbol === "function" && o[Symbol.iterator]; - if (!m) - return o; - var i = m.call(o), r, ar = [], e; - try { - while ((n === void 0 || n-- > 0) && !(r = i.next()).done) - ar.push(r.value); - } - catch (error) { - e = { error: error }; - } - finally { - try { - if (r && !r.done && (m = i["return"])) - m.call(i); - } - finally { - if (e) - throw e.error; - } - } - return ar; - } - function __spread() { - for (var ar = [], i = 0; i < arguments.length; i++) - ar = ar.concat(__read(arguments[i])); - return ar; - } - function __spreadArrays() { - for (var s = 0, i = 0, il = arguments.length; i < il; i++) - s += arguments[i].length; - for (var r = Array(s), k = 0, i = 0; i < il; i++) - for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) - r[k] = a[j]; - return r; - } - ; - function __await(v) { - return this instanceof __await ? (this.v = v, this) : new __await(v); - } - function __asyncGenerator(thisArg, _arguments, generator) { - if (!Symbol.asyncIterator) - throw new TypeError("Symbol.asyncIterator is not defined."); - var g = generator.apply(thisArg, _arguments || []), i, q = []; - return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i; - function verb(n) { if (g[n]) - i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; } - function resume(n, v) { try { - step(g[n](v)); - } - catch (e) { - settle(q[0][3], e); - } } - function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } - function fulfill(value) { resume("next", value); } - function reject(value) { resume("throw", value); } - function settle(f, v) { if (f(v), q.shift(), q.length) - resume(q[0][0], q[0][1]); } - } - function __asyncDelegator(o) { - var i, p; - return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i; - function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; } - } - function __asyncValues(o) { - if (!Symbol.asyncIterator) - throw new TypeError("Symbol.asyncIterator is not defined."); - var m = o[Symbol.asyncIterator], i; - return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); - function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } - function settle(resolve, reject, d, v) { Promise.resolve(v).then(function (v) { resolve({ value: v, done: d }); }, reject); } - } - function __makeTemplateObject(cooked, raw) { - if (Object.defineProperty) { - Object.defineProperty(cooked, "raw", { value: raw }); - } - else { - cooked.raw = raw; - } - return cooked; - } - ; - var __setModuleDefault = Object.create ? (function (o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); - }) : function (o, v) { - o["default"] = v; - }; - function __importStar(mod) { - if (mod && mod.__esModule) - return mod; - var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) - __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; - } - function __importDefault(mod) { - return (mod && mod.__esModule) ? mod : { default: mod }; - } - function __classPrivateFieldGet(receiver, privateMap) { - if (!privateMap.has(receiver)) { - throw new TypeError("attempted to get private field on non-instance"); - } - return privateMap.get(receiver); - } - function __classPrivateFieldSet(receiver, privateMap, value) { - if (!privateMap.has(receiver)) { - throw new TypeError("attempted to set private field on non-instance"); - } - privateMap.set(receiver, value); - return value; - } - - var ListNode = /** @class */ (function () { - function ListNode(value) { - this.value = value; - } - return ListNode; - }()); - var LinkedList = /** @class */ (function () { - function LinkedList() { - this.size = 0; - } - Object.defineProperty(LinkedList.prototype, "head", { - get: function () { - return this.first; - }, - enumerable: false, - configurable: true - }); - Object.defineProperty(LinkedList.prototype, "tail", { - get: function () { - return this.last; - }, - enumerable: false, - configurable: true - }); - Object.defineProperty(LinkedList.prototype, "length", { - get: function () { - return this.size; - }, - enumerable: false, - configurable: true - }); - LinkedList.prototype.attach = function (value, previousNode, nextNode) { - if (!previousNode) - return this.addHead(value); - if (!nextNode) - return this.addTail(value); - var node = new ListNode(value); - node.previous = previousNode; - previousNode.next = node; - node.next = nextNode; - nextNode.previous = node; - this.size++; - return node; - }; - LinkedList.prototype.attachMany = function (values, previousNode, nextNode) { - if (!values.length) - return []; - if (!previousNode) - return this.addManyHead(values); - if (!nextNode) - return this.addManyTail(values); - var list = new LinkedList(); - list.addManyTail(values); - list.first.previous = previousNode; - previousNode.next = list.first; - list.last.next = nextNode; - nextNode.previous = list.last; - this.size += values.length; - return list.toNodeArray(); - }; - LinkedList.prototype.detach = function (node) { - if (!node.previous) - return this.dropHead(); - if (!node.next) - return this.dropTail(); - node.previous.next = node.next; - node.next.previous = node.previous; - this.size--; - return node; - }; - LinkedList.prototype.add = function (value) { - var _this = this; - return { - after: function () { - var _a; - var params = []; - for (var _i = 0; _i < arguments.length; _i++) { - params[_i] = arguments[_i]; - } - return (_a = _this.addAfter).call.apply(_a, __spread([_this, value], params)); - }, - before: function () { - var _a; - var params = []; - for (var _i = 0; _i < arguments.length; _i++) { - params[_i] = arguments[_i]; - } - return (_a = _this.addBefore).call.apply(_a, __spread([_this, value], params)); - }, - byIndex: function (position) { return _this.addByIndex(value, position); }, - head: function () { return _this.addHead(value); }, - tail: function () { return _this.addTail(value); }, - }; - }; - LinkedList.prototype.addMany = function (values) { - var _this = this; - return { - after: function () { - var _a; - var params = []; - for (var _i = 0; _i < arguments.length; _i++) { - params[_i] = arguments[_i]; - } - return (_a = _this.addManyAfter).call.apply(_a, __spread([_this, values], params)); - }, - before: function () { - var _a; - var params = []; - for (var _i = 0; _i < arguments.length; _i++) { - params[_i] = arguments[_i]; - } - return (_a = _this.addManyBefore).call.apply(_a, __spread([_this, values], params)); - }, - byIndex: function (position) { return _this.addManyByIndex(values, position); }, - head: function () { return _this.addManyHead(values); }, - tail: function () { return _this.addManyTail(values); }, - }; - }; - LinkedList.prototype.addAfter = function (value, previousValue, compareFn) { - if (compareFn === void 0) { compareFn = compare; } - var previous = this.find(function (node) { return compareFn(node.value, previousValue); }); - return previous ? this.attach(value, previous, previous.next) : this.addTail(value); - }; - LinkedList.prototype.addBefore = function (value, nextValue, compareFn) { - if (compareFn === void 0) { compareFn = compare; } - var next = this.find(function (node) { return compareFn(node.value, nextValue); }); - return next ? this.attach(value, next.previous, next) : this.addHead(value); - }; - LinkedList.prototype.addByIndex = function (value, position) { - if (position < 0) - position += this.size; - else if (position >= this.size) - return this.addTail(value); - if (position <= 0) - return this.addHead(value); - var next = this.get(position); - return this.attach(value, next.previous, next); - }; - LinkedList.prototype.addHead = function (value) { - var node = new ListNode(value); - node.next = this.first; - if (this.first) - this.first.previous = node; - else - this.last = node; - this.first = node; - this.size++; - return node; - }; - LinkedList.prototype.addTail = function (value) { - var node = new ListNode(value); - if (this.first) { - node.previous = this.last; - this.last.next = node; - this.last = node; - } - else { - this.first = node; - this.last = node; - } - this.size++; - return node; - }; - LinkedList.prototype.addManyAfter = function (values, previousValue, compareFn) { - if (compareFn === void 0) { compareFn = compare; } - var previous = this.find(function (node) { return compareFn(node.value, previousValue); }); - return previous ? this.attachMany(values, previous, previous.next) : this.addManyTail(values); - }; - LinkedList.prototype.addManyBefore = function (values, nextValue, compareFn) { - if (compareFn === void 0) { compareFn = compare; } - var next = this.find(function (node) { return compareFn(node.value, nextValue); }); - return next ? this.attachMany(values, next.previous, next) : this.addManyHead(values); - }; - LinkedList.prototype.addManyByIndex = function (values, position) { - if (position < 0) - position += this.size; - if (position <= 0) - return this.addManyHead(values); - if (position >= this.size) - return this.addManyTail(values); - var next = this.get(position); - return this.attachMany(values, next.previous, next); - }; - LinkedList.prototype.addManyHead = function (values) { - var _this = this; - return values.reduceRight(function (nodes, value) { - nodes.unshift(_this.addHead(value)); - return nodes; - }, []); - }; - LinkedList.prototype.addManyTail = function (values) { - var _this = this; - return values.map(function (value) { return _this.addTail(value); }); - }; - LinkedList.prototype.drop = function () { - var _this = this; - return { - byIndex: function (position) { return _this.dropByIndex(position); }, - byValue: function () { - var params = []; - for (var _i = 0; _i < arguments.length; _i++) { - params[_i] = arguments[_i]; - } - return _this.dropByValue.apply(_this, params); - }, - byValueAll: function () { - var params = []; - for (var _i = 0; _i < arguments.length; _i++) { - params[_i] = arguments[_i]; - } - return _this.dropByValueAll.apply(_this, params); - }, - head: function () { return _this.dropHead(); }, - tail: function () { return _this.dropTail(); }, - }; - }; - LinkedList.prototype.dropMany = function (count) { - var _this = this; - return { - byIndex: function (position) { return _this.dropManyByIndex(count, position); }, - head: function () { return _this.dropManyHead(count); }, - tail: function () { return _this.dropManyTail(count); }, - }; - }; - LinkedList.prototype.dropByIndex = function (position) { - if (position < 0) - position += this.size; - var current = this.get(position); - return current ? this.detach(current) : undefined; - }; - LinkedList.prototype.dropByValue = function (value, compareFn) { - if (compareFn === void 0) { compareFn = compare; } - var position = this.findIndex(function (node) { return compareFn(node.value, value); }); - return position < 0 ? undefined : this.dropByIndex(position); - }; - LinkedList.prototype.dropByValueAll = function (value, compareFn) { - if (compareFn === void 0) { compareFn = compare; } - var dropped = []; - for (var current = this.first, position = 0; current; position++, current = current.next) { - if (compareFn(current.value, value)) { - dropped.push(this.dropByIndex(position - dropped.length)); - } - } - return dropped; - }; - LinkedList.prototype.dropHead = function () { - var head = this.first; - if (head) { - this.first = head.next; - if (this.first) - this.first.previous = undefined; - else - this.last = undefined; - this.size--; - return head; - } - return undefined; - }; - LinkedList.prototype.dropTail = function () { - var tail = this.last; - if (tail) { - this.last = tail.previous; - if (this.last) - this.last.next = undefined; - else - this.first = undefined; - this.size--; - return tail; - } - return undefined; - }; - LinkedList.prototype.dropManyByIndex = function (count, position) { - if (count <= 0) - return []; - if (position < 0) - position = Math.max(position + this.size, 0); - else if (position >= this.size) - return []; - count = Math.min(count, this.size - position); - var dropped = []; - while (count--) { - var current = this.get(position); - dropped.push(this.detach(current)); - } - return dropped; - }; - LinkedList.prototype.dropManyHead = function (count) { - if (count <= 0) - return []; - count = Math.min(count, this.size); - var dropped = []; - while (count--) - dropped.unshift(this.dropHead()); - return dropped; - }; - LinkedList.prototype.dropManyTail = function (count) { - if (count <= 0) - return []; - count = Math.min(count, this.size); - var dropped = []; - while (count--) - dropped.push(this.dropTail()); - return dropped; - }; - LinkedList.prototype.find = function (predicate) { - for (var current = this.first, position = 0; current; position++, current = current.next) { - if (predicate(current, position, this)) - return current; - } - return undefined; - }; - LinkedList.prototype.findIndex = function (predicate) { - for (var current = this.first, position = 0; current; position++, current = current.next) { - if (predicate(current, position, this)) - return position; - } - return -1; - }; - LinkedList.prototype.forEach = function (iteratorFn) { - for (var node = this.first, position = 0; node; position++, node = node.next) { - iteratorFn(node, position, this); - } - }; - LinkedList.prototype.get = function (position) { - return this.find(function (_, index) { return position === index; }); - }; - LinkedList.prototype.indexOf = function (value, compareFn) { - if (compareFn === void 0) { compareFn = compare; } - return this.findIndex(function (node) { return compareFn(node.value, value); }); - }; - LinkedList.prototype.toArray = function () { - var array = new Array(this.size); - this.forEach(function (node, index) { return (array[index] = node.value); }); - return array; - }; - LinkedList.prototype.toNodeArray = function () { - var array = new Array(this.size); - this.forEach(function (node, index) { return (array[index] = node); }); - return array; - }; - LinkedList.prototype.toString = function (mapperFn) { - if (mapperFn === void 0) { mapperFn = JSON.stringify; } - return this.toArray() - .map(function (value) { return mapperFn(value); }) - .join(' <-> '); - }; - // Cannot use Generator type because of ng-packagr - LinkedList.prototype[Symbol.iterator] = function () { - var node, position; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - node = this.first, position = 0; - _a.label = 1; - case 1: - if (!node) return [3 /*break*/, 4]; - return [4 /*yield*/, node.value]; - case 2: - _a.sent(); - _a.label = 3; - case 3: - position++, node = node.next; - return [3 /*break*/, 1]; - case 4: return [2 /*return*/]; - } - }); - }; - return LinkedList; - }()); - - /* - * Public API Surface of utils - */ - - /** - * Generated bundle index. Do not edit. - */ - - exports.LinkedList = LinkedList; - exports.ListNode = ListNode; - - Object.defineProperty(exports, '__esModule', { value: true }); - -}))); -//# sourceMappingURL=abp-utils.umd.js.map diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/jquery-validation/jquery.validate.js b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/jquery-validation/jquery.validate.js deleted file mode 100644 index abc357d190..0000000000 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/jquery-validation/jquery.validate.js +++ /dev/null @@ -1,1601 +0,0 @@ -/*! - * jQuery Validation Plugin v1.17.0 - * - * https://jqueryvalidation.org/ - * - * Copyright (c) 2017 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 ) { - 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 ], - settings, staticRules, existingRules, data, param, filtered; - - // If nothing is selected, return empty object; can't chain anyway - if ( element == null ) { - return; - } - - if ( !element.form && element.hasAttribute( "contenteditable" ) ) { - 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 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 ) { - - // Set form expando on contenteditable - if ( !this.form && this.hasAttribute( "contenteditable" ) ) { - this.form = $( this ).closest( "form" )[ 0 ]; - this.name = $( this ).attr( "name" ); - } - - 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 - if ( !name && validator.settings.debug && window.console ) { - console.error( "%o has no name assigned", this ); - } - - // Set form expando on contenteditable - if ( this.hasAttribute( "contenteditable" ) ) { - this.form = $( this ).closest( "form" )[ 0 ]; - this.name = name; - } - - // 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, - 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 ( element.hasAttribute( "contenteditable" ) ) { - 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 ); - - if ( typeof val !== "string" ) { - throw new TypeError( "The normalizer should return a string value." ); - } - - // 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" ); - } - - }, - - 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() ); - 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.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( value, element ) { - 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/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.concat.min.js b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.concat.min.js deleted file mode 100644 index 8d13c0a985..0000000000 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.concat.min.js +++ /dev/null @@ -1,5 +0,0 @@ -/* == jquery mousewheel plugin == Version: 3.1.13, License: MIT License (MIT) */ -!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof exports?module.exports=a:a(jQuery)}(function(a){function b(b){var g=b||window.event,h=i.call(arguments,1),j=0,l=0,m=0,n=0,o=0,p=0;if(b=a.event.fix(g),b.type="mousewheel","detail"in g&&(m=-1*g.detail),"wheelDelta"in g&&(m=g.wheelDelta),"wheelDeltaY"in g&&(m=g.wheelDeltaY),"wheelDeltaX"in g&&(l=-1*g.wheelDeltaX),"axis"in g&&g.axis===g.HORIZONTAL_AXIS&&(l=-1*m,m=0),j=0===m?l:m,"deltaY"in g&&(m=-1*g.deltaY,j=m),"deltaX"in g&&(l=g.deltaX,0===m&&(j=-1*l)),0!==m||0!==l){if(1===g.deltaMode){var q=a.data(this,"mousewheel-line-height");j*=q,m*=q,l*=q}else if(2===g.deltaMode){var r=a.data(this,"mousewheel-page-height");j*=r,m*=r,l*=r}if(n=Math.max(Math.abs(m),Math.abs(l)),(!f||f>n)&&(f=n,d(g,n)&&(f/=40)),d(g,n)&&(j/=40,l/=40,m/=40),j=Math[j>=1?"floor":"ceil"](j/f),l=Math[l>=1?"floor":"ceil"](l/f),m=Math[m>=1?"floor":"ceil"](m/f),k.settings.normalizeOffset&&this.getBoundingClientRect){var s=this.getBoundingClientRect();o=b.clientX-s.left,p=b.clientY-s.top}return b.deltaX=l,b.deltaY=m,b.deltaFactor=f,b.offsetX=o,b.offsetY=p,b.deltaMode=0,h.unshift(b,j,l,m),e&&clearTimeout(e),e=setTimeout(c,200),(a.event.dispatch||a.event.handle).apply(this,h)}}function c(){f=null}function d(a,b){return k.settings.adjustOldDeltas&&"mousewheel"===a.type&&b%120===0}var e,f,g=["wheel","mousewheel","DOMMouseScroll","MozMousePixelScroll"],h="onwheel"in document||document.documentMode>=9?["wheel"]:["mousewheel","DomMouseScroll","MozMousePixelScroll"],i=Array.prototype.slice;if(a.event.fixHooks)for(var j=g.length;j;)a.event.fixHooks[g[--j]]=a.event.mouseHooks;var k=a.event.special.mousewheel={version:"3.1.12",setup:function(){if(this.addEventListener)for(var c=h.length;c;)this.addEventListener(h[--c],b,!1);else this.onmousewheel=b;a.data(this,"mousewheel-line-height",k.getLineHeight(this)),a.data(this,"mousewheel-page-height",k.getPageHeight(this))},teardown:function(){if(this.removeEventListener)for(var c=h.length;c;)this.removeEventListener(h[--c],b,!1);else this.onmousewheel=null;a.removeData(this,"mousewheel-line-height"),a.removeData(this,"mousewheel-page-height")},getLineHeight:function(b){var c=a(b),d=c["offsetParent"in a.fn?"offsetParent":"parent"]();return d.length||(d=a("body")),parseInt(d.css("fontSize"),10)||parseInt(c.css("fontSize"),10)||16},getPageHeight:function(b){return a(b).height()},settings:{adjustOldDeltas:!0,normalizeOffset:!0}};a.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})});!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof exports?module.exports=a:a(jQuery)}(function(a){function b(b){var g=b||window.event,h=i.call(arguments,1),j=0,l=0,m=0,n=0,o=0,p=0;if(b=a.event.fix(g),b.type="mousewheel","detail"in g&&(m=-1*g.detail),"wheelDelta"in g&&(m=g.wheelDelta),"wheelDeltaY"in g&&(m=g.wheelDeltaY),"wheelDeltaX"in g&&(l=-1*g.wheelDeltaX),"axis"in g&&g.axis===g.HORIZONTAL_AXIS&&(l=-1*m,m=0),j=0===m?l:m,"deltaY"in g&&(m=-1*g.deltaY,j=m),"deltaX"in g&&(l=g.deltaX,0===m&&(j=-1*l)),0!==m||0!==l){if(1===g.deltaMode){var q=a.data(this,"mousewheel-line-height");j*=q,m*=q,l*=q}else if(2===g.deltaMode){var r=a.data(this,"mousewheel-page-height");j*=r,m*=r,l*=r}if(n=Math.max(Math.abs(m),Math.abs(l)),(!f||f>n)&&(f=n,d(g,n)&&(f/=40)),d(g,n)&&(j/=40,l/=40,m/=40),j=Math[j>=1?"floor":"ceil"](j/f),l=Math[l>=1?"floor":"ceil"](l/f),m=Math[m>=1?"floor":"ceil"](m/f),k.settings.normalizeOffset&&this.getBoundingClientRect){var s=this.getBoundingClientRect();o=b.clientX-s.left,p=b.clientY-s.top}return b.deltaX=l,b.deltaY=m,b.deltaFactor=f,b.offsetX=o,b.offsetY=p,b.deltaMode=0,h.unshift(b,j,l,m),e&&clearTimeout(e),e=setTimeout(c,200),(a.event.dispatch||a.event.handle).apply(this,h)}}function c(){f=null}function d(a,b){return k.settings.adjustOldDeltas&&"mousewheel"===a.type&&b%120===0}var e,f,g=["wheel","mousewheel","DOMMouseScroll","MozMousePixelScroll"],h="onwheel"in document||document.documentMode>=9?["wheel"]:["mousewheel","DomMouseScroll","MozMousePixelScroll"],i=Array.prototype.slice;if(a.event.fixHooks)for(var j=g.length;j;)a.event.fixHooks[g[--j]]=a.event.mouseHooks;var k=a.event.special.mousewheel={version:"3.1.12",setup:function(){if(this.addEventListener)for(var c=h.length;c;)this.addEventListener(h[--c],b,!1);else this.onmousewheel=b;a.data(this,"mousewheel-line-height",k.getLineHeight(this)),a.data(this,"mousewheel-page-height",k.getPageHeight(this))},teardown:function(){if(this.removeEventListener)for(var c=h.length;c;)this.removeEventListener(h[--c],b,!1);else this.onmousewheel=null;a.removeData(this,"mousewheel-line-height"),a.removeData(this,"mousewheel-page-height")},getLineHeight:function(b){var c=a(b),d=c["offsetParent"in a.fn?"offsetParent":"parent"]();return d.length||(d=a("body")),parseInt(d.css("fontSize"),10)||parseInt(c.css("fontSize"),10)||16},getPageHeight:function(b){return a(b).height()},settings:{adjustOldDeltas:!0,normalizeOffset:!0}};a.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})}); -/* == malihu jquery custom scrollbar plugin == Version: 3.1.5, License: MIT License (MIT) */ -!function(e){"function"==typeof define&&define.amd?define(["jquery"],e):"undefined"!=typeof module&&module.exports?module.exports=e:e(jQuery,window,document)}(function(e){!function(t){var o="function"==typeof define&&define.amd,a="undefined"!=typeof module&&module.exports,n="https:"==document.location.protocol?"https:":"http:",i="cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.13/jquery.mousewheel.min.js";o||(a?require("jquery-mousewheel")(e):e.event.special.mousewheel||e("head").append(decodeURI("%3Cscript src="+n+"//"+i+"%3E%3C/script%3E"))),t()}(function(){var t,o="mCustomScrollbar",a="mCS",n=".mCustomScrollbar",i={setTop:0,setLeft:0,axis:"y",scrollbarPosition:"inside",scrollInertia:950,autoDraggerLength:!0,alwaysShowScrollbar:0,snapOffset:0,mouseWheel:{enable:!0,scrollAmount:"auto",axis:"y",deltaFactor:"auto",disableOver:["select","option","keygen","datalist","textarea"]},scrollButtons:{scrollType:"stepless",scrollAmount:"auto"},keyboard:{enable:!0,scrollType:"stepless",scrollAmount:"auto"},contentTouchScroll:25,documentTouchScroll:!0,advanced:{autoScrollOnFocus:"input,textarea,select,button,datalist,keygen,a[tabindex],area,object,[contenteditable='true']",updateOnContentResize:!0,updateOnImageLoad:"auto",autoUpdateTimeout:60},theme:"light",callbacks:{onTotalScrollOffset:0,onTotalScrollBackOffset:0,alwaysTriggerOffsets:!0}},r=0,l={},s=window.attachEvent&&!window.addEventListener?1:0,c=!1,d=["mCSB_dragger_onDrag","mCSB_scrollTools_onDrag","mCS_img_loaded","mCS_disabled","mCS_destroyed","mCS_no_scrollbar","mCS-autoHide","mCS-dir-rtl","mCS_no_scrollbar_y","mCS_no_scrollbar_x","mCS_y_hidden","mCS_x_hidden","mCSB_draggerContainer","mCSB_buttonUp","mCSB_buttonDown","mCSB_buttonLeft","mCSB_buttonRight"],u={init:function(t){var t=e.extend(!0,{},i,t),o=f.call(this);if(t.live){var s=t.liveSelector||this.selector||n,c=e(s);if("off"===t.live)return void m(s);l[s]=setTimeout(function(){c.mCustomScrollbar(t),"once"===t.live&&c.length&&m(s)},500)}else m(s);return t.setWidth=t.set_width?t.set_width:t.setWidth,t.setHeight=t.set_height?t.set_height:t.setHeight,t.axis=t.horizontalScroll?"x":p(t.axis),t.scrollInertia=t.scrollInertia>0&&t.scrollInertia<17?17:t.scrollInertia,"object"!=typeof t.mouseWheel&&1==t.mouseWheel&&(t.mouseWheel={enable:!0,scrollAmount:"auto",axis:"y",preventDefault:!1,deltaFactor:"auto",normalizeDelta:!1,invert:!1}),t.mouseWheel.scrollAmount=t.mouseWheelPixels?t.mouseWheelPixels:t.mouseWheel.scrollAmount,t.mouseWheel.normalizeDelta=t.advanced.normalizeMouseWheelDelta?t.advanced.normalizeMouseWheelDelta:t.mouseWheel.normalizeDelta,t.scrollButtons.scrollType=g(t.scrollButtons.scrollType),h(t),e(o).each(function(){var o=e(this);if(!o.data(a)){o.data(a,{idx:++r,opt:t,scrollRatio:{y:null,x:null},overflowed:null,contentReset:{y:null,x:null},bindEvents:!1,tweenRunning:!1,sequential:{},langDir:o.css("direction"),cbOffsets:null,trigger:null,poll:{size:{o:0,n:0},img:{o:0,n:0},change:{o:0,n:0}}});var n=o.data(a),i=n.opt,l=o.data("mcs-axis"),s=o.data("mcs-scrollbar-position"),c=o.data("mcs-theme");l&&(i.axis=l),s&&(i.scrollbarPosition=s),c&&(i.theme=c,h(i)),v.call(this),n&&i.callbacks.onCreate&&"function"==typeof i.callbacks.onCreate&&i.callbacks.onCreate.call(this),e("#mCSB_"+n.idx+"_container img:not(."+d[2]+")").addClass(d[2]),u.update.call(null,o)}})},update:function(t,o){var n=t||f.call(this);return e(n).each(function(){var t=e(this);if(t.data(a)){var n=t.data(a),i=n.opt,r=e("#mCSB_"+n.idx+"_container"),l=e("#mCSB_"+n.idx),s=[e("#mCSB_"+n.idx+"_dragger_vertical"),e("#mCSB_"+n.idx+"_dragger_horizontal")];if(!r.length)return;n.tweenRunning&&Q(t),o&&n&&i.callbacks.onBeforeUpdate&&"function"==typeof i.callbacks.onBeforeUpdate&&i.callbacks.onBeforeUpdate.call(this),t.hasClass(d[3])&&t.removeClass(d[3]),t.hasClass(d[4])&&t.removeClass(d[4]),l.css("max-height","none"),l.height()!==t.height()&&l.css("max-height",t.height()),_.call(this),"y"===i.axis||i.advanced.autoExpandHorizontalScroll||r.css("width",x(r)),n.overflowed=y.call(this),M.call(this),i.autoDraggerLength&&S.call(this),b.call(this),T.call(this);var c=[Math.abs(r[0].offsetTop),Math.abs(r[0].offsetLeft)];"x"!==i.axis&&(n.overflowed[0]?s[0].height()>s[0].parent().height()?B.call(this):(G(t,c[0].toString(),{dir:"y",dur:0,overwrite:"none"}),n.contentReset.y=null):(B.call(this),"y"===i.axis?k.call(this):"yx"===i.axis&&n.overflowed[1]&&G(t,c[1].toString(),{dir:"x",dur:0,overwrite:"none"}))),"y"!==i.axis&&(n.overflowed[1]?s[1].width()>s[1].parent().width()?B.call(this):(G(t,c[1].toString(),{dir:"x",dur:0,overwrite:"none"}),n.contentReset.x=null):(B.call(this),"x"===i.axis?k.call(this):"yx"===i.axis&&n.overflowed[0]&&G(t,c[0].toString(),{dir:"y",dur:0,overwrite:"none"}))),o&&n&&(2===o&&i.callbacks.onImageLoad&&"function"==typeof i.callbacks.onImageLoad?i.callbacks.onImageLoad.call(this):3===o&&i.callbacks.onSelectorChange&&"function"==typeof i.callbacks.onSelectorChange?i.callbacks.onSelectorChange.call(this):i.callbacks.onUpdate&&"function"==typeof i.callbacks.onUpdate&&i.callbacks.onUpdate.call(this)),N.call(this)}})},scrollTo:function(t,o){if("undefined"!=typeof t&&null!=t){var n=f.call(this);return e(n).each(function(){var n=e(this);if(n.data(a)){var i=n.data(a),r=i.opt,l={trigger:"external",scrollInertia:r.scrollInertia,scrollEasing:"mcsEaseInOut",moveDragger:!1,timeout:60,callbacks:!0,onStart:!0,onUpdate:!0,onComplete:!0},s=e.extend(!0,{},l,o),c=Y.call(this,t),d=s.scrollInertia>0&&s.scrollInertia<17?17:s.scrollInertia;c[0]=X.call(this,c[0],"y"),c[1]=X.call(this,c[1],"x"),s.moveDragger&&(c[0]*=i.scrollRatio.y,c[1]*=i.scrollRatio.x),s.dur=ne()?0:d,setTimeout(function(){null!==c[0]&&"undefined"!=typeof c[0]&&"x"!==r.axis&&i.overflowed[0]&&(s.dir="y",s.overwrite="all",G(n,c[0].toString(),s)),null!==c[1]&&"undefined"!=typeof c[1]&&"y"!==r.axis&&i.overflowed[1]&&(s.dir="x",s.overwrite="none",G(n,c[1].toString(),s))},s.timeout)}})}},stop:function(){var t=f.call(this);return e(t).each(function(){var t=e(this);t.data(a)&&Q(t)})},disable:function(t){var o=f.call(this);return e(o).each(function(){var o=e(this);if(o.data(a)){o.data(a);N.call(this,"remove"),k.call(this),t&&B.call(this),M.call(this,!0),o.addClass(d[3])}})},destroy:function(){var t=f.call(this);return e(t).each(function(){var n=e(this);if(n.data(a)){var i=n.data(a),r=i.opt,l=e("#mCSB_"+i.idx),s=e("#mCSB_"+i.idx+"_container"),c=e(".mCSB_"+i.idx+"_scrollbar");r.live&&m(r.liveSelector||e(t).selector),N.call(this,"remove"),k.call(this),B.call(this),n.removeData(a),$(this,"mcs"),c.remove(),s.find("img."+d[2]).removeClass(d[2]),l.replaceWith(s.contents()),n.removeClass(o+" _"+a+"_"+i.idx+" "+d[6]+" "+d[7]+" "+d[5]+" "+d[3]).addClass(d[4])}})}},f=function(){return"object"!=typeof e(this)||e(this).length<1?n:this},h=function(t){var o=["rounded","rounded-dark","rounded-dots","rounded-dots-dark"],a=["rounded-dots","rounded-dots-dark","3d","3d-dark","3d-thick","3d-thick-dark","inset","inset-dark","inset-2","inset-2-dark","inset-3","inset-3-dark"],n=["minimal","minimal-dark"],i=["minimal","minimal-dark"],r=["minimal","minimal-dark"];t.autoDraggerLength=e.inArray(t.theme,o)>-1?!1:t.autoDraggerLength,t.autoExpandScrollbar=e.inArray(t.theme,a)>-1?!1:t.autoExpandScrollbar,t.scrollButtons.enable=e.inArray(t.theme,n)>-1?!1:t.scrollButtons.enable,t.autoHideScrollbar=e.inArray(t.theme,i)>-1?!0:t.autoHideScrollbar,t.scrollbarPosition=e.inArray(t.theme,r)>-1?"outside":t.scrollbarPosition},m=function(e){l[e]&&(clearTimeout(l[e]),$(l,e))},p=function(e){return"yx"===e||"xy"===e||"auto"===e?"yx":"x"===e||"horizontal"===e?"x":"y"},g=function(e){return"stepped"===e||"pixels"===e||"step"===e||"click"===e?"stepped":"stepless"},v=function(){var t=e(this),n=t.data(a),i=n.opt,r=i.autoExpandScrollbar?" "+d[1]+"_expand":"",l=["
","
"],s="yx"===i.axis?"mCSB_vertical_horizontal":"x"===i.axis?"mCSB_horizontal":"mCSB_vertical",c="yx"===i.axis?l[0]+l[1]:"x"===i.axis?l[1]:l[0],u="yx"===i.axis?"
":"",f=i.autoHideScrollbar?" "+d[6]:"",h="x"!==i.axis&&"rtl"===n.langDir?" "+d[7]:"";i.setWidth&&t.css("width",i.setWidth),i.setHeight&&t.css("height",i.setHeight),i.setLeft="y"!==i.axis&&"rtl"===n.langDir?"989999px":i.setLeft,t.addClass(o+" _"+a+"_"+n.idx+f+h).wrapInner("
");var m=e("#mCSB_"+n.idx),p=e("#mCSB_"+n.idx+"_container");"y"===i.axis||i.advanced.autoExpandHorizontalScroll||p.css("width",x(p)),"outside"===i.scrollbarPosition?("static"===t.css("position")&&t.css("position","relative"),t.css("overflow","visible"),m.addClass("mCSB_outside").after(c)):(m.addClass("mCSB_inside").append(c),p.wrap(u)),w.call(this);var g=[e("#mCSB_"+n.idx+"_dragger_vertical"),e("#mCSB_"+n.idx+"_dragger_horizontal")];g[0].css("min-height",g[0].height()),g[1].css("min-width",g[1].width())},x=function(t){var o=[t[0].scrollWidth,Math.max.apply(Math,t.children().map(function(){return e(this).outerWidth(!0)}).get())],a=t.parent().width();return o[0]>a?o[0]:o[1]>a?o[1]:"100%"},_=function(){var t=e(this),o=t.data(a),n=o.opt,i=e("#mCSB_"+o.idx+"_container");if(n.advanced.autoExpandHorizontalScroll&&"y"!==n.axis){i.css({width:"auto","min-width":0,"overflow-x":"scroll"});var r=Math.ceil(i[0].scrollWidth);3===n.advanced.autoExpandHorizontalScroll||2!==n.advanced.autoExpandHorizontalScroll&&r>i.parent().width()?i.css({width:r,"min-width":"100%","overflow-x":"inherit"}):i.css({"overflow-x":"inherit",position:"absolute"}).wrap("
").css({width:Math.ceil(i[0].getBoundingClientRect().right+.4)-Math.floor(i[0].getBoundingClientRect().left),"min-width":"100%",position:"relative"}).unwrap()}},w=function(){var t=e(this),o=t.data(a),n=o.opt,i=e(".mCSB_"+o.idx+"_scrollbar:first"),r=oe(n.scrollButtons.tabindex)?"tabindex='"+n.scrollButtons.tabindex+"'":"",l=["","","",""],s=["x"===n.axis?l[2]:l[0],"x"===n.axis?l[3]:l[1],l[2],l[3]];n.scrollButtons.enable&&i.prepend(s[0]).append(s[1]).next(".mCSB_scrollTools").prepend(s[2]).append(s[3])},S=function(){var t=e(this),o=t.data(a),n=e("#mCSB_"+o.idx),i=e("#mCSB_"+o.idx+"_container"),r=[e("#mCSB_"+o.idx+"_dragger_vertical"),e("#mCSB_"+o.idx+"_dragger_horizontal")],l=[n.height()/i.outerHeight(!1),n.width()/i.outerWidth(!1)],c=[parseInt(r[0].css("min-height")),Math.round(l[0]*r[0].parent().height()),parseInt(r[1].css("min-width")),Math.round(l[1]*r[1].parent().width())],d=s&&c[1]r&&(r=s),c>l&&(l=c),[r>n.height(),l>n.width()]},B=function(){var t=e(this),o=t.data(a),n=o.opt,i=e("#mCSB_"+o.idx),r=e("#mCSB_"+o.idx+"_container"),l=[e("#mCSB_"+o.idx+"_dragger_vertical"),e("#mCSB_"+o.idx+"_dragger_horizontal")];if(Q(t),("x"!==n.axis&&!o.overflowed[0]||"y"===n.axis&&o.overflowed[0])&&(l[0].add(r).css("top",0),G(t,"_resetY")),"y"!==n.axis&&!o.overflowed[1]||"x"===n.axis&&o.overflowed[1]){var s=dx=0;"rtl"===o.langDir&&(s=i.width()-r.outerWidth(!1),dx=Math.abs(s/o.scrollRatio.x)),r.css("left",s),l[1].css("left",dx),G(t,"_resetX")}},T=function(){function t(){r=setTimeout(function(){e.event.special.mousewheel?(clearTimeout(r),W.call(o[0])):t()},100)}var o=e(this),n=o.data(a),i=n.opt;if(!n.bindEvents){if(I.call(this),i.contentTouchScroll&&D.call(this),E.call(this),i.mouseWheel.enable){var r;t()}P.call(this),U.call(this),i.advanced.autoScrollOnFocus&&H.call(this),i.scrollButtons.enable&&F.call(this),i.keyboard.enable&&q.call(this),n.bindEvents=!0}},k=function(){var t=e(this),o=t.data(a),n=o.opt,i=a+"_"+o.idx,r=".mCSB_"+o.idx+"_scrollbar",l=e("#mCSB_"+o.idx+",#mCSB_"+o.idx+"_container,#mCSB_"+o.idx+"_container_wrapper,"+r+" ."+d[12]+",#mCSB_"+o.idx+"_dragger_vertical,#mCSB_"+o.idx+"_dragger_horizontal,"+r+">a"),s=e("#mCSB_"+o.idx+"_container");n.advanced.releaseDraggableSelectors&&l.add(e(n.advanced.releaseDraggableSelectors)),n.advanced.extraDraggableSelectors&&l.add(e(n.advanced.extraDraggableSelectors)),o.bindEvents&&(e(document).add(e(!A()||top.document)).unbind("."+i),l.each(function(){e(this).unbind("."+i)}),clearTimeout(t[0]._focusTimeout),$(t[0],"_focusTimeout"),clearTimeout(o.sequential.step),$(o.sequential,"step"),clearTimeout(s[0].onCompleteTimeout),$(s[0],"onCompleteTimeout"),o.bindEvents=!1)},M=function(t){var o=e(this),n=o.data(a),i=n.opt,r=e("#mCSB_"+n.idx+"_container_wrapper"),l=r.length?r:e("#mCSB_"+n.idx+"_container"),s=[e("#mCSB_"+n.idx+"_scrollbar_vertical"),e("#mCSB_"+n.idx+"_scrollbar_horizontal")],c=[s[0].find(".mCSB_dragger"),s[1].find(".mCSB_dragger")];"x"!==i.axis&&(n.overflowed[0]&&!t?(s[0].add(c[0]).add(s[0].children("a")).css("display","block"),l.removeClass(d[8]+" "+d[10])):(i.alwaysShowScrollbar?(2!==i.alwaysShowScrollbar&&c[0].css("display","none"),l.removeClass(d[10])):(s[0].css("display","none"),l.addClass(d[10])),l.addClass(d[8]))),"y"!==i.axis&&(n.overflowed[1]&&!t?(s[1].add(c[1]).add(s[1].children("a")).css("display","block"),l.removeClass(d[9]+" "+d[11])):(i.alwaysShowScrollbar?(2!==i.alwaysShowScrollbar&&c[1].css("display","none"),l.removeClass(d[11])):(s[1].css("display","none"),l.addClass(d[11])),l.addClass(d[9]))),n.overflowed[0]||n.overflowed[1]?o.removeClass(d[5]):o.addClass(d[5])},O=function(t){var o=t.type,a=t.target.ownerDocument!==document&&null!==frameElement?[e(frameElement).offset().top,e(frameElement).offset().left]:null,n=A()&&t.target.ownerDocument!==top.document&&null!==frameElement?[e(t.view.frameElement).offset().top,e(t.view.frameElement).offset().left]:[0,0];switch(o){case"pointerdown":case"MSPointerDown":case"pointermove":case"MSPointerMove":case"pointerup":case"MSPointerUp":return a?[t.originalEvent.pageY-a[0]+n[0],t.originalEvent.pageX-a[1]+n[1],!1]:[t.originalEvent.pageY,t.originalEvent.pageX,!1];case"touchstart":case"touchmove":case"touchend":var i=t.originalEvent.touches[0]||t.originalEvent.changedTouches[0],r=t.originalEvent.touches.length||t.originalEvent.changedTouches.length;return t.target.ownerDocument!==document?[i.screenY,i.screenX,r>1]:[i.pageY,i.pageX,r>1];default:return a?[t.pageY-a[0]+n[0],t.pageX-a[1]+n[1],!1]:[t.pageY,t.pageX,!1]}},I=function(){function t(e,t,a,n){if(h[0].idleTimer=d.scrollInertia<233?250:0,o.attr("id")===f[1])var i="x",s=(o[0].offsetLeft-t+n)*l.scrollRatio.x;else var i="y",s=(o[0].offsetTop-e+a)*l.scrollRatio.y;G(r,s.toString(),{dir:i,drag:!0})}var o,n,i,r=e(this),l=r.data(a),d=l.opt,u=a+"_"+l.idx,f=["mCSB_"+l.idx+"_dragger_vertical","mCSB_"+l.idx+"_dragger_horizontal"],h=e("#mCSB_"+l.idx+"_container"),m=e("#"+f[0]+",#"+f[1]),p=d.advanced.releaseDraggableSelectors?m.add(e(d.advanced.releaseDraggableSelectors)):m,g=d.advanced.extraDraggableSelectors?e(!A()||top.document).add(e(d.advanced.extraDraggableSelectors)):e(!A()||top.document);m.bind("contextmenu."+u,function(e){e.preventDefault()}).bind("mousedown."+u+" touchstart."+u+" pointerdown."+u+" MSPointerDown."+u,function(t){if(t.stopImmediatePropagation(),t.preventDefault(),ee(t)){c=!0,s&&(document.onselectstart=function(){return!1}),L.call(h,!1),Q(r),o=e(this);var a=o.offset(),l=O(t)[0]-a.top,u=O(t)[1]-a.left,f=o.height()+a.top,m=o.width()+a.left;f>l&&l>0&&m>u&&u>0&&(n=l,i=u),C(o,"active",d.autoExpandScrollbar)}}).bind("touchmove."+u,function(e){e.stopImmediatePropagation(),e.preventDefault();var a=o.offset(),r=O(e)[0]-a.top,l=O(e)[1]-a.left;t(n,i,r,l)}),e(document).add(g).bind("mousemove."+u+" pointermove."+u+" MSPointerMove."+u,function(e){if(o){var a=o.offset(),r=O(e)[0]-a.top,l=O(e)[1]-a.left;if(n===r&&i===l)return;t(n,i,r,l)}}).add(p).bind("mouseup."+u+" touchend."+u+" pointerup."+u+" MSPointerUp."+u,function(){o&&(C(o,"active",d.autoExpandScrollbar),o=null),c=!1,s&&(document.onselectstart=null),L.call(h,!0)})},D=function(){function o(e){if(!te(e)||c||O(e)[2])return void(t=0);t=1,b=0,C=0,d=1,y.removeClass("mCS_touch_action");var o=I.offset();u=O(e)[0]-o.top,f=O(e)[1]-o.left,z=[O(e)[0],O(e)[1]]}function n(e){if(te(e)&&!c&&!O(e)[2]&&(T.documentTouchScroll||e.preventDefault(),e.stopImmediatePropagation(),(!C||b)&&d)){g=K();var t=M.offset(),o=O(e)[0]-t.top,a=O(e)[1]-t.left,n="mcsLinearOut";if(E.push(o),W.push(a),z[2]=Math.abs(O(e)[0]-z[0]),z[3]=Math.abs(O(e)[1]-z[1]),B.overflowed[0])var i=D[0].parent().height()-D[0].height(),r=u-o>0&&o-u>-(i*B.scrollRatio.y)&&(2*z[3]0&&a-f>-(l*B.scrollRatio.x)&&(2*z[2]30)){_=1e3/(v-p);var n="mcsEaseOut",i=2.5>_,r=i?[E[E.length-2],W[W.length-2]]:[0,0];x=i?[o-r[0],a-r[1]]:[o-h,a-m];var u=[Math.abs(x[0]),Math.abs(x[1])];_=i?[Math.abs(x[0]/4),Math.abs(x[1]/4)]:[_,_];var f=[Math.abs(I[0].offsetTop)-x[0]*l(u[0]/_[0],_[0]),Math.abs(I[0].offsetLeft)-x[1]*l(u[1]/_[1],_[1])];w="yx"===T.axis?[f[0],f[1]]:"x"===T.axis?[null,f[1]]:[f[0],null],S=[4*u[0]+T.scrollInertia,4*u[1]+T.scrollInertia];var y=parseInt(T.contentTouchScroll)||0;w[0]=u[0]>y?w[0]:0,w[1]=u[1]>y?w[1]:0,B.overflowed[0]&&s(w[0],S[0],n,"y",L,!1),B.overflowed[1]&&s(w[1],S[1],n,"x",L,!1)}}}function l(e,t){var o=[1.5*t,2*t,t/1.5,t/2];return e>90?t>4?o[0]:o[3]:e>60?t>3?o[3]:o[2]:e>30?t>8?o[1]:t>6?o[0]:t>4?t:o[2]:t>8?t:o[3]}function s(e,t,o,a,n,i){e&&G(y,e.toString(),{dur:t,scrollEasing:o,dir:a,overwrite:n,drag:i})}var d,u,f,h,m,p,g,v,x,_,w,S,b,C,y=e(this),B=y.data(a),T=B.opt,k=a+"_"+B.idx,M=e("#mCSB_"+B.idx),I=e("#mCSB_"+B.idx+"_container"),D=[e("#mCSB_"+B.idx+"_dragger_vertical"),e("#mCSB_"+B.idx+"_dragger_horizontal")],E=[],W=[],R=0,L="yx"===T.axis?"none":"all",z=[],P=I.find("iframe"),H=["touchstart."+k+" pointerdown."+k+" MSPointerDown."+k,"touchmove."+k+" pointermove."+k+" MSPointerMove."+k,"touchend."+k+" pointerup."+k+" MSPointerUp."+k],U=void 0!==document.body.style.touchAction&&""!==document.body.style.touchAction;I.bind(H[0],function(e){o(e)}).bind(H[1],function(e){n(e)}),M.bind(H[0],function(e){i(e)}).bind(H[2],function(e){r(e)}),P.length&&P.each(function(){e(this).bind("load",function(){A(this)&&e(this.contentDocument||this.contentWindow.document).bind(H[0],function(e){o(e),i(e)}).bind(H[1],function(e){n(e)}).bind(H[2],function(e){r(e)})})})},E=function(){function o(){return window.getSelection?window.getSelection().toString():document.selection&&"Control"!=document.selection.type?document.selection.createRange().text:0}function n(e,t,o){d.type=o&&i?"stepped":"stepless",d.scrollAmount=10,j(r,e,t,"mcsLinearOut",o?60:null)}var i,r=e(this),l=r.data(a),s=l.opt,d=l.sequential,u=a+"_"+l.idx,f=e("#mCSB_"+l.idx+"_container"),h=f.parent();f.bind("mousedown."+u,function(){t||i||(i=1,c=!0)}).add(document).bind("mousemove."+u,function(e){if(!t&&i&&o()){var a=f.offset(),r=O(e)[0]-a.top+f[0].offsetTop,c=O(e)[1]-a.left+f[0].offsetLeft;r>0&&r0&&cr?n("on",38):r>h.height()&&n("on",40)),"y"!==s.axis&&l.overflowed[1]&&(0>c?n("on",37):c>h.width()&&n("on",39)))}}).bind("mouseup."+u+" dragend."+u,function(){t||(i&&(i=0,n("off",null)),c=!1)})},W=function(){function t(t,a){if(Q(o),!z(o,t.target)){var r="auto"!==i.mouseWheel.deltaFactor?parseInt(i.mouseWheel.deltaFactor):s&&t.deltaFactor<100?100:t.deltaFactor||100,d=i.scrollInertia;if("x"===i.axis||"x"===i.mouseWheel.axis)var u="x",f=[Math.round(r*n.scrollRatio.x),parseInt(i.mouseWheel.scrollAmount)],h="auto"!==i.mouseWheel.scrollAmount?f[1]:f[0]>=l.width()?.9*l.width():f[0],m=Math.abs(e("#mCSB_"+n.idx+"_container")[0].offsetLeft),p=c[1][0].offsetLeft,g=c[1].parent().width()-c[1].width(),v="y"===i.mouseWheel.axis?t.deltaY||a:t.deltaX;else var u="y",f=[Math.round(r*n.scrollRatio.y),parseInt(i.mouseWheel.scrollAmount)],h="auto"!==i.mouseWheel.scrollAmount?f[1]:f[0]>=l.height()?.9*l.height():f[0],m=Math.abs(e("#mCSB_"+n.idx+"_container")[0].offsetTop),p=c[0][0].offsetTop,g=c[0].parent().height()-c[0].height(),v=t.deltaY||a;"y"===u&&!n.overflowed[0]||"x"===u&&!n.overflowed[1]||((i.mouseWheel.invert||t.webkitDirectionInvertedFromDevice)&&(v=-v),i.mouseWheel.normalizeDelta&&(v=0>v?-1:1),(v>0&&0!==p||0>v&&p!==g||i.mouseWheel.preventDefault)&&(t.stopImmediatePropagation(),t.preventDefault()),t.deltaFactor<5&&!i.mouseWheel.normalizeDelta&&(h=t.deltaFactor,d=17),G(o,(m-v*h).toString(),{dir:u,dur:d}))}}if(e(this).data(a)){var o=e(this),n=o.data(a),i=n.opt,r=a+"_"+n.idx,l=e("#mCSB_"+n.idx),c=[e("#mCSB_"+n.idx+"_dragger_vertical"),e("#mCSB_"+n.idx+"_dragger_horizontal")],d=e("#mCSB_"+n.idx+"_container").find("iframe");d.length&&d.each(function(){e(this).bind("load",function(){A(this)&&e(this.contentDocument||this.contentWindow.document).bind("mousewheel."+r,function(e,o){t(e,o)})})}),l.bind("mousewheel."+r,function(e,o){t(e,o)})}},R=new Object,A=function(t){var o=!1,a=!1,n=null;if(void 0===t?a="#empty":void 0!==e(t).attr("id")&&(a=e(t).attr("id")),a!==!1&&void 0!==R[a])return R[a];if(t){try{var i=t.contentDocument||t.contentWindow.document;n=i.body.innerHTML}catch(r){}o=null!==n}else{try{var i=top.document;n=i.body.innerHTML}catch(r){}o=null!==n}return a!==!1&&(R[a]=o),o},L=function(e){var t=this.find("iframe");if(t.length){var o=e?"auto":"none";t.css("pointer-events",o)}},z=function(t,o){var n=o.nodeName.toLowerCase(),i=t.data(a).opt.mouseWheel.disableOver,r=["select","textarea"];return e.inArray(n,i)>-1&&!(e.inArray(n,r)>-1&&!e(o).is(":focus"))},P=function(){var t,o=e(this),n=o.data(a),i=a+"_"+n.idx,r=e("#mCSB_"+n.idx+"_container"),l=r.parent(),s=e(".mCSB_"+n.idx+"_scrollbar ."+d[12]);s.bind("mousedown."+i+" touchstart."+i+" pointerdown."+i+" MSPointerDown."+i,function(o){c=!0,e(o.target).hasClass("mCSB_dragger")||(t=1)}).bind("touchend."+i+" pointerup."+i+" MSPointerUp."+i,function(){c=!1}).bind("click."+i,function(a){if(t&&(t=0,e(a.target).hasClass(d[12])||e(a.target).hasClass("mCSB_draggerRail"))){Q(o);var i=e(this),s=i.find(".mCSB_dragger");if(i.parent(".mCSB_scrollTools_horizontal").length>0){if(!n.overflowed[1])return;var c="x",u=a.pageX>s.offset().left?-1:1,f=Math.abs(r[0].offsetLeft)-u*(.9*l.width())}else{if(!n.overflowed[0])return;var c="y",u=a.pageY>s.offset().top?-1:1,f=Math.abs(r[0].offsetTop)-u*(.9*l.height())}G(o,f.toString(),{dir:c,scrollEasing:"mcsEaseInOut"})}})},H=function(){var t=e(this),o=t.data(a),n=o.opt,i=a+"_"+o.idx,r=e("#mCSB_"+o.idx+"_container"),l=r.parent();r.bind("focusin."+i,function(){var o=e(document.activeElement),a=r.find(".mCustomScrollBox").length,i=0;o.is(n.advanced.autoScrollOnFocus)&&(Q(t),clearTimeout(t[0]._focusTimeout),t[0]._focusTimer=a?(i+17)*a:0,t[0]._focusTimeout=setTimeout(function(){var e=[ae(o)[0],ae(o)[1]],a=[r[0].offsetTop,r[0].offsetLeft],s=[a[0]+e[0]>=0&&a[0]+e[0]=0&&a[0]+e[1]a");s.bind("contextmenu."+r,function(e){e.preventDefault()}).bind("mousedown."+r+" touchstart."+r+" pointerdown."+r+" MSPointerDown."+r+" mouseup."+r+" touchend."+r+" pointerup."+r+" MSPointerUp."+r+" mouseout."+r+" pointerout."+r+" MSPointerOut."+r+" click."+r,function(a){function r(e,o){i.scrollAmount=n.scrollButtons.scrollAmount,j(t,e,o)}if(a.preventDefault(),ee(a)){var l=e(this).attr("class");switch(i.type=n.scrollButtons.scrollType,a.type){case"mousedown":case"touchstart":case"pointerdown":case"MSPointerDown":if("stepped"===i.type)return;c=!0,o.tweenRunning=!1,r("on",l);break;case"mouseup":case"touchend":case"pointerup":case"MSPointerUp":case"mouseout":case"pointerout":case"MSPointerOut":if("stepped"===i.type)return;c=!1,i.dir&&r("off",l);break;case"click":if("stepped"!==i.type||o.tweenRunning)return;r("on",l)}}})},q=function(){function t(t){function a(e,t){r.type=i.keyboard.scrollType,r.scrollAmount=i.keyboard.scrollAmount,"stepped"===r.type&&n.tweenRunning||j(o,e,t)}switch(t.type){case"blur":n.tweenRunning&&r.dir&&a("off",null);break;case"keydown":case"keyup":var l=t.keyCode?t.keyCode:t.which,s="on";if("x"!==i.axis&&(38===l||40===l)||"y"!==i.axis&&(37===l||39===l)){if((38===l||40===l)&&!n.overflowed[0]||(37===l||39===l)&&!n.overflowed[1])return;"keyup"===t.type&&(s="off"),e(document.activeElement).is(u)||(t.preventDefault(),t.stopImmediatePropagation(),a(s,l))}else if(33===l||34===l){if((n.overflowed[0]||n.overflowed[1])&&(t.preventDefault(),t.stopImmediatePropagation()),"keyup"===t.type){Q(o);var f=34===l?-1:1;if("x"===i.axis||"yx"===i.axis&&n.overflowed[1]&&!n.overflowed[0])var h="x",m=Math.abs(c[0].offsetLeft)-f*(.9*d.width());else var h="y",m=Math.abs(c[0].offsetTop)-f*(.9*d.height());G(o,m.toString(),{dir:h,scrollEasing:"mcsEaseInOut"})}}else if((35===l||36===l)&&!e(document.activeElement).is(u)&&((n.overflowed[0]||n.overflowed[1])&&(t.preventDefault(),t.stopImmediatePropagation()),"keyup"===t.type)){if("x"===i.axis||"yx"===i.axis&&n.overflowed[1]&&!n.overflowed[0])var h="x",m=35===l?Math.abs(d.width()-c.outerWidth(!1)):0;else var h="y",m=35===l?Math.abs(d.height()-c.outerHeight(!1)):0;G(o,m.toString(),{dir:h,scrollEasing:"mcsEaseInOut"})}}}var o=e(this),n=o.data(a),i=n.opt,r=n.sequential,l=a+"_"+n.idx,s=e("#mCSB_"+n.idx),c=e("#mCSB_"+n.idx+"_container"),d=c.parent(),u="input,textarea,select,datalist,keygen,[contenteditable='true']",f=c.find("iframe"),h=["blur."+l+" keydown."+l+" keyup."+l];f.length&&f.each(function(){e(this).bind("load",function(){A(this)&&e(this.contentDocument||this.contentWindow.document).bind(h[0],function(e){t(e)})})}),s.attr("tabindex","0").bind(h[0],function(e){t(e)})},j=function(t,o,n,i,r){function l(e){u.snapAmount&&(f.scrollAmount=u.snapAmount instanceof Array?"x"===f.dir[0]?u.snapAmount[1]:u.snapAmount[0]:u.snapAmount);var o="stepped"!==f.type,a=r?r:e?o?p/1.5:g:1e3/60,n=e?o?7.5:40:2.5,s=[Math.abs(h[0].offsetTop),Math.abs(h[0].offsetLeft)],d=[c.scrollRatio.y>10?10:c.scrollRatio.y,c.scrollRatio.x>10?10:c.scrollRatio.x],m="x"===f.dir[0]?s[1]+f.dir[1]*(d[1]*n):s[0]+f.dir[1]*(d[0]*n),v="x"===f.dir[0]?s[1]+f.dir[1]*parseInt(f.scrollAmount):s[0]+f.dir[1]*parseInt(f.scrollAmount),x="auto"!==f.scrollAmount?v:m,_=i?i:e?o?"mcsLinearOut":"mcsEaseInOut":"mcsLinear",w=!!e;return e&&17>a&&(x="x"===f.dir[0]?s[1]:s[0]),G(t,x.toString(),{dir:f.dir[0],scrollEasing:_,dur:a,onComplete:w}),e?void(f.dir=!1):(clearTimeout(f.step),void(f.step=setTimeout(function(){l()},a)))}function s(){clearTimeout(f.step),$(f,"step"),Q(t)}var c=t.data(a),u=c.opt,f=c.sequential,h=e("#mCSB_"+c.idx+"_container"),m="stepped"===f.type,p=u.scrollInertia<26?26:u.scrollInertia,g=u.scrollInertia<1?17:u.scrollInertia;switch(o){case"on":if(f.dir=[n===d[16]||n===d[15]||39===n||37===n?"x":"y",n===d[13]||n===d[15]||38===n||37===n?-1:1],Q(t),oe(n)&&"stepped"===f.type)return;l(m);break;case"off":s(),(m||c.tweenRunning&&f.dir)&&l(!0)}},Y=function(t){var o=e(this).data(a).opt,n=[];return"function"==typeof t&&(t=t()),t instanceof Array?n=t.length>1?[t[0],t[1]]:"x"===o.axis?[null,t[0]]:[t[0],null]:(n[0]=t.y?t.y:t.x||"x"===o.axis?null:t,n[1]=t.x?t.x:t.y||"y"===o.axis?null:t),"function"==typeof n[0]&&(n[0]=n[0]()),"function"==typeof n[1]&&(n[1]=n[1]()),n},X=function(t,o){if(null!=t&&"undefined"!=typeof t){var n=e(this),i=n.data(a),r=i.opt,l=e("#mCSB_"+i.idx+"_container"),s=l.parent(),c=typeof t;o||(o="x"===r.axis?"x":"y");var d="x"===o?l.outerWidth(!1)-s.width():l.outerHeight(!1)-s.height(),f="x"===o?l[0].offsetLeft:l[0].offsetTop,h="x"===o?"left":"top";switch(c){case"function":return t();case"object":var m=t.jquery?t:e(t);if(!m.length)return;return"x"===o?ae(m)[1]:ae(m)[0];case"string":case"number":if(oe(t))return Math.abs(t);if(-1!==t.indexOf("%"))return Math.abs(d*parseInt(t)/100);if(-1!==t.indexOf("-="))return Math.abs(f-parseInt(t.split("-=")[1]));if(-1!==t.indexOf("+=")){var p=f+parseInt(t.split("+=")[1]);return p>=0?0:Math.abs(p)}if(-1!==t.indexOf("px")&&oe(t.split("px")[0]))return Math.abs(t.split("px")[0]);if("top"===t||"left"===t)return 0;if("bottom"===t)return Math.abs(s.height()-l.outerHeight(!1));if("right"===t)return Math.abs(s.width()-l.outerWidth(!1));if("first"===t||"last"===t){var m=l.find(":"+t);return"x"===o?ae(m)[1]:ae(m)[0]}return e(t).length?"x"===o?ae(e(t))[1]:ae(e(t))[0]:(l.css(h,t),void u.update.call(null,n[0]))}}},N=function(t){function o(){return clearTimeout(f[0].autoUpdate),0===l.parents("html").length?void(l=null):void(f[0].autoUpdate=setTimeout(function(){return c.advanced.updateOnSelectorChange&&(s.poll.change.n=i(),s.poll.change.n!==s.poll.change.o)?(s.poll.change.o=s.poll.change.n,void r(3)):c.advanced.updateOnContentResize&&(s.poll.size.n=l[0].scrollHeight+l[0].scrollWidth+f[0].offsetHeight+l[0].offsetHeight+l[0].offsetWidth,s.poll.size.n!==s.poll.size.o)?(s.poll.size.o=s.poll.size.n,void r(1)):!c.advanced.updateOnImageLoad||"auto"===c.advanced.updateOnImageLoad&&"y"===c.axis||(s.poll.img.n=f.find("img").length,s.poll.img.n===s.poll.img.o)?void((c.advanced.updateOnSelectorChange||c.advanced.updateOnContentResize||c.advanced.updateOnImageLoad)&&o()):(s.poll.img.o=s.poll.img.n,void f.find("img").each(function(){n(this)}))},c.advanced.autoUpdateTimeout))}function n(t){function o(e,t){return function(){ -return t.apply(e,arguments)}}function a(){this.onload=null,e(t).addClass(d[2]),r(2)}if(e(t).hasClass(d[2]))return void r();var n=new Image;n.onload=o(n,a),n.src=t.src}function i(){c.advanced.updateOnSelectorChange===!0&&(c.advanced.updateOnSelectorChange="*");var e=0,t=f.find(c.advanced.updateOnSelectorChange);return c.advanced.updateOnSelectorChange&&t.length>0&&t.each(function(){e+=this.offsetHeight+this.offsetWidth}),e}function r(e){clearTimeout(f[0].autoUpdate),u.update.call(null,l[0],e)}var l=e(this),s=l.data(a),c=s.opt,f=e("#mCSB_"+s.idx+"_container");return t?(clearTimeout(f[0].autoUpdate),void $(f[0],"autoUpdate")):void o()},V=function(e,t,o){return Math.round(e/t)*t-o},Q=function(t){var o=t.data(a),n=e("#mCSB_"+o.idx+"_container,#mCSB_"+o.idx+"_container_wrapper,#mCSB_"+o.idx+"_dragger_vertical,#mCSB_"+o.idx+"_dragger_horizontal");n.each(function(){Z.call(this)})},G=function(t,o,n){function i(e){return s&&c.callbacks[e]&&"function"==typeof c.callbacks[e]}function r(){return[c.callbacks.alwaysTriggerOffsets||w>=S[0]+y,c.callbacks.alwaysTriggerOffsets||-B>=w]}function l(){var e=[h[0].offsetTop,h[0].offsetLeft],o=[x[0].offsetTop,x[0].offsetLeft],a=[h.outerHeight(!1),h.outerWidth(!1)],i=[f.height(),f.width()];t[0].mcs={content:h,top:e[0],left:e[1],draggerTop:o[0],draggerLeft:o[1],topPct:Math.round(100*Math.abs(e[0])/(Math.abs(a[0])-i[0])),leftPct:Math.round(100*Math.abs(e[1])/(Math.abs(a[1])-i[1])),direction:n.dir}}var s=t.data(a),c=s.opt,d={trigger:"internal",dir:"y",scrollEasing:"mcsEaseOut",drag:!1,dur:c.scrollInertia,overwrite:"all",callbacks:!0,onStart:!0,onUpdate:!0,onComplete:!0},n=e.extend(d,n),u=[n.dur,n.drag?0:n.dur],f=e("#mCSB_"+s.idx),h=e("#mCSB_"+s.idx+"_container"),m=h.parent(),p=c.callbacks.onTotalScrollOffset?Y.call(t,c.callbacks.onTotalScrollOffset):[0,0],g=c.callbacks.onTotalScrollBackOffset?Y.call(t,c.callbacks.onTotalScrollBackOffset):[0,0];if(s.trigger=n.trigger,0===m.scrollTop()&&0===m.scrollLeft()||(e(".mCSB_"+s.idx+"_scrollbar").css("visibility","visible"),m.scrollTop(0).scrollLeft(0)),"_resetY"!==o||s.contentReset.y||(i("onOverflowYNone")&&c.callbacks.onOverflowYNone.call(t[0]),s.contentReset.y=1),"_resetX"!==o||s.contentReset.x||(i("onOverflowXNone")&&c.callbacks.onOverflowXNone.call(t[0]),s.contentReset.x=1),"_resetY"!==o&&"_resetX"!==o){if(!s.contentReset.y&&t[0].mcs||!s.overflowed[0]||(i("onOverflowY")&&c.callbacks.onOverflowY.call(t[0]),s.contentReset.x=null),!s.contentReset.x&&t[0].mcs||!s.overflowed[1]||(i("onOverflowX")&&c.callbacks.onOverflowX.call(t[0]),s.contentReset.x=null),c.snapAmount){var v=c.snapAmount instanceof Array?"x"===n.dir?c.snapAmount[1]:c.snapAmount[0]:c.snapAmount;o=V(o,v,c.snapOffset)}switch(n.dir){case"x":var x=e("#mCSB_"+s.idx+"_dragger_horizontal"),_="left",w=h[0].offsetLeft,S=[f.width()-h.outerWidth(!1),x.parent().width()-x.width()],b=[o,0===o?0:o/s.scrollRatio.x],y=p[1],B=g[1],T=y>0?y/s.scrollRatio.x:0,k=B>0?B/s.scrollRatio.x:0;break;case"y":var x=e("#mCSB_"+s.idx+"_dragger_vertical"),_="top",w=h[0].offsetTop,S=[f.height()-h.outerHeight(!1),x.parent().height()-x.height()],b=[o,0===o?0:o/s.scrollRatio.y],y=p[0],B=g[0],T=y>0?y/s.scrollRatio.y:0,k=B>0?B/s.scrollRatio.y:0}b[1]<0||0===b[0]&&0===b[1]?b=[0,0]:b[1]>=S[1]?b=[S[0],S[1]]:b[0]=-b[0],t[0].mcs||(l(),i("onInit")&&c.callbacks.onInit.call(t[0])),clearTimeout(h[0].onCompleteTimeout),J(x[0],_,Math.round(b[1]),u[1],n.scrollEasing),!s.tweenRunning&&(0===w&&b[0]>=0||w===S[0]&&b[0]<=S[0])||J(h[0],_,Math.round(b[0]),u[0],n.scrollEasing,n.overwrite,{onStart:function(){n.callbacks&&n.onStart&&!s.tweenRunning&&(i("onScrollStart")&&(l(),c.callbacks.onScrollStart.call(t[0])),s.tweenRunning=!0,C(x),s.cbOffsets=r())},onUpdate:function(){n.callbacks&&n.onUpdate&&i("whileScrolling")&&(l(),c.callbacks.whileScrolling.call(t[0]))},onComplete:function(){if(n.callbacks&&n.onComplete){"yx"===c.axis&&clearTimeout(h[0].onCompleteTimeout);var e=h[0].idleTimer||0;h[0].onCompleteTimeout=setTimeout(function(){i("onScroll")&&(l(),c.callbacks.onScroll.call(t[0])),i("onTotalScroll")&&b[1]>=S[1]-T&&s.cbOffsets[0]&&(l(),c.callbacks.onTotalScroll.call(t[0])),i("onTotalScrollBack")&&b[1]<=k&&s.cbOffsets[1]&&(l(),c.callbacks.onTotalScrollBack.call(t[0])),s.tweenRunning=!1,h[0].idleTimer=0,C(x,"hide")},e)}}})}},J=function(e,t,o,a,n,i,r){function l(){S.stop||(x||m.call(),x=K()-v,s(),x>=S.time&&(S.time=x>S.time?x+f-(x-S.time):x+f-1,S.time0?(S.currVal=u(S.time,_,b,a,n),w[t]=Math.round(S.currVal)+"px"):w[t]=o+"px",p.call()}function c(){f=1e3/60,S.time=x+f,h=window.requestAnimationFrame?window.requestAnimationFrame:function(e){return s(),setTimeout(e,.01)},S.id=h(l)}function d(){null!=S.id&&(window.requestAnimationFrame?window.cancelAnimationFrame(S.id):clearTimeout(S.id),S.id=null)}function u(e,t,o,a,n){switch(n){case"linear":case"mcsLinear":return o*e/a+t;case"mcsLinearOut":return e/=a,e--,o*Math.sqrt(1-e*e)+t;case"easeInOutSmooth":return e/=a/2,1>e?o/2*e*e+t:(e--,-o/2*(e*(e-2)-1)+t);case"easeInOutStrong":return e/=a/2,1>e?o/2*Math.pow(2,10*(e-1))+t:(e--,o/2*(-Math.pow(2,-10*e)+2)+t);case"easeInOut":case"mcsEaseInOut":return e/=a/2,1>e?o/2*e*e*e+t:(e-=2,o/2*(e*e*e+2)+t);case"easeOutSmooth":return e/=a,e--,-o*(e*e*e*e-1)+t;case"easeOutStrong":return o*(-Math.pow(2,-10*e/a)+1)+t;case"easeOut":case"mcsEaseOut":default:var i=(e/=a)*e,r=i*e;return t+o*(.499999999999997*r*i+-2.5*i*i+5.5*r+-6.5*i+4*e)}}e._mTween||(e._mTween={top:{},left:{}});var f,h,r=r||{},m=r.onStart||function(){},p=r.onUpdate||function(){},g=r.onComplete||function(){},v=K(),x=0,_=e.offsetTop,w=e.style,S=e._mTween[t];"left"===t&&(_=e.offsetLeft);var b=o-_;S.stop=0,"none"!==i&&d(),c()},K=function(){return window.performance&&window.performance.now?window.performance.now():window.performance&&window.performance.webkitNow?window.performance.webkitNow():Date.now?Date.now():(new Date).getTime()},Z=function(){var e=this;e._mTween||(e._mTween={top:{},left:{}});for(var t=["top","left"],o=0;o=0&&a[0]+ae(n)[0]=0&&a[1]+ae(n)[1]=0&&r[1]-i[1]*l[1][0]<0&&r[1]+n[1]-i[1]*l[1][1]>=0},mcsOverflow:e.expr[":"].mcsOverflow||function(t){var o=e(t).data(a);if(o)return o.overflowed[0]||o.overflowed[1]}})})})}); \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.css b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.css deleted file mode 100644 index 8f013dafa3..0000000000 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.css +++ /dev/null @@ -1,1267 +0,0 @@ -/* -== malihu jquery custom scrollbar plugin == -Plugin URI: http://manos.malihu.gr/jquery-custom-content-scroller -*/ - - - -/* -CONTENTS: - 1. BASIC STYLE - Plugin's basic/essential CSS properties (normally, should not be edited). - 2. VERTICAL SCROLLBAR - Positioning and dimensions of vertical scrollbar. - 3. HORIZONTAL SCROLLBAR - Positioning and dimensions of horizontal scrollbar. - 4. VERTICAL AND HORIZONTAL SCROLLBARS - Positioning and dimensions of 2-axis scrollbars. - 5. TRANSITIONS - CSS3 transitions for hover events, auto-expanded and auto-hidden scrollbars. - 6. SCROLLBAR COLORS, OPACITY AND BACKGROUNDS - 6.1 THEMES - Scrollbar colors, opacity, dimensions, backgrounds etc. via ready-to-use themes. -*/ - - - -/* ------------------------------------------------------------------------------------------------------------------------- -1. BASIC STYLE ------------------------------------------------------------------------------------------------------------------------- -*/ - - .mCustomScrollbar{ -ms-touch-action: pinch-zoom; touch-action: pinch-zoom; /* direct pointer events to js */ } - .mCustomScrollbar.mCS_no_scrollbar, .mCustomScrollbar.mCS_touch_action{ -ms-touch-action: auto; touch-action: auto; } - - .mCustomScrollBox{ /* contains plugin's markup */ - position: relative; - overflow: hidden; - height: 100%; - max-width: 100%; - outline: none; - direction: ltr; - } - - .mCSB_container{ /* contains the original content */ - overflow: hidden; - width: auto; - height: auto; - } - - - -/* ------------------------------------------------------------------------------------------------------------------------- -2. VERTICAL SCROLLBAR -y-axis ------------------------------------------------------------------------------------------------------------------------- -*/ - - .mCSB_inside > .mCSB_container{ margin-right: 30px; } - - .mCSB_container.mCS_no_scrollbar_y.mCS_y_hidden{ margin-right: 0; } /* non-visible scrollbar */ - - .mCS-dir-rtl > .mCSB_inside > .mCSB_container{ /* RTL direction/left-side scrollbar */ - margin-right: 0; - margin-left: 30px; - } - - .mCS-dir-rtl > .mCSB_inside > .mCSB_container.mCS_no_scrollbar_y.mCS_y_hidden{ margin-left: 0; } /* RTL direction/left-side scrollbar */ - - .mCSB_scrollTools{ /* contains scrollbar markup (draggable element, dragger rail, buttons etc.) */ - position: absolute; - width: 16px; - height: auto; - left: auto; - top: 0; - right: 0; - bottom: 0; - } - - .mCSB_outside + .mCSB_scrollTools{ right: -26px; } /* scrollbar position: outside */ - - .mCS-dir-rtl > .mCSB_inside > .mCSB_scrollTools, - .mCS-dir-rtl > .mCSB_outside + .mCSB_scrollTools{ /* RTL direction/left-side scrollbar */ - right: auto; - left: 0; - } - - .mCS-dir-rtl > .mCSB_outside + .mCSB_scrollTools{ left: -26px; } /* RTL direction/left-side scrollbar (scrollbar position: outside) */ - - .mCSB_scrollTools .mCSB_draggerContainer{ /* contains the draggable element and dragger rail markup */ - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - height: auto; - } - - .mCSB_scrollTools a + .mCSB_draggerContainer{ margin: 20px 0; } - - .mCSB_scrollTools .mCSB_draggerRail{ - width: 2px; - height: 100%; - margin: 0 auto; - -webkit-border-radius: 16px; -moz-border-radius: 16px; border-radius: 16px; - } - - .mCSB_scrollTools .mCSB_dragger{ /* the draggable element */ - cursor: pointer; - width: 100%; - height: 30px; /* minimum dragger height */ - z-index: 1; - } - - .mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ /* the dragger element */ - position: relative; - width: 4px; - height: 100%; - margin: 0 auto; - -webkit-border-radius: 16px; -moz-border-radius: 16px; border-radius: 16px; - text-align: center; - } - - .mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar, - .mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar{ width: 12px; /* auto-expanded scrollbar */ } - - .mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{ width: 8px; /* auto-expanded scrollbar */ } - - .mCSB_scrollTools .mCSB_buttonUp, - .mCSB_scrollTools .mCSB_buttonDown{ - display: block; - position: absolute; - height: 20px; - width: 100%; - overflow: hidden; - margin: 0 auto; - cursor: pointer; - } - - .mCSB_scrollTools .mCSB_buttonDown{ bottom: 0; } - - - -/* ------------------------------------------------------------------------------------------------------------------------- -3. HORIZONTAL SCROLLBAR -x-axis ------------------------------------------------------------------------------------------------------------------------- -*/ - - .mCSB_horizontal.mCSB_inside > .mCSB_container{ - margin-right: 0; - margin-bottom: 30px; - } - - .mCSB_horizontal.mCSB_outside > .mCSB_container{ min-height: 100%; } - - .mCSB_horizontal > .mCSB_container.mCS_no_scrollbar_x.mCS_x_hidden{ margin-bottom: 0; } /* non-visible scrollbar */ - - .mCSB_scrollTools.mCSB_scrollTools_horizontal{ - width: auto; - height: 16px; - top: auto; - right: 0; - bottom: 0; - left: 0; - } - - .mCustomScrollBox + .mCSB_scrollTools.mCSB_scrollTools_horizontal, - .mCustomScrollBox + .mCSB_scrollTools + .mCSB_scrollTools.mCSB_scrollTools_horizontal{ bottom: -26px; } /* scrollbar position: outside */ - - .mCSB_scrollTools.mCSB_scrollTools_horizontal a + .mCSB_draggerContainer{ margin: 0 20px; } - - .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_draggerRail{ - width: 100%; - height: 2px; - margin: 7px 0; - } - - .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_dragger{ - width: 30px; /* minimum dragger width */ - height: 100%; - left: 0; - } - - .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - width: 100%; - height: 4px; - margin: 6px auto; - } - - .mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar, - .mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar{ - height: 12px; /* auto-expanded scrollbar */ - margin: 2px auto; - } - - .mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{ - height: 8px; /* auto-expanded scrollbar */ - margin: 4px 0; - } - - .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_buttonLeft, - .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_buttonRight{ - display: block; - position: absolute; - width: 20px; - height: 100%; - overflow: hidden; - margin: 0 auto; - cursor: pointer; - } - - .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_buttonLeft{ left: 0; } - - .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_buttonRight{ right: 0; } - - - -/* ------------------------------------------------------------------------------------------------------------------------- -4. VERTICAL AND HORIZONTAL SCROLLBARS -yx-axis ------------------------------------------------------------------------------------------------------------------------- -*/ - - .mCSB_container_wrapper{ - position: absolute; - height: auto; - width: auto; - overflow: hidden; - top: 0; - left: 0; - right: 0; - bottom: 0; - margin-right: 30px; - margin-bottom: 30px; - } - - .mCSB_container_wrapper > .mCSB_container{ - padding-right: 30px; - padding-bottom: 30px; - -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; - } - - .mCSB_vertical_horizontal > .mCSB_scrollTools.mCSB_scrollTools_vertical{ bottom: 20px; } - - .mCSB_vertical_horizontal > .mCSB_scrollTools.mCSB_scrollTools_horizontal{ right: 20px; } - - /* non-visible horizontal scrollbar */ - .mCSB_container_wrapper.mCS_no_scrollbar_x.mCS_x_hidden + .mCSB_scrollTools.mCSB_scrollTools_vertical{ bottom: 0; } - - /* non-visible vertical scrollbar/RTL direction/left-side scrollbar */ - .mCSB_container_wrapper.mCS_no_scrollbar_y.mCS_y_hidden + .mCSB_scrollTools ~ .mCSB_scrollTools.mCSB_scrollTools_horizontal, - .mCS-dir-rtl > .mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside > .mCSB_scrollTools.mCSB_scrollTools_horizontal{ right: 0; } - - /* RTL direction/left-side scrollbar */ - .mCS-dir-rtl > .mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside > .mCSB_scrollTools.mCSB_scrollTools_horizontal{ left: 20px; } - - /* non-visible scrollbar/RTL direction/left-side scrollbar */ - .mCS-dir-rtl > .mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside > .mCSB_container_wrapper.mCS_no_scrollbar_y.mCS_y_hidden + .mCSB_scrollTools ~ .mCSB_scrollTools.mCSB_scrollTools_horizontal{ left: 0; } - - .mCS-dir-rtl > .mCSB_inside > .mCSB_container_wrapper{ /* RTL direction/left-side scrollbar */ - margin-right: 0; - margin-left: 30px; - } - - .mCSB_container_wrapper.mCS_no_scrollbar_y.mCS_y_hidden > .mCSB_container{ padding-right: 0; } - - .mCSB_container_wrapper.mCS_no_scrollbar_x.mCS_x_hidden > .mCSB_container{ padding-bottom: 0; } - - .mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside > .mCSB_container_wrapper.mCS_no_scrollbar_y.mCS_y_hidden{ - margin-right: 0; /* non-visible scrollbar */ - margin-left: 0; - } - - /* non-visible horizontal scrollbar */ - .mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside > .mCSB_container_wrapper.mCS_no_scrollbar_x.mCS_x_hidden{ margin-bottom: 0; } - - - -/* ------------------------------------------------------------------------------------------------------------------------- -5. TRANSITIONS ------------------------------------------------------------------------------------------------------------------------- -*/ - - .mCSB_scrollTools, - .mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCSB_scrollTools .mCSB_buttonUp, - .mCSB_scrollTools .mCSB_buttonDown, - .mCSB_scrollTools .mCSB_buttonLeft, - .mCSB_scrollTools .mCSB_buttonRight{ - -webkit-transition: opacity .2s ease-in-out, background-color .2s ease-in-out; - -moz-transition: opacity .2s ease-in-out, background-color .2s ease-in-out; - -o-transition: opacity .2s ease-in-out, background-color .2s ease-in-out; - transition: opacity .2s ease-in-out, background-color .2s ease-in-out; - } - - .mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger_bar, /* auto-expanded scrollbar */ - .mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerRail, - .mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger_bar, - .mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerRail{ - -webkit-transition: width .2s ease-out .2s, height .2s ease-out .2s, - margin-left .2s ease-out .2s, margin-right .2s ease-out .2s, - margin-top .2s ease-out .2s, margin-bottom .2s ease-out .2s, - opacity .2s ease-in-out, background-color .2s ease-in-out; - -moz-transition: width .2s ease-out .2s, height .2s ease-out .2s, - margin-left .2s ease-out .2s, margin-right .2s ease-out .2s, - margin-top .2s ease-out .2s, margin-bottom .2s ease-out .2s, - opacity .2s ease-in-out, background-color .2s ease-in-out; - -o-transition: width .2s ease-out .2s, height .2s ease-out .2s, - margin-left .2s ease-out .2s, margin-right .2s ease-out .2s, - margin-top .2s ease-out .2s, margin-bottom .2s ease-out .2s, - opacity .2s ease-in-out, background-color .2s ease-in-out; - transition: width .2s ease-out .2s, height .2s ease-out .2s, - margin-left .2s ease-out .2s, margin-right .2s ease-out .2s, - margin-top .2s ease-out .2s, margin-bottom .2s ease-out .2s, - opacity .2s ease-in-out, background-color .2s ease-in-out; - } - - - -/* ------------------------------------------------------------------------------------------------------------------------- -6. SCROLLBAR COLORS, OPACITY AND BACKGROUNDS ------------------------------------------------------------------------------------------------------------------------- -*/ - - /* - ---------------------------------------- - 6.1 THEMES - ---------------------------------------- - */ - - /* default theme ("light") */ - - .mCSB_scrollTools{ opacity: 0.75; filter: "alpha(opacity=75)"; -ms-filter: "alpha(opacity=75)"; } - - .mCS-autoHide > .mCustomScrollBox > .mCSB_scrollTools, - .mCS-autoHide > .mCustomScrollBox ~ .mCSB_scrollTools{ opacity: 0; filter: "alpha(opacity=0)"; -ms-filter: "alpha(opacity=0)"; } - - .mCustomScrollbar > .mCustomScrollBox > .mCSB_scrollTools.mCSB_scrollTools_onDrag, - .mCustomScrollbar > .mCustomScrollBox ~ .mCSB_scrollTools.mCSB_scrollTools_onDrag, - .mCustomScrollBox:hover > .mCSB_scrollTools, - .mCustomScrollBox:hover ~ .mCSB_scrollTools, - .mCS-autoHide:hover > .mCustomScrollBox > .mCSB_scrollTools, - .mCS-autoHide:hover > .mCustomScrollBox ~ .mCSB_scrollTools{ opacity: 1; filter: "alpha(opacity=100)"; -ms-filter: "alpha(opacity=100)"; } - - .mCSB_scrollTools .mCSB_draggerRail{ - background-color: #000; background-color: rgba(0,0,0,0.4); - filter: "alpha(opacity=40)"; -ms-filter: "alpha(opacity=40)"; - } - - .mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - background-color: #fff; background-color: rgba(255,255,255,0.75); - filter: "alpha(opacity=75)"; -ms-filter: "alpha(opacity=75)"; - } - - .mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ - background-color: #fff; background-color: rgba(255,255,255,0.85); - filter: "alpha(opacity=85)"; -ms-filter: "alpha(opacity=85)"; - } - .mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ - background-color: #fff; background-color: rgba(255,255,255,0.9); - filter: "alpha(opacity=90)"; -ms-filter: "alpha(opacity=90)"; - } - - .mCSB_scrollTools .mCSB_buttonUp, - .mCSB_scrollTools .mCSB_buttonDown, - .mCSB_scrollTools .mCSB_buttonLeft, - .mCSB_scrollTools .mCSB_buttonRight{ - background-image: url(mCSB_buttons.png); /* css sprites */ - background-repeat: no-repeat; - opacity: 0.4; filter: "alpha(opacity=40)"; -ms-filter: "alpha(opacity=40)"; - } - - .mCSB_scrollTools .mCSB_buttonUp{ - background-position: 0 0; - /* - sprites locations - light: 0 0, -16px 0, -32px 0, -48px 0, 0 -72px, -16px -72px, -32px -72px - dark: -80px 0, -96px 0, -112px 0, -128px 0, -80px -72px, -96px -72px, -112px -72px - */ - } - - .mCSB_scrollTools .mCSB_buttonDown{ - background-position: 0 -20px; - /* - sprites locations - light: 0 -20px, -16px -20px, -32px -20px, -48px -20px, 0 -92px, -16px -92px, -32px -92px - dark: -80px -20px, -96px -20px, -112px -20px, -128px -20px, -80px -92px, -96px -92px, -112 -92px - */ - } - - .mCSB_scrollTools .mCSB_buttonLeft{ - background-position: 0 -40px; - /* - sprites locations - light: 0 -40px, -20px -40px, -40px -40px, -60px -40px, 0 -112px, -20px -112px, -40px -112px - dark: -80px -40px, -100px -40px, -120px -40px, -140px -40px, -80px -112px, -100px -112px, -120px -112px - */ - } - - .mCSB_scrollTools .mCSB_buttonRight{ - background-position: 0 -56px; - /* - sprites locations - light: 0 -56px, -20px -56px, -40px -56px, -60px -56px, 0 -128px, -20px -128px, -40px -128px - dark: -80px -56px, -100px -56px, -120px -56px, -140px -56px, -80px -128px, -100px -128px, -120px -128px - */ - } - - .mCSB_scrollTools .mCSB_buttonUp:hover, - .mCSB_scrollTools .mCSB_buttonDown:hover, - .mCSB_scrollTools .mCSB_buttonLeft:hover, - .mCSB_scrollTools .mCSB_buttonRight:hover{ opacity: 0.75; filter: "alpha(opacity=75)"; -ms-filter: "alpha(opacity=75)"; } - - .mCSB_scrollTools .mCSB_buttonUp:active, - .mCSB_scrollTools .mCSB_buttonDown:active, - .mCSB_scrollTools .mCSB_buttonLeft:active, - .mCSB_scrollTools .mCSB_buttonRight:active{ opacity: 0.9; filter: "alpha(opacity=90)"; -ms-filter: "alpha(opacity=90)"; } - - - /* theme: "dark" */ - - .mCS-dark.mCSB_scrollTools .mCSB_draggerRail{ background-color: #000; background-color: rgba(0,0,0,0.15); } - - .mCS-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.75); } - - .mCS-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: rgba(0,0,0,0.85); } - - .mCS-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: rgba(0,0,0,0.9); } - - .mCS-dark.mCSB_scrollTools .mCSB_buttonUp{ background-position: -80px 0; } - - .mCS-dark.mCSB_scrollTools .mCSB_buttonDown{ background-position: -80px -20px; } - - .mCS-dark.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -80px -40px; } - - .mCS-dark.mCSB_scrollTools .mCSB_buttonRight{ background-position: -80px -56px; } - - /* ---------------------------------------- */ - - - - /* theme: "light-2", "dark-2" */ - - .mCS-light-2.mCSB_scrollTools .mCSB_draggerRail, - .mCS-dark-2.mCSB_scrollTools .mCSB_draggerRail{ - width: 4px; - background-color: #fff; background-color: rgba(255,255,255,0.1); - -webkit-border-radius: 1px; -moz-border-radius: 1px; border-radius: 1px; - } - - .mCS-light-2.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-dark-2.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - width: 4px; - background-color: #fff; background-color: rgba(255,255,255,0.75); - -webkit-border-radius: 1px; -moz-border-radius: 1px; border-radius: 1px; - } - - .mCS-light-2.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-dark-2.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-light-2.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-dark-2.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - width: 100%; - height: 4px; - margin: 6px auto; - } - - .mCS-light-2.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #fff; background-color: rgba(255,255,255,0.85); } - - .mCS-light-2.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-light-2.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #fff; background-color: rgba(255,255,255,0.9); } - - .mCS-light-2.mCSB_scrollTools .mCSB_buttonUp{ background-position: -32px 0; } - - .mCS-light-2.mCSB_scrollTools .mCSB_buttonDown{ background-position: -32px -20px; } - - .mCS-light-2.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -40px -40px; } - - .mCS-light-2.mCSB_scrollTools .mCSB_buttonRight{ background-position: -40px -56px; } - - - /* theme: "dark-2" */ - - .mCS-dark-2.mCSB_scrollTools .mCSB_draggerRail{ - background-color: #000; background-color: rgba(0,0,0,0.1); - -webkit-border-radius: 1px; -moz-border-radius: 1px; border-radius: 1px; - } - - .mCS-dark-2.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - background-color: #000; background-color: rgba(0,0,0,0.75); - -webkit-border-radius: 1px; -moz-border-radius: 1px; border-radius: 1px; - } - - .mCS-dark-2.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.85); } - - .mCS-dark-2.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-dark-2.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.9); } - - .mCS-dark-2.mCSB_scrollTools .mCSB_buttonUp{ background-position: -112px 0; } - - .mCS-dark-2.mCSB_scrollTools .mCSB_buttonDown{ background-position: -112px -20px; } - - .mCS-dark-2.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -120px -40px; } - - .mCS-dark-2.mCSB_scrollTools .mCSB_buttonRight{ background-position: -120px -56px; } - - /* ---------------------------------------- */ - - - - /* theme: "light-thick", "dark-thick" */ - - .mCS-light-thick.mCSB_scrollTools .mCSB_draggerRail, - .mCS-dark-thick.mCSB_scrollTools .mCSB_draggerRail{ - width: 4px; - background-color: #fff; background-color: rgba(255,255,255,0.1); - -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; - } - - .mCS-light-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-dark-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - width: 6px; - background-color: #fff; background-color: rgba(255,255,255,0.75); - -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; - } - - .mCS-light-thick.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-dark-thick.mCSB_scrollTools_horizontal .mCSB_draggerRail{ - width: 100%; - height: 4px; - margin: 6px 0; - } - - .mCS-light-thick.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-dark-thick.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - width: 100%; - height: 6px; - margin: 5px auto; - } - - .mCS-light-thick.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #fff; background-color: rgba(255,255,255,0.85); } - - .mCS-light-thick.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-light-thick.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #fff; background-color: rgba(255,255,255,0.9); } - - .mCS-light-thick.mCSB_scrollTools .mCSB_buttonUp{ background-position: -16px 0; } - - .mCS-light-thick.mCSB_scrollTools .mCSB_buttonDown{ background-position: -16px -20px; } - - .mCS-light-thick.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -20px -40px; } - - .mCS-light-thick.mCSB_scrollTools .mCSB_buttonRight{ background-position: -20px -56px; } - - - /* theme: "dark-thick" */ - - .mCS-dark-thick.mCSB_scrollTools .mCSB_draggerRail{ - background-color: #000; background-color: rgba(0,0,0,0.1); - -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; - } - - .mCS-dark-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - background-color: #000; background-color: rgba(0,0,0,0.75); - -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; - } - - .mCS-dark-thick.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.85); } - - .mCS-dark-thick.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-dark-thick.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.9); } - - .mCS-dark-thick.mCSB_scrollTools .mCSB_buttonUp{ background-position: -96px 0; } - - .mCS-dark-thick.mCSB_scrollTools .mCSB_buttonDown{ background-position: -96px -20px; } - - .mCS-dark-thick.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -100px -40px; } - - .mCS-dark-thick.mCSB_scrollTools .mCSB_buttonRight{ background-position: -100px -56px; } - - /* ---------------------------------------- */ - - - - /* theme: "light-thin", "dark-thin" */ - - .mCS-light-thin.mCSB_scrollTools .mCSB_draggerRail{ background-color: #fff; background-color: rgba(255,255,255,0.1); } - - .mCS-light-thin.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-dark-thin.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ width: 2px; } - - .mCS-light-thin.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-dark-thin.mCSB_scrollTools_horizontal .mCSB_draggerRail{ width: 100%; } - - .mCS-light-thin.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-dark-thin.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - width: 100%; - height: 2px; - margin: 7px auto; - } - - - /* theme "dark-thin" */ - - .mCS-dark-thin.mCSB_scrollTools .mCSB_draggerRail{ background-color: #000; background-color: rgba(0,0,0,0.15); } - - .mCS-dark-thin.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.75); } - - .mCS-dark-thin.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.85); } - - .mCS-dark-thin.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-dark-thin.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.9); } - - .mCS-dark-thin.mCSB_scrollTools .mCSB_buttonUp{ background-position: -80px 0; } - - .mCS-dark-thin.mCSB_scrollTools .mCSB_buttonDown{ background-position: -80px -20px; } - - .mCS-dark-thin.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -80px -40px; } - - .mCS-dark-thin.mCSB_scrollTools .mCSB_buttonRight{ background-position: -80px -56px; } - - /* ---------------------------------------- */ - - - - /* theme "rounded", "rounded-dark", "rounded-dots", "rounded-dots-dark" */ - - .mCS-rounded.mCSB_scrollTools .mCSB_draggerRail{ background-color: #fff; background-color: rgba(255,255,255,0.15); } - - .mCS-rounded.mCSB_scrollTools .mCSB_dragger, - .mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger, - .mCS-rounded-dots.mCSB_scrollTools .mCSB_dragger, - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger{ height: 14px; } - - .mCS-rounded.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dots.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - width: 14px; - margin: 0 1px; - } - - .mCS-rounded.mCSB_scrollTools_horizontal .mCSB_dragger, - .mCS-rounded-dark.mCSB_scrollTools_horizontal .mCSB_dragger, - .mCS-rounded-dots.mCSB_scrollTools_horizontal .mCSB_dragger, - .mCS-rounded-dots-dark.mCSB_scrollTools_horizontal .mCSB_dragger{ width: 14px; } - - .mCS-rounded.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dots.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dots-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - height: 14px; - margin: 1px 0; - } - - .mCS-rounded.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar, - .mCS-rounded.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dark.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar, - .mCS-rounded-dark.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar{ - width: 16px; /* auto-expanded scrollbar */ - height: 16px; - margin: -1px 0; - } - - .mCS-rounded.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCS-rounded.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail, - .mCS-rounded-dark.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCS-rounded-dark.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{ width: 4px; /* auto-expanded scrollbar */ } - - .mCS-rounded.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar, - .mCS-rounded.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dark.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar, - .mCS-rounded-dark.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar{ - height: 16px; /* auto-expanded scrollbar */ - width: 16px; - margin: 0 -1px; - } - - .mCS-rounded.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCS-rounded.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail, - .mCS-rounded-dark.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCS-rounded-dark.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{ - height: 4px; /* auto-expanded scrollbar */ - margin: 6px 0; - } - - .mCS-rounded.mCSB_scrollTools .mCSB_buttonUp{ background-position: 0 -72px; } - - .mCS-rounded.mCSB_scrollTools .mCSB_buttonDown{ background-position: 0 -92px; } - - .mCS-rounded.mCSB_scrollTools .mCSB_buttonLeft{ background-position: 0 -112px; } - - .mCS-rounded.mCSB_scrollTools .mCSB_buttonRight{ background-position: 0 -128px; } - - - /* theme "rounded-dark", "rounded-dots-dark" */ - - .mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.75); } - - .mCS-rounded-dark.mCSB_scrollTools .mCSB_draggerRail{ background-color: #000; background-color: rgba(0,0,0,0.15); } - - .mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar, - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.85); } - - .mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar, - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.9); } - - .mCS-rounded-dark.mCSB_scrollTools .mCSB_buttonUp{ background-position: -80px -72px; } - - .mCS-rounded-dark.mCSB_scrollTools .mCSB_buttonDown{ background-position: -80px -92px; } - - .mCS-rounded-dark.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -80px -112px; } - - .mCS-rounded-dark.mCSB_scrollTools .mCSB_buttonRight{ background-position: -80px -128px; } - - - /* theme "rounded-dots", "rounded-dots-dark" */ - - .mCS-rounded-dots.mCSB_scrollTools_vertical .mCSB_draggerRail, - .mCS-rounded-dots-dark.mCSB_scrollTools_vertical .mCSB_draggerRail{ width: 4px; } - - .mCS-rounded-dots.mCSB_scrollTools .mCSB_draggerRail, - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_draggerRail, - .mCS-rounded-dots.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-rounded-dots-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail{ - background-color: transparent; - background-position: center; - } - - .mCS-rounded-dots.mCSB_scrollTools .mCSB_draggerRail, - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_draggerRail{ - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAANElEQVQYV2NkIAAYiVbw//9/Y6DiM1ANJoyMjGdBbLgJQAX/kU0DKgDLkaQAvxW4HEvQFwCRcxIJK1XznAAAAABJRU5ErkJggg=="); - background-repeat: repeat-y; - opacity: 0.3; - filter: "alpha(opacity=30)"; -ms-filter: "alpha(opacity=30)"; - } - - .mCS-rounded-dots.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-rounded-dots-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail{ - height: 4px; - margin: 6px 0; - background-repeat: repeat-x; - } - - .mCS-rounded-dots.mCSB_scrollTools .mCSB_buttonUp{ background-position: -16px -72px; } - - .mCS-rounded-dots.mCSB_scrollTools .mCSB_buttonDown{ background-position: -16px -92px; } - - .mCS-rounded-dots.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -20px -112px; } - - .mCS-rounded-dots.mCSB_scrollTools .mCSB_buttonRight{ background-position: -20px -128px; } - - - /* theme "rounded-dots-dark" */ - - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_draggerRail{ - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAALElEQVQYV2NkIAAYSVFgDFR8BqrBBEifBbGRTfiPZhpYjiQFBK3A6l6CvgAAE9kGCd1mvgEAAAAASUVORK5CYII="); - } - - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_buttonUp{ background-position: -96px -72px; } - - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_buttonDown{ background-position: -96px -92px; } - - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -100px -112px; } - - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_buttonRight{ background-position: -100px -128px; } - - /* ---------------------------------------- */ - - - - /* theme "3d", "3d-dark", "3d-thick", "3d-thick-dark" */ - - .mCS-3d.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - background-repeat: repeat-y; - background-image: -moz-linear-gradient(left, rgba(255,255,255,0.5) 0%, rgba(255,255,255,0) 100%); - background-image: -webkit-gradient(linear, left top, right top, color-stop(0%,rgba(255,255,255,0.5)), color-stop(100%,rgba(255,255,255,0))); - background-image: -webkit-linear-gradient(left, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 100%); - background-image: -o-linear-gradient(left, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 100%); - background-image: -ms-linear-gradient(left, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 100%); - background-image: linear-gradient(to right, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 100%); - } - - .mCS-3d.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - background-repeat: repeat-x; - background-image: -moz-linear-gradient(top, rgba(255,255,255,0.5) 0%, rgba(255,255,255,0) 100%); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,0.5)), color-stop(100%,rgba(255,255,255,0))); - background-image: -webkit-linear-gradient(top, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 100%); - background-image: -o-linear-gradient(top, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 100%); - background-image: -ms-linear-gradient(top, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 100%); - background-image: linear-gradient(to bottom, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 100%); - } - - - /* theme "3d", "3d-dark" */ - - .mCS-3d.mCSB_scrollTools_vertical .mCSB_dragger, - .mCS-3d-dark.mCSB_scrollTools_vertical .mCSB_dragger{ height: 70px; } - - .mCS-3d.mCSB_scrollTools_horizontal .mCSB_dragger, - .mCS-3d-dark.mCSB_scrollTools_horizontal .mCSB_dragger{ width: 70px; } - - .mCS-3d.mCSB_scrollTools, - .mCS-3d-dark.mCSB_scrollTools{ - opacity: 1; - filter: "alpha(opacity=30)"; -ms-filter: "alpha(opacity=30)"; - } - - .mCS-3d.mCSB_scrollTools .mCSB_draggerRail, - .mCS-3d.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools .mCSB_draggerRail, - .mCS-3d-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ -webkit-border-radius: 16px; -moz-border-radius: 16px; border-radius: 16px; } - - .mCS-3d.mCSB_scrollTools .mCSB_draggerRail, - .mCS-3d-dark.mCSB_scrollTools .mCSB_draggerRail{ - width: 8px; - background-color: #000; background-color: rgba(0,0,0,0.2); - box-shadow: inset 1px 0 1px rgba(0,0,0,0.5), inset -1px 0 1px rgba(255,255,255,0.2); - } - - .mCS-3d.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar, - .mCS-3d.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-3d.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #555; } - - .mCS-3d.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ width: 8px; } - - .mCS-3d.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-3d-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail{ - width: 100%; - height: 8px; - margin: 4px 0; - box-shadow: inset 0 1px 1px rgba(0,0,0,0.5), inset 0 -1px 1px rgba(255,255,255,0.2); - } - - .mCS-3d.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - width: 100%; - height: 8px; - margin: 4px auto; - } - - .mCS-3d.mCSB_scrollTools .mCSB_buttonUp{ background-position: -32px -72px; } - - .mCS-3d.mCSB_scrollTools .mCSB_buttonDown{ background-position: -32px -92px; } - - .mCS-3d.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -40px -112px; } - - .mCS-3d.mCSB_scrollTools .mCSB_buttonRight{ background-position: -40px -128px; } - - - /* theme "3d-dark" */ - - .mCS-3d-dark.mCSB_scrollTools .mCSB_draggerRail{ - background-color: #000; background-color: rgba(0,0,0,0.1); - box-shadow: inset 1px 0 1px rgba(0,0,0,0.1); - } - - .mCS-3d-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail{ box-shadow: inset 0 1px 1px rgba(0,0,0,0.1); } - - .mCS-3d-dark.mCSB_scrollTools .mCSB_buttonUp{ background-position: -112px -72px; } - - .mCS-3d-dark.mCSB_scrollTools .mCSB_buttonDown{ background-position: -112px -92px; } - - .mCS-3d-dark.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -120px -112px; } - - .mCS-3d-dark.mCSB_scrollTools .mCSB_buttonRight{ background-position: -120px -128px; } - - /* ---------------------------------------- */ - - - - /* theme: "3d-thick", "3d-thick-dark" */ - - .mCS-3d-thick.mCSB_scrollTools, - .mCS-3d-thick-dark.mCSB_scrollTools{ - opacity: 1; - filter: "alpha(opacity=30)"; -ms-filter: "alpha(opacity=30)"; - } - - .mCS-3d-thick.mCSB_scrollTools, - .mCS-3d-thick-dark.mCSB_scrollTools, - .mCS-3d-thick.mCSB_scrollTools .mCSB_draggerContainer, - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_draggerContainer{ -webkit-border-radius: 7px; -moz-border-radius: 7px; border-radius: 7px; } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; } - - .mCSB_inside + .mCS-3d-thick.mCSB_scrollTools_vertical, - .mCSB_inside + .mCS-3d-thick-dark.mCSB_scrollTools_vertical{ right: 1px; } - - .mCS-3d-thick.mCSB_scrollTools_vertical, - .mCS-3d-thick-dark.mCSB_scrollTools_vertical{ box-shadow: inset 1px 0 1px rgba(0,0,0,0.1), inset 0 0 14px rgba(0,0,0,0.5); } - - .mCS-3d-thick.mCSB_scrollTools_horizontal, - .mCS-3d-thick-dark.mCSB_scrollTools_horizontal{ - bottom: 1px; - box-shadow: inset 0 1px 1px rgba(0,0,0,0.1), inset 0 0 14px rgba(0,0,0,0.5); - } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - box-shadow: inset 1px 0 0 rgba(255,255,255,0.4); - width: 12px; - margin: 2px; - position: absolute; - height: auto; - top: 0; - bottom: 0; - left: 0; - right: 0; - } - - .mCS-3d-thick.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ box-shadow: inset 0 1px 0 rgba(255,255,255,0.4); } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar, - .mCS-3d-thick.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-3d-thick.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #555; } - - .mCS-3d-thick.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - height: 12px; - width: auto; - } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_draggerContainer{ - background-color: #000; background-color: rgba(0,0,0,0.05); - box-shadow: inset 1px 1px 16px rgba(0,0,0,0.1); - } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_draggerRail{ background-color: transparent; } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_buttonUp{ background-position: -32px -72px; } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_buttonDown{ background-position: -32px -92px; } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -40px -112px; } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_buttonRight{ background-position: -40px -128px; } - - - /* theme: "3d-thick-dark" */ - - .mCS-3d-thick-dark.mCSB_scrollTools{ box-shadow: inset 0 0 14px rgba(0,0,0,0.2); } - - .mCS-3d-thick-dark.mCSB_scrollTools_horizontal{ box-shadow: inset 0 1px 1px rgba(0,0,0,0.1), inset 0 0 14px rgba(0,0,0,0.2); } - - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ box-shadow: inset 1px 0 0 rgba(255,255,255,0.4), inset -1px 0 0 rgba(0,0,0,0.2); } - - .mCS-3d-thick-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ box-shadow: inset 0 1px 0 rgba(255,255,255,0.4), inset 0 -1px 0 rgba(0,0,0,0.2); } - - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #777; } - - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_draggerContainer{ - background-color: #fff; background-color: rgba(0,0,0,0.05); - box-shadow: inset 1px 1px 16px rgba(0,0,0,0.1); - } - - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_draggerRail{ background-color: transparent; } - - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_buttonUp{ background-position: -112px -72px; } - - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_buttonDown{ background-position: -112px -92px; } - - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -120px -112px; } - - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_buttonRight{ background-position: -120px -128px; } - - /* ---------------------------------------- */ - - - - /* theme: "minimal", "minimal-dark" */ - - .mCSB_outside + .mCS-minimal.mCSB_scrollTools_vertical, - .mCSB_outside + .mCS-minimal-dark.mCSB_scrollTools_vertical{ - right: 0; - margin: 12px 0; - } - - .mCustomScrollBox.mCS-minimal + .mCSB_scrollTools.mCSB_scrollTools_horizontal, - .mCustomScrollBox.mCS-minimal + .mCSB_scrollTools + .mCSB_scrollTools.mCSB_scrollTools_horizontal, - .mCustomScrollBox.mCS-minimal-dark + .mCSB_scrollTools.mCSB_scrollTools_horizontal, - .mCustomScrollBox.mCS-minimal-dark + .mCSB_scrollTools + .mCSB_scrollTools.mCSB_scrollTools_horizontal{ - bottom: 0; - margin: 0 12px; - } - - /* RTL direction/left-side scrollbar */ - .mCS-dir-rtl > .mCSB_outside + .mCS-minimal.mCSB_scrollTools_vertical, - .mCS-dir-rtl > .mCSB_outside + .mCS-minimal-dark.mCSB_scrollTools_vertical{ - left: 0; - right: auto; - } - - .mCS-minimal.mCSB_scrollTools .mCSB_draggerRail, - .mCS-minimal-dark.mCSB_scrollTools .mCSB_draggerRail{ background-color: transparent; } - - .mCS-minimal.mCSB_scrollTools_vertical .mCSB_dragger, - .mCS-minimal-dark.mCSB_scrollTools_vertical .mCSB_dragger{ height: 50px; } - - .mCS-minimal.mCSB_scrollTools_horizontal .mCSB_dragger, - .mCS-minimal-dark.mCSB_scrollTools_horizontal .mCSB_dragger{ width: 50px; } - - .mCS-minimal.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - background-color: #fff; background-color: rgba(255,255,255,0.2); - filter: "alpha(opacity=20)"; -ms-filter: "alpha(opacity=20)"; - } - - .mCS-minimal.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-minimal.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ - background-color: #fff; background-color: rgba(255,255,255,0.5); - filter: "alpha(opacity=50)"; -ms-filter: "alpha(opacity=50)"; - } - - - /* theme: "minimal-dark" */ - - .mCS-minimal-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - background-color: #000; background-color: rgba(0,0,0,0.2); - filter: "alpha(opacity=20)"; -ms-filter: "alpha(opacity=20)"; - } - - .mCS-minimal-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-minimal-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ - background-color: #000; background-color: rgba(0,0,0,0.5); - filter: "alpha(opacity=50)"; -ms-filter: "alpha(opacity=50)"; - } - - /* ---------------------------------------- */ - - - - /* theme "light-3", "dark-3" */ - - .mCS-light-3.mCSB_scrollTools .mCSB_draggerRail, - .mCS-dark-3.mCSB_scrollTools .mCSB_draggerRail{ - width: 6px; - background-color: #000; background-color: rgba(0,0,0,0.2); - } - - .mCS-light-3.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-dark-3.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ width: 6px; } - - .mCS-light-3.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-dark-3.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-light-3.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-dark-3.mCSB_scrollTools_horizontal .mCSB_draggerRail{ - width: 100%; - height: 6px; - margin: 5px 0; - } - - .mCS-light-3.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCS-light-3.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail, - .mCS-dark-3.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCS-dark-3.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{ - width: 12px; - } - - .mCS-light-3.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCS-light-3.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail, - .mCS-dark-3.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCS-dark-3.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{ - height: 12px; - margin: 2px 0; - } - - .mCS-light-3.mCSB_scrollTools .mCSB_buttonUp{ background-position: -32px -72px; } - - .mCS-light-3.mCSB_scrollTools .mCSB_buttonDown{ background-position: -32px -92px; } - - .mCS-light-3.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -40px -112px; } - - .mCS-light-3.mCSB_scrollTools .mCSB_buttonRight{ background-position: -40px -128px; } - - - /* theme "dark-3" */ - - .mCS-dark-3.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.75); } - - .mCS-dark-3.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.85); } - - .mCS-dark-3.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-dark-3.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.9); } - - .mCS-dark-3.mCSB_scrollTools .mCSB_draggerRail{ background-color: #000; background-color: rgba(0,0,0,0.1); } - - .mCS-dark-3.mCSB_scrollTools .mCSB_buttonUp{ background-position: -112px -72px; } - - .mCS-dark-3.mCSB_scrollTools .mCSB_buttonDown{ background-position: -112px -92px; } - - .mCS-dark-3.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -120px -112px; } - - .mCS-dark-3.mCSB_scrollTools .mCSB_buttonRight{ background-position: -120px -128px; } - - /* ---------------------------------------- */ - - - - /* theme "inset", "inset-dark", "inset-2", "inset-2-dark", "inset-3", "inset-3-dark" */ - - .mCS-inset.mCSB_scrollTools .mCSB_draggerRail, - .mCS-inset-dark.mCSB_scrollTools .mCSB_draggerRail, - .mCS-inset-2.mCSB_scrollTools .mCSB_draggerRail, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_draggerRail, - .mCS-inset-3.mCSB_scrollTools .mCSB_draggerRail, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_draggerRail{ - width: 12px; - background-color: #000; background-color: rgba(0,0,0,0.2); - } - - .mCS-inset.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-2.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-3.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - width: 6px; - margin: 3px 5px; - position: absolute; - height: auto; - top: 0; - bottom: 0; - left: 0; - right: 0; - } - - .mCS-inset.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-2.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-2-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-3.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-3-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - height: 6px; - margin: 5px 3px; - position: absolute; - width: auto; - top: 0; - bottom: 0; - left: 0; - right: 0; - } - - .mCS-inset.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-inset-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-inset-2.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-inset-2-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-inset-3.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-inset-3-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail{ - width: 100%; - height: 12px; - margin: 2px 0; - } - - .mCS-inset.mCSB_scrollTools .mCSB_buttonUp, - .mCS-inset-2.mCSB_scrollTools .mCSB_buttonUp, - .mCS-inset-3.mCSB_scrollTools .mCSB_buttonUp{ background-position: -32px -72px; } - - .mCS-inset.mCSB_scrollTools .mCSB_buttonDown, - .mCS-inset-2.mCSB_scrollTools .mCSB_buttonDown, - .mCS-inset-3.mCSB_scrollTools .mCSB_buttonDown{ background-position: -32px -92px; } - - .mCS-inset.mCSB_scrollTools .mCSB_buttonLeft, - .mCS-inset-2.mCSB_scrollTools .mCSB_buttonLeft, - .mCS-inset-3.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -40px -112px; } - - .mCS-inset.mCSB_scrollTools .mCSB_buttonRight, - .mCS-inset-2.mCSB_scrollTools .mCSB_buttonRight, - .mCS-inset-3.mCSB_scrollTools .mCSB_buttonRight{ background-position: -40px -128px; } - - - /* theme "inset-dark", "inset-2-dark", "inset-3-dark" */ - - .mCS-inset-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.75); } - - .mCS-inset-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.85); } - - .mCS-inset-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-inset-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.9); } - - .mCS-inset-dark.mCSB_scrollTools .mCSB_draggerRail, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_draggerRail, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_draggerRail{ background-color: #000; background-color: rgba(0,0,0,0.1); } - - .mCS-inset-dark.mCSB_scrollTools .mCSB_buttonUp, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_buttonUp, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_buttonUp{ background-position: -112px -72px; } - - .mCS-inset-dark.mCSB_scrollTools .mCSB_buttonDown, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_buttonDown, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_buttonDown{ background-position: -112px -92px; } - - .mCS-inset-dark.mCSB_scrollTools .mCSB_buttonLeft, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_buttonLeft, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -120px -112px; } - - .mCS-inset-dark.mCSB_scrollTools .mCSB_buttonRight, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_buttonRight, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_buttonRight{ background-position: -120px -128px; } - - - /* theme "inset-2", "inset-2-dark" */ - - .mCS-inset-2.mCSB_scrollTools .mCSB_draggerRail, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_draggerRail{ - background-color: transparent; - border-width: 1px; - border-style: solid; - border-color: #fff; - border-color: rgba(255,255,255,0.2); - -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; - } - - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_draggerRail{ border-color: #000; border-color: rgba(0,0,0,0.2); } - - - /* theme "inset-3", "inset-3-dark" */ - - .mCS-inset-3.mCSB_scrollTools .mCSB_draggerRail{ background-color: #fff; background-color: rgba(255,255,255,0.6); } - - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_draggerRail{ background-color: #000; background-color: rgba(0,0,0,0.6); } - - .mCS-inset-3.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.75); } - - .mCS-inset-3.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.85); } - - .mCS-inset-3.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-inset-3.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.9); } - - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ background-color: #fff; background-color: rgba(255,255,255,0.75); } - - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #fff; background-color: rgba(255,255,255,0.85); } - - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #fff; background-color: rgba(255,255,255,0.9); } - - /* ---------------------------------------- */ diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.js b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.js deleted file mode 100644 index ff7a7263f8..0000000000 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.js +++ /dev/null @@ -1,2458 +0,0 @@ -/* -== malihu jquery custom scrollbar plugin == -Version: 3.1.5 -Plugin URI: http://manos.malihu.gr/jquery-custom-content-scroller -Author: malihu -Author URI: http://manos.malihu.gr -License: MIT License (MIT) -*/ - -/* -Copyright Manos Malihutsakis (email: manos@malihu.gr) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -/* -The code below is fairly long, fully commented and should be normally used in development. -For production, use either the minified jquery.mCustomScrollbar.min.js script or -the production-ready jquery.mCustomScrollbar.concat.min.js which contains the plugin -and dependencies (minified). -*/ - -(function(factory){ - if(typeof define==="function" && define.amd){ - define(["jquery"],factory); - }else if(typeof module!=="undefined" && module.exports){ - module.exports=factory; - }else{ - factory(jQuery,window,document); - } -}(function($){ -(function(init){ - var _rjs=typeof define==="function" && define.amd, /* RequireJS */ - _njs=typeof module !== "undefined" && module.exports, /* NodeJS */ - _dlp=("https:"==document.location.protocol) ? "https:" : "http:", /* location protocol */ - _url="cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.13/jquery.mousewheel.min.js"; - if(!_rjs){ - if(_njs){ - require("jquery-mousewheel")($); - }else{ - /* load jquery-mousewheel plugin (via CDN) if it's not present or not loaded via RequireJS - (works when mCustomScrollbar fn is called on window load) */ - $.event.special.mousewheel || $("head").append(decodeURI("%3Cscript src="+_dlp+"//"+_url+"%3E%3C/script%3E")); - } - } - init(); -}(function(){ - - /* - ---------------------------------------- - PLUGIN NAMESPACE, PREFIX, DEFAULT SELECTOR(S) - ---------------------------------------- - */ - - var pluginNS="mCustomScrollbar", - pluginPfx="mCS", - defaultSelector=".mCustomScrollbar", - - - - - - /* - ---------------------------------------- - DEFAULT OPTIONS - ---------------------------------------- - */ - - defaults={ - /* - set element/content width/height programmatically - values: boolean, pixels, percentage - option default - ------------------------------------- - setWidth false - setHeight false - */ - /* - set the initial css top property of content - values: string (e.g. "-100px", "10%" etc.) - */ - setTop:0, - /* - set the initial css left property of content - values: string (e.g. "-100px", "10%" etc.) - */ - setLeft:0, - /* - scrollbar axis (vertical and/or horizontal scrollbars) - values (string): "y", "x", "yx" - */ - axis:"y", - /* - position of scrollbar relative to content - values (string): "inside", "outside" ("outside" requires elements with position:relative) - */ - scrollbarPosition:"inside", - /* - scrolling inertia - values: integer (milliseconds) - */ - scrollInertia:950, - /* - auto-adjust scrollbar dragger length - values: boolean - */ - autoDraggerLength:true, - /* - auto-hide scrollbar when idle - values: boolean - option default - ------------------------------------- - autoHideScrollbar false - */ - /* - auto-expands scrollbar on mouse-over and dragging - values: boolean - option default - ------------------------------------- - autoExpandScrollbar false - */ - /* - always show scrollbar, even when there's nothing to scroll - values: integer (0=disable, 1=always show dragger rail and buttons, 2=always show dragger rail, dragger and buttons), boolean - */ - alwaysShowScrollbar:0, - /* - scrolling always snaps to a multiple of this number in pixels - values: integer, array ([y,x]) - option default - ------------------------------------- - snapAmount null - */ - /* - when snapping, snap with this number in pixels as an offset - values: integer - */ - snapOffset:0, - /* - mouse-wheel scrolling - */ - mouseWheel:{ - /* - enable mouse-wheel scrolling - values: boolean - */ - enable:true, - /* - scrolling amount in pixels - values: "auto", integer - */ - scrollAmount:"auto", - /* - mouse-wheel scrolling axis - the default scrolling direction when both vertical and horizontal scrollbars are present - values (string): "y", "x" - */ - axis:"y", - /* - prevent the default behaviour which automatically scrolls the parent element(s) when end of scrolling is reached - values: boolean - option default - ------------------------------------- - preventDefault null - */ - /* - the reported mouse-wheel delta value. The number of lines (translated to pixels) one wheel notch scrolls. - values: "auto", integer - "auto" uses the default OS/browser value - */ - deltaFactor:"auto", - /* - normalize mouse-wheel delta to -1 or 1 (disables mouse-wheel acceleration) - values: boolean - option default - ------------------------------------- - normalizeDelta null - */ - /* - invert mouse-wheel scrolling direction - values: boolean - option default - ------------------------------------- - invert null - */ - /* - the tags that disable mouse-wheel when cursor is over them - */ - disableOver:["select","option","keygen","datalist","textarea"] - }, - /* - scrollbar buttons - */ - scrollButtons:{ - /* - enable scrollbar buttons - values: boolean - option default - ------------------------------------- - enable null - */ - /* - scrollbar buttons scrolling type - values (string): "stepless", "stepped" - */ - scrollType:"stepless", - /* - scrolling amount in pixels - values: "auto", integer - */ - scrollAmount:"auto" - /* - tabindex of the scrollbar buttons - values: false, integer - option default - ------------------------------------- - tabindex null - */ - }, - /* - keyboard scrolling - */ - keyboard:{ - /* - enable scrolling via keyboard - values: boolean - */ - enable:true, - /* - keyboard scrolling type - values (string): "stepless", "stepped" - */ - scrollType:"stepless", - /* - scrolling amount in pixels - values: "auto", integer - */ - scrollAmount:"auto" - }, - /* - enable content touch-swipe scrolling - values: boolean, integer, string (number) - integer values define the axis-specific minimum amount required for scrolling momentum - */ - contentTouchScroll:25, - /* - enable/disable document (default) touch-swipe scrolling - */ - documentTouchScroll:true, - /* - advanced option parameters - */ - advanced:{ - /* - auto-expand content horizontally (for "x" or "yx" axis) - values: boolean, integer (the value 2 forces the non scrollHeight/scrollWidth method, the value 3 forces the scrollHeight/scrollWidth method) - option default - ------------------------------------- - autoExpandHorizontalScroll null - */ - /* - auto-scroll to elements with focus - */ - autoScrollOnFocus:"input,textarea,select,button,datalist,keygen,a[tabindex],area,object,[contenteditable='true']", - /* - auto-update scrollbars on content, element or viewport resize - should be true for fluid layouts/elements, adding/removing content dynamically, hiding/showing elements, content with images etc. - values: boolean - */ - updateOnContentResize:true, - /* - auto-update scrollbars each time each image inside the element is fully loaded - values: "auto", boolean - */ - updateOnImageLoad:"auto", - /* - auto-update scrollbars based on the amount and size changes of specific selectors - useful when you need to update the scrollbar(s) automatically, each time a type of element is added, removed or changes its size - values: boolean, string (e.g. "ul li" will auto-update scrollbars each time list-items inside the element are changed) - a value of true (boolean) will auto-update scrollbars each time any element is changed - option default - ------------------------------------- - updateOnSelectorChange null - */ - /* - extra selectors that'll allow scrollbar dragging upon mousemove/up, pointermove/up, touchend etc. (e.g. "selector-1, selector-2") - option default - ------------------------------------- - extraDraggableSelectors null - */ - /* - extra selectors that'll release scrollbar dragging upon mouseup, pointerup, touchend etc. (e.g. "selector-1, selector-2") - option default - ------------------------------------- - releaseDraggableSelectors null - */ - /* - auto-update timeout - values: integer (milliseconds) - */ - autoUpdateTimeout:60 - }, - /* - scrollbar theme - values: string (see CSS/plugin URI for a list of ready-to-use themes) - */ - theme:"light", - /* - user defined callback functions - */ - callbacks:{ - /* - Available callbacks: - callback default - ------------------------------------- - onCreate null - onInit null - onScrollStart null - onScroll null - onTotalScroll null - onTotalScrollBack null - whileScrolling null - onOverflowY null - onOverflowX null - onOverflowYNone null - onOverflowXNone null - onImageLoad null - onSelectorChange null - onBeforeUpdate null - onUpdate null - */ - onTotalScrollOffset:0, - onTotalScrollBackOffset:0, - alwaysTriggerOffsets:true - } - /* - add scrollbar(s) on all elements matching the current selector, now and in the future - values: boolean, string - string values: "on" (enable), "once" (disable after first invocation), "off" (disable) - liveSelector values: string (selector) - option default - ------------------------------------- - live false - liveSelector null - */ - }, - - - - - - /* - ---------------------------------------- - VARS, CONSTANTS - ---------------------------------------- - */ - - totalInstances=0, /* plugin instances amount */ - liveTimers={}, /* live option timers */ - oldIE=(window.attachEvent && !window.addEventListener) ? 1 : 0, /* detect IE < 9 */ - touchActive=false,touchable, /* global touch vars (for touch and pointer events) */ - /* general plugin classes */ - classes=[ - "mCSB_dragger_onDrag","mCSB_scrollTools_onDrag","mCS_img_loaded","mCS_disabled","mCS_destroyed","mCS_no_scrollbar", - "mCS-autoHide","mCS-dir-rtl","mCS_no_scrollbar_y","mCS_no_scrollbar_x","mCS_y_hidden","mCS_x_hidden","mCSB_draggerContainer", - "mCSB_buttonUp","mCSB_buttonDown","mCSB_buttonLeft","mCSB_buttonRight" - ], - - - - - - /* - ---------------------------------------- - METHODS - ---------------------------------------- - */ - - methods={ - - /* - plugin initialization method - creates the scrollbar(s), plugin data object and options - ---------------------------------------- - */ - - init:function(options){ - - var options=$.extend(true,{},defaults,options), - selector=_selector.call(this); /* validate selector */ - - /* - if live option is enabled, monitor for elements matching the current selector and - apply scrollbar(s) when found (now and in the future) - */ - if(options.live){ - var liveSelector=options.liveSelector || this.selector || defaultSelector, /* live selector(s) */ - $liveSelector=$(liveSelector); /* live selector(s) as jquery object */ - if(options.live==="off"){ - /* - disable live if requested - usage: $(selector).mCustomScrollbar({live:"off"}); - */ - removeLiveTimers(liveSelector); - return; - } - liveTimers[liveSelector]=setTimeout(function(){ - /* call mCustomScrollbar fn on live selector(s) every half-second */ - $liveSelector.mCustomScrollbar(options); - if(options.live==="once" && $liveSelector.length){ - /* disable live after first invocation */ - removeLiveTimers(liveSelector); - } - },500); - }else{ - removeLiveTimers(liveSelector); - } - - /* options backward compatibility (for versions < 3.0.0) and normalization */ - options.setWidth=(options.set_width) ? options.set_width : options.setWidth; - options.setHeight=(options.set_height) ? options.set_height : options.setHeight; - options.axis=(options.horizontalScroll) ? "x" : _findAxis(options.axis); - options.scrollInertia=options.scrollInertia>0 && options.scrollInertia<17 ? 17 : options.scrollInertia; - if(typeof options.mouseWheel!=="object" && options.mouseWheel==true){ /* old school mouseWheel option (non-object) */ - options.mouseWheel={enable:true,scrollAmount:"auto",axis:"y",preventDefault:false,deltaFactor:"auto",normalizeDelta:false,invert:false} - } - options.mouseWheel.scrollAmount=!options.mouseWheelPixels ? options.mouseWheel.scrollAmount : options.mouseWheelPixels; - options.mouseWheel.normalizeDelta=!options.advanced.normalizeMouseWheelDelta ? options.mouseWheel.normalizeDelta : options.advanced.normalizeMouseWheelDelta; - options.scrollButtons.scrollType=_findScrollButtonsType(options.scrollButtons.scrollType); - - _theme(options); /* theme-specific options */ - - /* plugin constructor */ - return $(selector).each(function(){ - - var $this=$(this); - - if(!$this.data(pluginPfx)){ /* prevent multiple instantiations */ - - /* store options and create objects in jquery data */ - $this.data(pluginPfx,{ - idx:++totalInstances, /* instance index */ - opt:options, /* options */ - scrollRatio:{y:null,x:null}, /* scrollbar to content ratio */ - overflowed:null, /* overflowed axis */ - contentReset:{y:null,x:null}, /* object to check when content resets */ - bindEvents:false, /* object to check if events are bound */ - tweenRunning:false, /* object to check if tween is running */ - sequential:{}, /* sequential scrolling object */ - langDir:$this.css("direction"), /* detect/store direction (ltr or rtl) */ - cbOffsets:null, /* object to check whether callback offsets always trigger */ - /* - object to check how scrolling events where last triggered - "internal" (default - triggered by this script), "external" (triggered by other scripts, e.g. via scrollTo method) - usage: object.data("mCS").trigger - */ - trigger:null, - /* - object to check for changes in elements in order to call the update method automatically - */ - poll:{size:{o:0,n:0},img:{o:0,n:0},change:{o:0,n:0}} - }); - - var d=$this.data(pluginPfx),o=d.opt, - /* HTML data attributes */ - htmlDataAxis=$this.data("mcs-axis"),htmlDataSbPos=$this.data("mcs-scrollbar-position"),htmlDataTheme=$this.data("mcs-theme"); - - if(htmlDataAxis){o.axis=htmlDataAxis;} /* usage example: data-mcs-axis="y" */ - if(htmlDataSbPos){o.scrollbarPosition=htmlDataSbPos;} /* usage example: data-mcs-scrollbar-position="outside" */ - if(htmlDataTheme){ /* usage example: data-mcs-theme="minimal" */ - o.theme=htmlDataTheme; - _theme(o); /* theme-specific options */ - } - - _pluginMarkup.call(this); /* add plugin markup */ - - if(d && o.callbacks.onCreate && typeof o.callbacks.onCreate==="function"){o.callbacks.onCreate.call(this);} /* callbacks: onCreate */ - - $("#mCSB_"+d.idx+"_container img:not(."+classes[2]+")").addClass(classes[2]); /* flag loaded images */ - - methods.update.call(null,$this); /* call the update method */ - - } - - }); - - }, - /* ---------------------------------------- */ - - - - /* - plugin update method - updates content and scrollbar(s) values, events and status - ---------------------------------------- - usage: $(selector).mCustomScrollbar("update"); - */ - - update:function(el,cb){ - - var selector=el || _selector.call(this); /* validate selector */ - - return $(selector).each(function(){ - - var $this=$(this); - - if($this.data(pluginPfx)){ /* check if plugin has initialized */ - - var d=$this.data(pluginPfx),o=d.opt, - mCSB_container=$("#mCSB_"+d.idx+"_container"), - mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_dragger=[$("#mCSB_"+d.idx+"_dragger_vertical"),$("#mCSB_"+d.idx+"_dragger_horizontal")]; - - if(!mCSB_container.length){return;} - - if(d.tweenRunning){_stop($this);} /* stop any running tweens while updating */ - - if(cb && d && o.callbacks.onBeforeUpdate && typeof o.callbacks.onBeforeUpdate==="function"){o.callbacks.onBeforeUpdate.call(this);} /* callbacks: onBeforeUpdate */ - - /* if element was disabled or destroyed, remove class(es) */ - if($this.hasClass(classes[3])){$this.removeClass(classes[3]);} - if($this.hasClass(classes[4])){$this.removeClass(classes[4]);} - - /* css flexbox fix, detect/set max-height */ - mCustomScrollBox.css("max-height","none"); - if(mCustomScrollBox.height()!==$this.height()){mCustomScrollBox.css("max-height",$this.height());} - - _expandContentHorizontally.call(this); /* expand content horizontally */ - - if(o.axis!=="y" && !o.advanced.autoExpandHorizontalScroll){ - mCSB_container.css("width",_contentWidth(mCSB_container)); - } - - d.overflowed=_overflowed.call(this); /* determine if scrolling is required */ - - _scrollbarVisibility.call(this); /* show/hide scrollbar(s) */ - - /* auto-adjust scrollbar dragger length analogous to content */ - if(o.autoDraggerLength){_setDraggerLength.call(this);} - - _scrollRatio.call(this); /* calculate and store scrollbar to content ratio */ - - _bindEvents.call(this); /* bind scrollbar events */ - - /* reset scrolling position and/or events */ - var to=[Math.abs(mCSB_container[0].offsetTop),Math.abs(mCSB_container[0].offsetLeft)]; - if(o.axis!=="x"){ /* y/yx axis */ - if(!d.overflowed[0]){ /* y scrolling is not required */ - _resetContentPosition.call(this); /* reset content position */ - if(o.axis==="y"){ - _unbindEvents.call(this); - }else if(o.axis==="yx" && d.overflowed[1]){ - _scrollTo($this,to[1].toString(),{dir:"x",dur:0,overwrite:"none"}); - } - }else if(mCSB_dragger[0].height()>mCSB_dragger[0].parent().height()){ - _resetContentPosition.call(this); /* reset content position */ - }else{ /* y scrolling is required */ - _scrollTo($this,to[0].toString(),{dir:"y",dur:0,overwrite:"none"}); - d.contentReset.y=null; - } - } - if(o.axis!=="y"){ /* x/yx axis */ - if(!d.overflowed[1]){ /* x scrolling is not required */ - _resetContentPosition.call(this); /* reset content position */ - if(o.axis==="x"){ - _unbindEvents.call(this); - }else if(o.axis==="yx" && d.overflowed[0]){ - _scrollTo($this,to[0].toString(),{dir:"y",dur:0,overwrite:"none"}); - } - }else if(mCSB_dragger[1].width()>mCSB_dragger[1].parent().width()){ - _resetContentPosition.call(this); /* reset content position */ - }else{ /* x scrolling is required */ - _scrollTo($this,to[1].toString(),{dir:"x",dur:0,overwrite:"none"}); - d.contentReset.x=null; - } - } - - /* callbacks: onImageLoad, onSelectorChange, onUpdate */ - if(cb && d){ - if(cb===2 && o.callbacks.onImageLoad && typeof o.callbacks.onImageLoad==="function"){ - o.callbacks.onImageLoad.call(this); - }else if(cb===3 && o.callbacks.onSelectorChange && typeof o.callbacks.onSelectorChange==="function"){ - o.callbacks.onSelectorChange.call(this); - }else if(o.callbacks.onUpdate && typeof o.callbacks.onUpdate==="function"){ - o.callbacks.onUpdate.call(this); - } - } - - _autoUpdate.call(this); /* initialize automatic updating (for dynamic content, fluid layouts etc.) */ - - } - - }); - - }, - /* ---------------------------------------- */ - - - - /* - plugin scrollTo method - triggers a scrolling event to a specific value - ---------------------------------------- - usage: $(selector).mCustomScrollbar("scrollTo",value,options); - */ - - scrollTo:function(val,options){ - - /* prevent silly things like $(selector).mCustomScrollbar("scrollTo",undefined); */ - if(typeof val=="undefined" || val==null){return;} - - var selector=_selector.call(this); /* validate selector */ - - return $(selector).each(function(){ - - var $this=$(this); - - if($this.data(pluginPfx)){ /* check if plugin has initialized */ - - var d=$this.data(pluginPfx),o=d.opt, - /* method default options */ - methodDefaults={ - trigger:"external", /* method is by default triggered externally (e.g. from other scripts) */ - scrollInertia:o.scrollInertia, /* scrolling inertia (animation duration) */ - scrollEasing:"mcsEaseInOut", /* animation easing */ - moveDragger:false, /* move dragger instead of content */ - timeout:60, /* scroll-to delay */ - callbacks:true, /* enable/disable callbacks */ - onStart:true, - onUpdate:true, - onComplete:true - }, - methodOptions=$.extend(true,{},methodDefaults,options), - to=_arr.call(this,val),dur=methodOptions.scrollInertia>0 && methodOptions.scrollInertia<17 ? 17 : methodOptions.scrollInertia; - - /* translate yx values to actual scroll-to positions */ - to[0]=_to.call(this,to[0],"y"); - to[1]=_to.call(this,to[1],"x"); - - /* - check if scroll-to value moves the dragger instead of content. - Only pixel values apply on dragger (e.g. 100, "100px", "-=100" etc.) - */ - if(methodOptions.moveDragger){ - to[0]*=d.scrollRatio.y; - to[1]*=d.scrollRatio.x; - } - - methodOptions.dur=_isTabHidden() ? 0 : dur; //skip animations if browser tab is hidden - - setTimeout(function(){ - /* do the scrolling */ - if(to[0]!==null && typeof to[0]!=="undefined" && o.axis!=="x" && d.overflowed[0]){ /* scroll y */ - methodOptions.dir="y"; - methodOptions.overwrite="all"; - _scrollTo($this,to[0].toString(),methodOptions); - } - if(to[1]!==null && typeof to[1]!=="undefined" && o.axis!=="y" && d.overflowed[1]){ /* scroll x */ - methodOptions.dir="x"; - methodOptions.overwrite="none"; - _scrollTo($this,to[1].toString(),methodOptions); - } - },methodOptions.timeout); - - } - - }); - - }, - /* ---------------------------------------- */ - - - - /* - plugin stop method - stops scrolling animation - ---------------------------------------- - usage: $(selector).mCustomScrollbar("stop"); - */ - stop:function(){ - - var selector=_selector.call(this); /* validate selector */ - - return $(selector).each(function(){ - - var $this=$(this); - - if($this.data(pluginPfx)){ /* check if plugin has initialized */ - - _stop($this); - - } - - }); - - }, - /* ---------------------------------------- */ - - - - /* - plugin disable method - temporarily disables the scrollbar(s) - ---------------------------------------- - usage: $(selector).mCustomScrollbar("disable",reset); - reset (boolean): resets content position to 0 - */ - disable:function(r){ - - var selector=_selector.call(this); /* validate selector */ - - return $(selector).each(function(){ - - var $this=$(this); - - if($this.data(pluginPfx)){ /* check if plugin has initialized */ - - var d=$this.data(pluginPfx); - - _autoUpdate.call(this,"remove"); /* remove automatic updating */ - - _unbindEvents.call(this); /* unbind events */ - - if(r){_resetContentPosition.call(this);} /* reset content position */ - - _scrollbarVisibility.call(this,true); /* show/hide scrollbar(s) */ - - $this.addClass(classes[3]); /* add disable class */ - - } - - }); - - }, - /* ---------------------------------------- */ - - - - /* - plugin destroy method - completely removes the scrollbar(s) and returns the element to its original state - ---------------------------------------- - usage: $(selector).mCustomScrollbar("destroy"); - */ - destroy:function(){ - - var selector=_selector.call(this); /* validate selector */ - - return $(selector).each(function(){ - - var $this=$(this); - - if($this.data(pluginPfx)){ /* check if plugin has initialized */ - - var d=$this.data(pluginPfx),o=d.opt, - mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_container=$("#mCSB_"+d.idx+"_container"), - scrollbar=$(".mCSB_"+d.idx+"_scrollbar"); - - if(o.live){removeLiveTimers(o.liveSelector || $(selector).selector);} /* remove live timers */ - - _autoUpdate.call(this,"remove"); /* remove automatic updating */ - - _unbindEvents.call(this); /* unbind events */ - - _resetContentPosition.call(this); /* reset content position */ - - $this.removeData(pluginPfx); /* remove plugin data object */ - - _delete(this,"mcs"); /* delete callbacks object */ - - /* remove plugin markup */ - scrollbar.remove(); /* remove scrollbar(s) first (those can be either inside or outside plugin's inner wrapper) */ - mCSB_container.find("img."+classes[2]).removeClass(classes[2]); /* remove loaded images flag */ - mCustomScrollBox.replaceWith(mCSB_container.contents()); /* replace plugin's inner wrapper with the original content */ - /* remove plugin classes from the element and add destroy class */ - $this.removeClass(pluginNS+" _"+pluginPfx+"_"+d.idx+" "+classes[6]+" "+classes[7]+" "+classes[5]+" "+classes[3]).addClass(classes[4]); - - } - - }); - - } - /* ---------------------------------------- */ - - }, - - - - - - /* - ---------------------------------------- - FUNCTIONS - ---------------------------------------- - */ - - /* validates selector (if selector is invalid or undefined uses the default one) */ - _selector=function(){ - return (typeof $(this)!=="object" || $(this).length<1) ? defaultSelector : this; - }, - /* -------------------- */ - - - /* changes options according to theme */ - _theme=function(obj){ - var fixedSizeScrollbarThemes=["rounded","rounded-dark","rounded-dots","rounded-dots-dark"], - nonExpandedScrollbarThemes=["rounded-dots","rounded-dots-dark","3d","3d-dark","3d-thick","3d-thick-dark","inset","inset-dark","inset-2","inset-2-dark","inset-3","inset-3-dark"], - disabledScrollButtonsThemes=["minimal","minimal-dark"], - enabledAutoHideScrollbarThemes=["minimal","minimal-dark"], - scrollbarPositionOutsideThemes=["minimal","minimal-dark"]; - obj.autoDraggerLength=$.inArray(obj.theme,fixedSizeScrollbarThemes) > -1 ? false : obj.autoDraggerLength; - obj.autoExpandScrollbar=$.inArray(obj.theme,nonExpandedScrollbarThemes) > -1 ? false : obj.autoExpandScrollbar; - obj.scrollButtons.enable=$.inArray(obj.theme,disabledScrollButtonsThemes) > -1 ? false : obj.scrollButtons.enable; - obj.autoHideScrollbar=$.inArray(obj.theme,enabledAutoHideScrollbarThemes) > -1 ? true : obj.autoHideScrollbar; - obj.scrollbarPosition=$.inArray(obj.theme,scrollbarPositionOutsideThemes) > -1 ? "outside" : obj.scrollbarPosition; - }, - /* -------------------- */ - - - /* live option timers removal */ - removeLiveTimers=function(selector){ - if(liveTimers[selector]){ - clearTimeout(liveTimers[selector]); - _delete(liveTimers,selector); - } - }, - /* -------------------- */ - - - /* normalizes axis option to valid values: "y", "x", "yx" */ - _findAxis=function(val){ - return (val==="yx" || val==="xy" || val==="auto") ? "yx" : (val==="x" || val==="horizontal") ? "x" : "y"; - }, - /* -------------------- */ - - - /* normalizes scrollButtons.scrollType option to valid values: "stepless", "stepped" */ - _findScrollButtonsType=function(val){ - return (val==="stepped" || val==="pixels" || val==="step" || val==="click") ? "stepped" : "stepless"; - }, - /* -------------------- */ - - - /* generates plugin markup */ - _pluginMarkup=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - expandClass=o.autoExpandScrollbar ? " "+classes[1]+"_expand" : "", - scrollbar=["
","
"], - wrapperClass=o.axis==="yx" ? "mCSB_vertical_horizontal" : o.axis==="x" ? "mCSB_horizontal" : "mCSB_vertical", - scrollbars=o.axis==="yx" ? scrollbar[0]+scrollbar[1] : o.axis==="x" ? scrollbar[1] : scrollbar[0], - contentWrapper=o.axis==="yx" ? "
" : "", - autoHideClass=o.autoHideScrollbar ? " "+classes[6] : "", - scrollbarDirClass=(o.axis!=="x" && d.langDir==="rtl") ? " "+classes[7] : ""; - if(o.setWidth){$this.css("width",o.setWidth);} /* set element width */ - if(o.setHeight){$this.css("height",o.setHeight);} /* set element height */ - o.setLeft=(o.axis!=="y" && d.langDir==="rtl") ? "989999px" : o.setLeft; /* adjust left position for rtl direction */ - $this.addClass(pluginNS+" _"+pluginPfx+"_"+d.idx+autoHideClass+scrollbarDirClass).wrapInner("
"); - var mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_container=$("#mCSB_"+d.idx+"_container"); - if(o.axis!=="y" && !o.advanced.autoExpandHorizontalScroll){ - mCSB_container.css("width",_contentWidth(mCSB_container)); - } - if(o.scrollbarPosition==="outside"){ - if($this.css("position")==="static"){ /* requires elements with non-static position */ - $this.css("position","relative"); - } - $this.css("overflow","visible"); - mCustomScrollBox.addClass("mCSB_outside").after(scrollbars); - }else{ - mCustomScrollBox.addClass("mCSB_inside").append(scrollbars); - mCSB_container.wrap(contentWrapper); - } - _scrollButtons.call(this); /* add scrollbar buttons */ - /* minimum dragger length */ - var mCSB_dragger=[$("#mCSB_"+d.idx+"_dragger_vertical"),$("#mCSB_"+d.idx+"_dragger_horizontal")]; - mCSB_dragger[0].css("min-height",mCSB_dragger[0].height()); - mCSB_dragger[1].css("min-width",mCSB_dragger[1].width()); - }, - /* -------------------- */ - - - /* calculates content width */ - _contentWidth=function(el){ - var val=[el[0].scrollWidth,Math.max.apply(Math,el.children().map(function(){return $(this).outerWidth(true);}).get())],w=el.parent().width(); - return val[0]>w ? val[0] : val[1]>w ? val[1] : "100%"; - }, - /* -------------------- */ - - - /* expands content horizontally */ - _expandContentHorizontally=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - mCSB_container=$("#mCSB_"+d.idx+"_container"); - if(o.advanced.autoExpandHorizontalScroll && o.axis!=="y"){ - /* calculate scrollWidth */ - mCSB_container.css({"width":"auto","min-width":0,"overflow-x":"scroll"}); - var w=Math.ceil(mCSB_container[0].scrollWidth); - if(o.advanced.autoExpandHorizontalScroll===3 || (o.advanced.autoExpandHorizontalScroll!==2 && w>mCSB_container.parent().width())){ - mCSB_container.css({"width":w,"min-width":"100%","overflow-x":"inherit"}); - }else{ - /* - wrap content with an infinite width div and set its position to absolute and width to auto. - Setting width to auto before calculating the actual width is important! - We must let the browser set the width as browser zoom values are impossible to calculate. - */ - mCSB_container.css({"overflow-x":"inherit","position":"absolute"}) - .wrap("
") - .css({ /* set actual width, original position and un-wrap */ - /* - get the exact width (with decimals) and then round-up. - Using jquery outerWidth() will round the width value which will mess up with inner elements that have non-integer width - */ - "width":(Math.ceil(mCSB_container[0].getBoundingClientRect().right+0.4)-Math.floor(mCSB_container[0].getBoundingClientRect().left)), - "min-width":"100%", - "position":"relative" - }).unwrap(); - } - } - }, - /* -------------------- */ - - - /* adds scrollbar buttons */ - _scrollButtons=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - mCSB_scrollTools=$(".mCSB_"+d.idx+"_scrollbar:first"), - tabindex=!_isNumeric(o.scrollButtons.tabindex) ? "" : "tabindex='"+o.scrollButtons.tabindex+"'", - btnHTML=[ - "", - "", - "", - "" - ], - btn=[(o.axis==="x" ? btnHTML[2] : btnHTML[0]),(o.axis==="x" ? btnHTML[3] : btnHTML[1]),btnHTML[2],btnHTML[3]]; - if(o.scrollButtons.enable){ - mCSB_scrollTools.prepend(btn[0]).append(btn[1]).next(".mCSB_scrollTools").prepend(btn[2]).append(btn[3]); - } - }, - /* -------------------- */ - - - /* auto-adjusts scrollbar dragger length */ - _setDraggerLength=function(){ - var $this=$(this),d=$this.data(pluginPfx), - mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_container=$("#mCSB_"+d.idx+"_container"), - mCSB_dragger=[$("#mCSB_"+d.idx+"_dragger_vertical"),$("#mCSB_"+d.idx+"_dragger_horizontal")], - ratio=[mCustomScrollBox.height()/mCSB_container.outerHeight(false),mCustomScrollBox.width()/mCSB_container.outerWidth(false)], - l=[ - parseInt(mCSB_dragger[0].css("min-height")),Math.round(ratio[0]*mCSB_dragger[0].parent().height()), - parseInt(mCSB_dragger[1].css("min-width")),Math.round(ratio[1]*mCSB_dragger[1].parent().width()) - ], - h=oldIE && (l[1]contentHeight){contentHeight=h;} - if(w>contentWidth){contentWidth=w;} - return [contentHeight>mCustomScrollBox.height(),contentWidth>mCustomScrollBox.width()]; - }, - /* -------------------- */ - - - /* resets content position to 0 */ - _resetContentPosition=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_container=$("#mCSB_"+d.idx+"_container"), - mCSB_dragger=[$("#mCSB_"+d.idx+"_dragger_vertical"),$("#mCSB_"+d.idx+"_dragger_horizontal")]; - _stop($this); /* stop any current scrolling before resetting */ - if((o.axis!=="x" && !d.overflowed[0]) || (o.axis==="y" && d.overflowed[0])){ /* reset y */ - mCSB_dragger[0].add(mCSB_container).css("top",0); - _scrollTo($this,"_resetY"); - } - if((o.axis!=="y" && !d.overflowed[1]) || (o.axis==="x" && d.overflowed[1])){ /* reset x */ - var cx=dx=0; - if(d.langDir==="rtl"){ /* adjust left position for rtl direction */ - cx=mCustomScrollBox.width()-mCSB_container.outerWidth(false); - dx=Math.abs(cx/d.scrollRatio.x); - } - mCSB_container.css("left",cx); - mCSB_dragger[1].css("left",dx); - _scrollTo($this,"_resetX"); - } - }, - /* -------------------- */ - - - /* binds scrollbar events */ - _bindEvents=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt; - if(!d.bindEvents){ /* check if events are already bound */ - _draggable.call(this); - if(o.contentTouchScroll){_contentDraggable.call(this);} - _selectable.call(this); - if(o.mouseWheel.enable){ /* bind mousewheel fn when plugin is available */ - function _mwt(){ - mousewheelTimeout=setTimeout(function(){ - if(!$.event.special.mousewheel){ - _mwt(); - }else{ - clearTimeout(mousewheelTimeout); - _mousewheel.call($this[0]); - } - },100); - } - var mousewheelTimeout; - _mwt(); - } - _draggerRail.call(this); - _wrapperScroll.call(this); - if(o.advanced.autoScrollOnFocus){_focus.call(this);} - if(o.scrollButtons.enable){_buttons.call(this);} - if(o.keyboard.enable){_keyboard.call(this);} - d.bindEvents=true; - } - }, - /* -------------------- */ - - - /* unbinds scrollbar events */ - _unbindEvents=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - namespace=pluginPfx+"_"+d.idx, - sb=".mCSB_"+d.idx+"_scrollbar", - sel=$("#mCSB_"+d.idx+",#mCSB_"+d.idx+"_container,#mCSB_"+d.idx+"_container_wrapper,"+sb+" ."+classes[12]+",#mCSB_"+d.idx+"_dragger_vertical,#mCSB_"+d.idx+"_dragger_horizontal,"+sb+">a"), - mCSB_container=$("#mCSB_"+d.idx+"_container"); - if(o.advanced.releaseDraggableSelectors){sel.add($(o.advanced.releaseDraggableSelectors));} - if(o.advanced.extraDraggableSelectors){sel.add($(o.advanced.extraDraggableSelectors));} - if(d.bindEvents){ /* check if events are bound */ - /* unbind namespaced events from document/selectors */ - $(document).add($(!_canAccessIFrame() || top.document)).unbind("."+namespace); - sel.each(function(){ - $(this).unbind("."+namespace); - }); - /* clear and delete timeouts/objects */ - clearTimeout($this[0]._focusTimeout); _delete($this[0],"_focusTimeout"); - clearTimeout(d.sequential.step); _delete(d.sequential,"step"); - clearTimeout(mCSB_container[0].onCompleteTimeout); _delete(mCSB_container[0],"onCompleteTimeout"); - d.bindEvents=false; - } - }, - /* -------------------- */ - - - /* toggles scrollbar visibility */ - _scrollbarVisibility=function(disabled){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - contentWrapper=$("#mCSB_"+d.idx+"_container_wrapper"), - content=contentWrapper.length ? contentWrapper : $("#mCSB_"+d.idx+"_container"), - scrollbar=[$("#mCSB_"+d.idx+"_scrollbar_vertical"),$("#mCSB_"+d.idx+"_scrollbar_horizontal")], - mCSB_dragger=[scrollbar[0].find(".mCSB_dragger"),scrollbar[1].find(".mCSB_dragger")]; - if(o.axis!=="x"){ - if(d.overflowed[0] && !disabled){ - scrollbar[0].add(mCSB_dragger[0]).add(scrollbar[0].children("a")).css("display","block"); - content.removeClass(classes[8]+" "+classes[10]); - }else{ - if(o.alwaysShowScrollbar){ - if(o.alwaysShowScrollbar!==2){mCSB_dragger[0].css("display","none");} - content.removeClass(classes[10]); - }else{ - scrollbar[0].css("display","none"); - content.addClass(classes[10]); - } - content.addClass(classes[8]); - } - } - if(o.axis!=="y"){ - if(d.overflowed[1] && !disabled){ - scrollbar[1].add(mCSB_dragger[1]).add(scrollbar[1].children("a")).css("display","block"); - content.removeClass(classes[9]+" "+classes[11]); - }else{ - if(o.alwaysShowScrollbar){ - if(o.alwaysShowScrollbar!==2){mCSB_dragger[1].css("display","none");} - content.removeClass(classes[11]); - }else{ - scrollbar[1].css("display","none"); - content.addClass(classes[11]); - } - content.addClass(classes[9]); - } - } - if(!d.overflowed[0] && !d.overflowed[1]){ - $this.addClass(classes[5]); - }else{ - $this.removeClass(classes[5]); - } - }, - /* -------------------- */ - - - /* returns input coordinates of pointer, touch and mouse events (relative to document) */ - _coordinates=function(e){ - var t=e.type,o=e.target.ownerDocument!==document && frameElement!==null ? [$(frameElement).offset().top,$(frameElement).offset().left] : null, - io=_canAccessIFrame() && e.target.ownerDocument!==top.document && frameElement!==null ? [$(e.view.frameElement).offset().top,$(e.view.frameElement).offset().left] : [0,0]; - switch(t){ - case "pointerdown": case "MSPointerDown": case "pointermove": case "MSPointerMove": case "pointerup": case "MSPointerUp": - return o ? [e.originalEvent.pageY-o[0]+io[0],e.originalEvent.pageX-o[1]+io[1],false] : [e.originalEvent.pageY,e.originalEvent.pageX,false]; - break; - case "touchstart": case "touchmove": case "touchend": - var touch=e.originalEvent.touches[0] || e.originalEvent.changedTouches[0], - touches=e.originalEvent.touches.length || e.originalEvent.changedTouches.length; - return e.target.ownerDocument!==document ? [touch.screenY,touch.screenX,touches>1] : [touch.pageY,touch.pageX,touches>1]; - break; - default: - return o ? [e.pageY-o[0]+io[0],e.pageX-o[1]+io[1],false] : [e.pageY,e.pageX,false]; - } - }, - /* -------------------- */ - - - /* - SCROLLBAR DRAG EVENTS - scrolls content via scrollbar dragging - */ - _draggable=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - namespace=pluginPfx+"_"+d.idx, - draggerId=["mCSB_"+d.idx+"_dragger_vertical","mCSB_"+d.idx+"_dragger_horizontal"], - mCSB_container=$("#mCSB_"+d.idx+"_container"), - mCSB_dragger=$("#"+draggerId[0]+",#"+draggerId[1]), - draggable,dragY,dragX, - rds=o.advanced.releaseDraggableSelectors ? mCSB_dragger.add($(o.advanced.releaseDraggableSelectors)) : mCSB_dragger, - eds=o.advanced.extraDraggableSelectors ? $(!_canAccessIFrame() || top.document).add($(o.advanced.extraDraggableSelectors)) : $(!_canAccessIFrame() || top.document); - mCSB_dragger.bind("contextmenu."+namespace,function(e){ - e.preventDefault(); //prevent right click - }).bind("mousedown."+namespace+" touchstart."+namespace+" pointerdown."+namespace+" MSPointerDown."+namespace,function(e){ - e.stopImmediatePropagation(); - e.preventDefault(); - if(!_mouseBtnLeft(e)){return;} /* left mouse button only */ - touchActive=true; - if(oldIE){document.onselectstart=function(){return false;}} /* disable text selection for IE < 9 */ - _iframe.call(mCSB_container,false); /* enable scrollbar dragging over iframes by disabling their events */ - _stop($this); - draggable=$(this); - var offset=draggable.offset(),y=_coordinates(e)[0]-offset.top,x=_coordinates(e)[1]-offset.left, - h=draggable.height()+offset.top,w=draggable.width()+offset.left; - if(y0 && x0){ - dragY=y; - dragX=x; - } - _onDragClasses(draggable,"active",o.autoExpandScrollbar); - }).bind("touchmove."+namespace,function(e){ - e.stopImmediatePropagation(); - e.preventDefault(); - var offset=draggable.offset(),y=_coordinates(e)[0]-offset.top,x=_coordinates(e)[1]-offset.left; - _drag(dragY,dragX,y,x); - }); - $(document).add(eds).bind("mousemove."+namespace+" pointermove."+namespace+" MSPointerMove."+namespace,function(e){ - if(draggable){ - var offset=draggable.offset(),y=_coordinates(e)[0]-offset.top,x=_coordinates(e)[1]-offset.left; - if(dragY===y && dragX===x){return;} /* has it really moved? */ - _drag(dragY,dragX,y,x); - } - }).add(rds).bind("mouseup."+namespace+" touchend."+namespace+" pointerup."+namespace+" MSPointerUp."+namespace,function(e){ - if(draggable){ - _onDragClasses(draggable,"active",o.autoExpandScrollbar); - draggable=null; - } - touchActive=false; - if(oldIE){document.onselectstart=null;} /* enable text selection for IE < 9 */ - _iframe.call(mCSB_container,true); /* enable iframes events */ - }); - function _drag(dragY,dragX,y,x){ - mCSB_container[0].idleTimer=o.scrollInertia<233 ? 250 : 0; - if(draggable.attr("id")===draggerId[1]){ - var dir="x",to=((draggable[0].offsetLeft-dragX)+x)*d.scrollRatio.x; - }else{ - var dir="y",to=((draggable[0].offsetTop-dragY)+y)*d.scrollRatio.y; - } - _scrollTo($this,to.toString(),{dir:dir,drag:true}); - } - }, - /* -------------------- */ - - - /* - TOUCH SWIPE EVENTS - scrolls content via touch swipe - Emulates the native touch-swipe scrolling with momentum found in iOS, Android and WP devices - */ - _contentDraggable=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - namespace=pluginPfx+"_"+d.idx, - mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_container=$("#mCSB_"+d.idx+"_container"), - mCSB_dragger=[$("#mCSB_"+d.idx+"_dragger_vertical"),$("#mCSB_"+d.idx+"_dragger_horizontal")], - draggable,dragY,dragX,touchStartY,touchStartX,touchMoveY=[],touchMoveX=[],startTime,runningTime,endTime,distance,speed,amount, - durA=0,durB,overwrite=o.axis==="yx" ? "none" : "all",touchIntent=[],touchDrag,docDrag, - iframe=mCSB_container.find("iframe"), - events=[ - "touchstart."+namespace+" pointerdown."+namespace+" MSPointerDown."+namespace, //start - "touchmove."+namespace+" pointermove."+namespace+" MSPointerMove."+namespace, //move - "touchend."+namespace+" pointerup."+namespace+" MSPointerUp."+namespace //end - ], - touchAction=document.body.style.touchAction!==undefined && document.body.style.touchAction!==""; - mCSB_container.bind(events[0],function(e){ - _onTouchstart(e); - }).bind(events[1],function(e){ - _onTouchmove(e); - }); - mCustomScrollBox.bind(events[0],function(e){ - _onTouchstart2(e); - }).bind(events[2],function(e){ - _onTouchend(e); - }); - if(iframe.length){ - iframe.each(function(){ - $(this).bind("load",function(){ - /* bind events on accessible iframes */ - if(_canAccessIFrame(this)){ - $(this.contentDocument || this.contentWindow.document).bind(events[0],function(e){ - _onTouchstart(e); - _onTouchstart2(e); - }).bind(events[1],function(e){ - _onTouchmove(e); - }).bind(events[2],function(e){ - _onTouchend(e); - }); - } - }); - }); - } - function _onTouchstart(e){ - if(!_pointerTouch(e) || touchActive || _coordinates(e)[2]){touchable=0; return;} - touchable=1; touchDrag=0; docDrag=0; draggable=1; - $this.removeClass("mCS_touch_action"); - var offset=mCSB_container.offset(); - dragY=_coordinates(e)[0]-offset.top; - dragX=_coordinates(e)[1]-offset.left; - touchIntent=[_coordinates(e)[0],_coordinates(e)[1]]; - } - function _onTouchmove(e){ - if(!_pointerTouch(e) || touchActive || _coordinates(e)[2]){return;} - if(!o.documentTouchScroll){e.preventDefault();} - e.stopImmediatePropagation(); - if(docDrag && !touchDrag){return;} - if(draggable){ - runningTime=_getTime(); - var offset=mCustomScrollBox.offset(),y=_coordinates(e)[0]-offset.top,x=_coordinates(e)[1]-offset.left, - easing="mcsLinearOut"; - touchMoveY.push(y); - touchMoveX.push(x); - touchIntent[2]=Math.abs(_coordinates(e)[0]-touchIntent[0]); touchIntent[3]=Math.abs(_coordinates(e)[1]-touchIntent[1]); - if(d.overflowed[0]){ - var limit=mCSB_dragger[0].parent().height()-mCSB_dragger[0].height(), - prevent=((dragY-y)>0 && (y-dragY)>-(limit*d.scrollRatio.y) && (touchIntent[3]*20 && (x-dragX)>-(limitX*d.scrollRatio.x) && (touchIntent[2]*230){return;} - speed=1000/(endTime-startTime); - var easing="mcsEaseOut",slow=speed<2.5, - diff=slow ? [touchMoveY[touchMoveY.length-2],touchMoveX[touchMoveX.length-2]] : [0,0]; - distance=slow ? [(y-diff[0]),(x-diff[1])] : [y-touchStartY,x-touchStartX]; - var absDistance=[Math.abs(distance[0]),Math.abs(distance[1])]; - speed=slow ? [Math.abs(distance[0]/4),Math.abs(distance[1]/4)] : [speed,speed]; - var a=[ - Math.abs(mCSB_container[0].offsetTop)-(distance[0]*_m((absDistance[0]/speed[0]),speed[0])), - Math.abs(mCSB_container[0].offsetLeft)-(distance[1]*_m((absDistance[1]/speed[1]),speed[1])) - ]; - amount=o.axis==="yx" ? [a[0],a[1]] : o.axis==="x" ? [null,a[1]] : [a[0],null]; - durB=[(absDistance[0]*4)+o.scrollInertia,(absDistance[1]*4)+o.scrollInertia]; - var md=parseInt(o.contentTouchScroll) || 0; /* absolute minimum distance required */ - amount[0]=absDistance[0]>md ? amount[0] : 0; - amount[1]=absDistance[1]>md ? amount[1] : 0; - if(d.overflowed[0]){_drag(amount[0],durB[0],easing,"y",overwrite,false);} - if(d.overflowed[1]){_drag(amount[1],durB[1],easing,"x",overwrite,false);} - } - function _m(ds,s){ - var r=[s*1.5,s*2,s/1.5,s/2]; - if(ds>90){ - return s>4 ? r[0] : r[3]; - }else if(ds>60){ - return s>3 ? r[3] : r[2]; - }else if(ds>30){ - return s>8 ? r[1] : s>6 ? r[0] : s>4 ? s : r[2]; - }else{ - return s>8 ? s : r[3]; - } - } - function _drag(amount,dur,easing,dir,overwrite,drag){ - if(!amount){return;} - _scrollTo($this,amount.toString(),{dur:dur,scrollEasing:easing,dir:dir,overwrite:overwrite,drag:drag}); - } - }, - /* -------------------- */ - - - /* - SELECT TEXT EVENTS - scrolls content when text is selected - */ - _selectable=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt,seq=d.sequential, - namespace=pluginPfx+"_"+d.idx, - mCSB_container=$("#mCSB_"+d.idx+"_container"), - wrapper=mCSB_container.parent(), - action; - mCSB_container.bind("mousedown."+namespace,function(e){ - if(touchable){return;} - if(!action){action=1; touchActive=true;} - }).add(document).bind("mousemove."+namespace,function(e){ - if(!touchable && action && _sel()){ - var offset=mCSB_container.offset(), - y=_coordinates(e)[0]-offset.top+mCSB_container[0].offsetTop,x=_coordinates(e)[1]-offset.left+mCSB_container[0].offsetLeft; - if(y>0 && y0 && xwrapper.height()){ - _seq("on",40); - } - } - if(o.axis!=="y" && d.overflowed[1]){ - if(x<0){ - _seq("on",37); - }else if(x>wrapper.width()){ - _seq("on",39); - } - } - } - } - }).bind("mouseup."+namespace+" dragend."+namespace,function(e){ - if(touchable){return;} - if(action){action=0; _seq("off",null);} - touchActive=false; - }); - function _sel(){ - return window.getSelection ? window.getSelection().toString() : - document.selection && document.selection.type!="Control" ? document.selection.createRange().text : 0; - } - function _seq(a,c,s){ - seq.type=s && action ? "stepped" : "stepless"; - seq.scrollAmount=10; - _sequentialScroll($this,a,c,"mcsLinearOut",s ? 60 : null); - } - }, - /* -------------------- */ - - - /* - MOUSE WHEEL EVENT - scrolls content via mouse-wheel - via mouse-wheel plugin (https://github.com/brandonaaron/jquery-mousewheel) - */ - _mousewheel=function(){ - if(!$(this).data(pluginPfx)){return;} /* Check if the scrollbar is ready to use mousewheel events (issue: #185) */ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - namespace=pluginPfx+"_"+d.idx, - mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_dragger=[$("#mCSB_"+d.idx+"_dragger_vertical"),$("#mCSB_"+d.idx+"_dragger_horizontal")], - iframe=$("#mCSB_"+d.idx+"_container").find("iframe"); - if(iframe.length){ - iframe.each(function(){ - $(this).bind("load",function(){ - /* bind events on accessible iframes */ - if(_canAccessIFrame(this)){ - $(this.contentDocument || this.contentWindow.document).bind("mousewheel."+namespace,function(e,delta){ - _onMousewheel(e,delta); - }); - } - }); - }); - } - mCustomScrollBox.bind("mousewheel."+namespace,function(e,delta){ - _onMousewheel(e,delta); - }); - function _onMousewheel(e,delta){ - _stop($this); - if(_disableMousewheel($this,e.target)){return;} /* disables mouse-wheel when hovering specific elements */ - var deltaFactor=o.mouseWheel.deltaFactor!=="auto" ? parseInt(o.mouseWheel.deltaFactor) : (oldIE && e.deltaFactor<100) ? 100 : e.deltaFactor || 100, - dur=o.scrollInertia; - if(o.axis==="x" || o.mouseWheel.axis==="x"){ - var dir="x", - px=[Math.round(deltaFactor*d.scrollRatio.x),parseInt(o.mouseWheel.scrollAmount)], - amount=o.mouseWheel.scrollAmount!=="auto" ? px[1] : px[0]>=mCustomScrollBox.width() ? mCustomScrollBox.width()*0.9 : px[0], - contentPos=Math.abs($("#mCSB_"+d.idx+"_container")[0].offsetLeft), - draggerPos=mCSB_dragger[1][0].offsetLeft, - limit=mCSB_dragger[1].parent().width()-mCSB_dragger[1].width(), - dlt=o.mouseWheel.axis==="y" ? (e.deltaY || delta) : e.deltaX; - }else{ - var dir="y", - px=[Math.round(deltaFactor*d.scrollRatio.y),parseInt(o.mouseWheel.scrollAmount)], - amount=o.mouseWheel.scrollAmount!=="auto" ? px[1] : px[0]>=mCustomScrollBox.height() ? mCustomScrollBox.height()*0.9 : px[0], - contentPos=Math.abs($("#mCSB_"+d.idx+"_container")[0].offsetTop), - draggerPos=mCSB_dragger[0][0].offsetTop, - limit=mCSB_dragger[0].parent().height()-mCSB_dragger[0].height(), - dlt=e.deltaY || delta; - } - if((dir==="y" && !d.overflowed[0]) || (dir==="x" && !d.overflowed[1])){return;} - if(o.mouseWheel.invert || e.webkitDirectionInvertedFromDevice){dlt=-dlt;} - if(o.mouseWheel.normalizeDelta){dlt=dlt<0 ? -1 : 1;} - if((dlt>0 && draggerPos!==0) || (dlt<0 && draggerPos!==limit) || o.mouseWheel.preventDefault){ - e.stopImmediatePropagation(); - e.preventDefault(); - } - if(e.deltaFactor<5 && !o.mouseWheel.normalizeDelta){ - //very low deltaFactor values mean some kind of delta acceleration (e.g. osx trackpad), so adjusting scrolling accordingly - amount=e.deltaFactor; dur=17; - } - _scrollTo($this,(contentPos-(dlt*amount)).toString(),{dir:dir,dur:dur}); - } - }, - /* -------------------- */ - - - /* checks if iframe can be accessed */ - _canAccessIFrameCache=new Object(), - _canAccessIFrame=function(iframe){ - var result=false,cacheKey=false,html=null; - if(iframe===undefined){ - cacheKey="#empty"; - }else if($(iframe).attr("id")!==undefined){ - cacheKey=$(iframe).attr("id"); - } - if(cacheKey!==false && _canAccessIFrameCache[cacheKey]!==undefined){ - return _canAccessIFrameCache[cacheKey]; - } - if(!iframe){ - try{ - var doc=top.document; - html=doc.body.innerHTML; - }catch(err){/* do nothing */} - result=(html!==null); - }else{ - try{ - var doc=iframe.contentDocument || iframe.contentWindow.document; - html=doc.body.innerHTML; - }catch(err){/* do nothing */} - result=(html!==null); - } - if(cacheKey!==false){_canAccessIFrameCache[cacheKey]=result;} - return result; - }, - /* -------------------- */ - - - /* switches iframe's pointer-events property (drag, mousewheel etc. over cross-domain iframes) */ - _iframe=function(evt){ - var el=this.find("iframe"); - if(!el.length){return;} /* check if content contains iframes */ - var val=!evt ? "none" : "auto"; - el.css("pointer-events",val); /* for IE11, iframe's display property should not be "block" */ - }, - /* -------------------- */ - - - /* disables mouse-wheel when hovering specific elements like select, datalist etc. */ - _disableMousewheel=function(el,target){ - var tag=target.nodeName.toLowerCase(), - tags=el.data(pluginPfx).opt.mouseWheel.disableOver, - /* elements that require focus */ - focusTags=["select","textarea"]; - return $.inArray(tag,tags) > -1 && !($.inArray(tag,focusTags) > -1 && !$(target).is(":focus")); - }, - /* -------------------- */ - - - /* - DRAGGER RAIL CLICK EVENT - scrolls content via dragger rail - */ - _draggerRail=function(){ - var $this=$(this),d=$this.data(pluginPfx), - namespace=pluginPfx+"_"+d.idx, - mCSB_container=$("#mCSB_"+d.idx+"_container"), - wrapper=mCSB_container.parent(), - mCSB_draggerContainer=$(".mCSB_"+d.idx+"_scrollbar ."+classes[12]), - clickable; - mCSB_draggerContainer.bind("mousedown."+namespace+" touchstart."+namespace+" pointerdown."+namespace+" MSPointerDown."+namespace,function(e){ - touchActive=true; - if(!$(e.target).hasClass("mCSB_dragger")){clickable=1;} - }).bind("touchend."+namespace+" pointerup."+namespace+" MSPointerUp."+namespace,function(e){ - touchActive=false; - }).bind("click."+namespace,function(e){ - if(!clickable){return;} - clickable=0; - if($(e.target).hasClass(classes[12]) || $(e.target).hasClass("mCSB_draggerRail")){ - _stop($this); - var el=$(this),mCSB_dragger=el.find(".mCSB_dragger"); - if(el.parent(".mCSB_scrollTools_horizontal").length>0){ - if(!d.overflowed[1]){return;} - var dir="x", - clickDir=e.pageX>mCSB_dragger.offset().left ? -1 : 1, - to=Math.abs(mCSB_container[0].offsetLeft)-(clickDir*(wrapper.width()*0.9)); - }else{ - if(!d.overflowed[0]){return;} - var dir="y", - clickDir=e.pageY>mCSB_dragger.offset().top ? -1 : 1, - to=Math.abs(mCSB_container[0].offsetTop)-(clickDir*(wrapper.height()*0.9)); - } - _scrollTo($this,to.toString(),{dir:dir,scrollEasing:"mcsEaseInOut"}); - } - }); - }, - /* -------------------- */ - - - /* - FOCUS EVENT - scrolls content via element focus (e.g. clicking an input, pressing TAB key etc.) - */ - _focus=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - namespace=pluginPfx+"_"+d.idx, - mCSB_container=$("#mCSB_"+d.idx+"_container"), - wrapper=mCSB_container.parent(); - mCSB_container.bind("focusin."+namespace,function(e){ - var el=$(document.activeElement), - nested=mCSB_container.find(".mCustomScrollBox").length, - dur=0; - if(!el.is(o.advanced.autoScrollOnFocus)){return;} - _stop($this); - clearTimeout($this[0]._focusTimeout); - $this[0]._focusTimer=nested ? (dur+17)*nested : 0; - $this[0]._focusTimeout=setTimeout(function(){ - var to=[_childPos(el)[0],_childPos(el)[1]], - contentPos=[mCSB_container[0].offsetTop,mCSB_container[0].offsetLeft], - isVisible=[ - (contentPos[0]+to[0]>=0 && contentPos[0]+to[0]=0 && contentPos[0]+to[1]a"); - btn.bind("contextmenu."+namespace,function(e){ - e.preventDefault(); //prevent right click - }).bind("mousedown."+namespace+" touchstart."+namespace+" pointerdown."+namespace+" MSPointerDown."+namespace+" mouseup."+namespace+" touchend."+namespace+" pointerup."+namespace+" MSPointerUp."+namespace+" mouseout."+namespace+" pointerout."+namespace+" MSPointerOut."+namespace+" click."+namespace,function(e){ - e.preventDefault(); - if(!_mouseBtnLeft(e)){return;} /* left mouse button only */ - var btnClass=$(this).attr("class"); - seq.type=o.scrollButtons.scrollType; - switch(e.type){ - case "mousedown": case "touchstart": case "pointerdown": case "MSPointerDown": - if(seq.type==="stepped"){return;} - touchActive=true; - d.tweenRunning=false; - _seq("on",btnClass); - break; - case "mouseup": case "touchend": case "pointerup": case "MSPointerUp": - case "mouseout": case "pointerout": case "MSPointerOut": - if(seq.type==="stepped"){return;} - touchActive=false; - if(seq.dir){_seq("off",btnClass);} - break; - case "click": - if(seq.type!=="stepped" || d.tweenRunning){return;} - _seq("on",btnClass); - break; - } - function _seq(a,c){ - seq.scrollAmount=o.scrollButtons.scrollAmount; - _sequentialScroll($this,a,c); - } - }); - }, - /* -------------------- */ - - - /* - KEYBOARD EVENTS - scrolls content via keyboard - Keys: up arrow, down arrow, left arrow, right arrow, PgUp, PgDn, Home, End - */ - _keyboard=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt,seq=d.sequential, - namespace=pluginPfx+"_"+d.idx, - mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_container=$("#mCSB_"+d.idx+"_container"), - wrapper=mCSB_container.parent(), - editables="input,textarea,select,datalist,keygen,[contenteditable='true']", - iframe=mCSB_container.find("iframe"), - events=["blur."+namespace+" keydown."+namespace+" keyup."+namespace]; - if(iframe.length){ - iframe.each(function(){ - $(this).bind("load",function(){ - /* bind events on accessible iframes */ - if(_canAccessIFrame(this)){ - $(this.contentDocument || this.contentWindow.document).bind(events[0],function(e){ - _onKeyboard(e); - }); - } - }); - }); - } - mCustomScrollBox.attr("tabindex","0").bind(events[0],function(e){ - _onKeyboard(e); - }); - function _onKeyboard(e){ - switch(e.type){ - case "blur": - if(d.tweenRunning && seq.dir){_seq("off",null);} - break; - case "keydown": case "keyup": - var code=e.keyCode ? e.keyCode : e.which,action="on"; - if((o.axis!=="x" && (code===38 || code===40)) || (o.axis!=="y" && (code===37 || code===39))){ - /* up (38), down (40), left (37), right (39) arrows */ - if(((code===38 || code===40) && !d.overflowed[0]) || ((code===37 || code===39) && !d.overflowed[1])){return;} - if(e.type==="keyup"){action="off";} - if(!$(document.activeElement).is(editables)){ - e.preventDefault(); - e.stopImmediatePropagation(); - _seq(action,code); - } - }else if(code===33 || code===34){ - /* PgUp (33), PgDn (34) */ - if(d.overflowed[0] || d.overflowed[1]){ - e.preventDefault(); - e.stopImmediatePropagation(); - } - if(e.type==="keyup"){ - _stop($this); - var keyboardDir=code===34 ? -1 : 1; - if(o.axis==="x" || (o.axis==="yx" && d.overflowed[1] && !d.overflowed[0])){ - var dir="x",to=Math.abs(mCSB_container[0].offsetLeft)-(keyboardDir*(wrapper.width()*0.9)); - }else{ - var dir="y",to=Math.abs(mCSB_container[0].offsetTop)-(keyboardDir*(wrapper.height()*0.9)); - } - _scrollTo($this,to.toString(),{dir:dir,scrollEasing:"mcsEaseInOut"}); - } - }else if(code===35 || code===36){ - /* End (35), Home (36) */ - if(!$(document.activeElement).is(editables)){ - if(d.overflowed[0] || d.overflowed[1]){ - e.preventDefault(); - e.stopImmediatePropagation(); - } - if(e.type==="keyup"){ - if(o.axis==="x" || (o.axis==="yx" && d.overflowed[1] && !d.overflowed[0])){ - var dir="x",to=code===35 ? Math.abs(wrapper.width()-mCSB_container.outerWidth(false)) : 0; - }else{ - var dir="y",to=code===35 ? Math.abs(wrapper.height()-mCSB_container.outerHeight(false)) : 0; - } - _scrollTo($this,to.toString(),{dir:dir,scrollEasing:"mcsEaseInOut"}); - } - } - } - break; - } - function _seq(a,c){ - seq.type=o.keyboard.scrollType; - seq.scrollAmount=o.keyboard.scrollAmount; - if(seq.type==="stepped" && d.tweenRunning){return;} - _sequentialScroll($this,a,c); - } - } - }, - /* -------------------- */ - - - /* scrolls content sequentially (used when scrolling via buttons, keyboard arrows etc.) */ - _sequentialScroll=function(el,action,trigger,e,s){ - var d=el.data(pluginPfx),o=d.opt,seq=d.sequential, - mCSB_container=$("#mCSB_"+d.idx+"_container"), - once=seq.type==="stepped" ? true : false, - steplessSpeed=o.scrollInertia < 26 ? 26 : o.scrollInertia, /* 26/1.5=17 */ - steppedSpeed=o.scrollInertia < 1 ? 17 : o.scrollInertia; - switch(action){ - case "on": - seq.dir=[ - (trigger===classes[16] || trigger===classes[15] || trigger===39 || trigger===37 ? "x" : "y"), - (trigger===classes[13] || trigger===classes[15] || trigger===38 || trigger===37 ? -1 : 1) - ]; - _stop(el); - if(_isNumeric(trigger) && seq.type==="stepped"){return;} - _on(once); - break; - case "off": - _off(); - if(once || (d.tweenRunning && seq.dir)){ - _on(true); - } - break; - } - - /* starts sequence */ - function _on(once){ - if(o.snapAmount){seq.scrollAmount=!(o.snapAmount instanceof Array) ? o.snapAmount : seq.dir[0]==="x" ? o.snapAmount[1] : o.snapAmount[0];} /* scrolling snapping */ - var c=seq.type!=="stepped", /* continuous scrolling */ - t=s ? s : !once ? 1000/60 : c ? steplessSpeed/1.5 : steppedSpeed, /* timer */ - m=!once ? 2.5 : c ? 7.5 : 40, /* multiplier */ - contentPos=[Math.abs(mCSB_container[0].offsetTop),Math.abs(mCSB_container[0].offsetLeft)], - ratio=[d.scrollRatio.y>10 ? 10 : d.scrollRatio.y,d.scrollRatio.x>10 ? 10 : d.scrollRatio.x], - amount=seq.dir[0]==="x" ? contentPos[1]+(seq.dir[1]*(ratio[1]*m)) : contentPos[0]+(seq.dir[1]*(ratio[0]*m)), - px=seq.dir[0]==="x" ? contentPos[1]+(seq.dir[1]*parseInt(seq.scrollAmount)) : contentPos[0]+(seq.dir[1]*parseInt(seq.scrollAmount)), - to=seq.scrollAmount!=="auto" ? px : amount, - easing=e ? e : !once ? "mcsLinear" : c ? "mcsLinearOut" : "mcsEaseInOut", - onComplete=!once ? false : true; - if(once && t<17){ - to=seq.dir[0]==="x" ? contentPos[1] : contentPos[0]; - } - _scrollTo(el,to.toString(),{dir:seq.dir[0],scrollEasing:easing,dur:t,onComplete:onComplete}); - if(once){ - seq.dir=false; - return; - } - clearTimeout(seq.step); - seq.step=setTimeout(function(){ - _on(); - },t); - } - /* stops sequence */ - function _off(){ - clearTimeout(seq.step); - _delete(seq,"step"); - _stop(el); - } - }, - /* -------------------- */ - - - /* returns a yx array from value */ - _arr=function(val){ - var o=$(this).data(pluginPfx).opt,vals=[]; - if(typeof val==="function"){val=val();} /* check if the value is a single anonymous function */ - /* check if value is object or array, its length and create an array with yx values */ - if(!(val instanceof Array)){ /* object value (e.g. {y:"100",x:"100"}, 100 etc.) */ - vals[0]=val.y ? val.y : val.x || o.axis==="x" ? null : val; - vals[1]=val.x ? val.x : val.y || o.axis==="y" ? null : val; - }else{ /* array value (e.g. [100,100]) */ - vals=val.length>1 ? [val[0],val[1]] : o.axis==="x" ? [null,val[0]] : [val[0],null]; - } - /* check if array values are anonymous functions */ - if(typeof vals[0]==="function"){vals[0]=vals[0]();} - if(typeof vals[1]==="function"){vals[1]=vals[1]();} - return vals; - }, - /* -------------------- */ - - - /* translates values (e.g. "top", 100, "100px", "#id") to actual scroll-to positions */ - _to=function(val,dir){ - if(val==null || typeof val=="undefined"){return;} - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - mCSB_container=$("#mCSB_"+d.idx+"_container"), - wrapper=mCSB_container.parent(), - t=typeof val; - if(!dir){dir=o.axis==="x" ? "x" : "y";} - var contentLength=dir==="x" ? mCSB_container.outerWidth(false)-wrapper.width() : mCSB_container.outerHeight(false)-wrapper.height(), - contentPos=dir==="x" ? mCSB_container[0].offsetLeft : mCSB_container[0].offsetTop, - cssProp=dir==="x" ? "left" : "top"; - switch(t){ - case "function": /* this currently is not used. Consider removing it */ - return val(); - break; - case "object": /* js/jquery object */ - var obj=val.jquery ? val : $(val); - if(!obj.length){return;} - return dir==="x" ? _childPos(obj)[1] : _childPos(obj)[0]; - break; - case "string": case "number": - if(_isNumeric(val)){ /* numeric value */ - return Math.abs(val); - }else if(val.indexOf("%")!==-1){ /* percentage value */ - return Math.abs(contentLength*parseInt(val)/100); - }else if(val.indexOf("-=")!==-1){ /* decrease value */ - return Math.abs(contentPos-parseInt(val.split("-=")[1])); - }else if(val.indexOf("+=")!==-1){ /* inrease value */ - var p=(contentPos+parseInt(val.split("+=")[1])); - return p>=0 ? 0 : Math.abs(p); - }else if(val.indexOf("px")!==-1 && _isNumeric(val.split("px")[0])){ /* pixels string value (e.g. "100px") */ - return Math.abs(val.split("px")[0]); - }else{ - if(val==="top" || val==="left"){ /* special strings */ - return 0; - }else if(val==="bottom"){ - return Math.abs(wrapper.height()-mCSB_container.outerHeight(false)); - }else if(val==="right"){ - return Math.abs(wrapper.width()-mCSB_container.outerWidth(false)); - }else if(val==="first" || val==="last"){ - var obj=mCSB_container.find(":"+val); - return dir==="x" ? _childPos(obj)[1] : _childPos(obj)[0]; - }else{ - if($(val).length){ /* jquery selector */ - return dir==="x" ? _childPos($(val))[1] : _childPos($(val))[0]; - }else{ /* other values (e.g. "100em") */ - mCSB_container.css(cssProp,val); - methods.update.call(null,$this[0]); - return; - } - } - } - break; - } - }, - /* -------------------- */ - - - /* calls the update method automatically */ - _autoUpdate=function(rem){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - mCSB_container=$("#mCSB_"+d.idx+"_container"); - if(rem){ - /* - removes autoUpdate timer - usage: _autoUpdate.call(this,"remove"); - */ - clearTimeout(mCSB_container[0].autoUpdate); - _delete(mCSB_container[0],"autoUpdate"); - return; - } - upd(); - function upd(){ - clearTimeout(mCSB_container[0].autoUpdate); - if($this.parents("html").length===0){ - /* check element in dom tree */ - $this=null; - return; - } - mCSB_container[0].autoUpdate=setTimeout(function(){ - /* update on specific selector(s) length and size change */ - if(o.advanced.updateOnSelectorChange){ - d.poll.change.n=sizesSum(); - if(d.poll.change.n!==d.poll.change.o){ - d.poll.change.o=d.poll.change.n; - doUpd(3); - return; - } - } - /* update on main element and scrollbar size changes */ - if(o.advanced.updateOnContentResize){ - d.poll.size.n=$this[0].scrollHeight+$this[0].scrollWidth+mCSB_container[0].offsetHeight+$this[0].offsetHeight+$this[0].offsetWidth; - if(d.poll.size.n!==d.poll.size.o){ - d.poll.size.o=d.poll.size.n; - doUpd(1); - return; - } - } - /* update on image load */ - if(o.advanced.updateOnImageLoad){ - if(!(o.advanced.updateOnImageLoad==="auto" && o.axis==="y")){ //by default, it doesn't run on vertical content - d.poll.img.n=mCSB_container.find("img").length; - if(d.poll.img.n!==d.poll.img.o){ - d.poll.img.o=d.poll.img.n; - mCSB_container.find("img").each(function(){ - imgLoader(this); - }); - return; - } - } - } - if(o.advanced.updateOnSelectorChange || o.advanced.updateOnContentResize || o.advanced.updateOnImageLoad){upd();} - },o.advanced.autoUpdateTimeout); - } - /* a tiny image loader */ - function imgLoader(el){ - if($(el).hasClass(classes[2])){doUpd(); return;} - var img=new Image(); - function createDelegate(contextObject,delegateMethod){ - return function(){return delegateMethod.apply(contextObject,arguments);} - } - function imgOnLoad(){ - this.onload=null; - $(el).addClass(classes[2]); - doUpd(2); - } - img.onload=createDelegate(img,imgOnLoad); - img.src=el.src; - } - /* returns the total height and width sum of all elements matching the selector */ - function sizesSum(){ - if(o.advanced.updateOnSelectorChange===true){o.advanced.updateOnSelectorChange="*";} - var total=0,sel=mCSB_container.find(o.advanced.updateOnSelectorChange); - if(o.advanced.updateOnSelectorChange && sel.length>0){sel.each(function(){total+=this.offsetHeight+this.offsetWidth;});} - return total; - } - /* calls the update method */ - function doUpd(cb){ - clearTimeout(mCSB_container[0].autoUpdate); - methods.update.call(null,$this[0],cb); - } - }, - /* -------------------- */ - - - /* snaps scrolling to a multiple of a pixels number */ - _snapAmount=function(to,amount,offset){ - return (Math.round(to/amount)*amount-offset); - }, - /* -------------------- */ - - - /* stops content and scrollbar animations */ - _stop=function(el){ - var d=el.data(pluginPfx), - sel=$("#mCSB_"+d.idx+"_container,#mCSB_"+d.idx+"_container_wrapper,#mCSB_"+d.idx+"_dragger_vertical,#mCSB_"+d.idx+"_dragger_horizontal"); - sel.each(function(){ - _stopTween.call(this); - }); - }, - /* -------------------- */ - - - /* - ANIMATES CONTENT - This is where the actual scrolling happens - */ - _scrollTo=function(el,to,options){ - var d=el.data(pluginPfx),o=d.opt, - defaults={ - trigger:"internal", - dir:"y", - scrollEasing:"mcsEaseOut", - drag:false, - dur:o.scrollInertia, - overwrite:"all", - callbacks:true, - onStart:true, - onUpdate:true, - onComplete:true - }, - options=$.extend(defaults,options), - dur=[options.dur,(options.drag ? 0 : options.dur)], - mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_container=$("#mCSB_"+d.idx+"_container"), - wrapper=mCSB_container.parent(), - totalScrollOffsets=o.callbacks.onTotalScrollOffset ? _arr.call(el,o.callbacks.onTotalScrollOffset) : [0,0], - totalScrollBackOffsets=o.callbacks.onTotalScrollBackOffset ? _arr.call(el,o.callbacks.onTotalScrollBackOffset) : [0,0]; - d.trigger=options.trigger; - if(wrapper.scrollTop()!==0 || wrapper.scrollLeft()!==0){ /* always reset scrollTop/Left */ - $(".mCSB_"+d.idx+"_scrollbar").css("visibility","visible"); - wrapper.scrollTop(0).scrollLeft(0); - } - if(to==="_resetY" && !d.contentReset.y){ - /* callbacks: onOverflowYNone */ - if(_cb("onOverflowYNone")){o.callbacks.onOverflowYNone.call(el[0]);} - d.contentReset.y=1; - } - if(to==="_resetX" && !d.contentReset.x){ - /* callbacks: onOverflowXNone */ - if(_cb("onOverflowXNone")){o.callbacks.onOverflowXNone.call(el[0]);} - d.contentReset.x=1; - } - if(to==="_resetY" || to==="_resetX"){return;} - if((d.contentReset.y || !el[0].mcs) && d.overflowed[0]){ - /* callbacks: onOverflowY */ - if(_cb("onOverflowY")){o.callbacks.onOverflowY.call(el[0]);} - d.contentReset.x=null; - } - if((d.contentReset.x || !el[0].mcs) && d.overflowed[1]){ - /* callbacks: onOverflowX */ - if(_cb("onOverflowX")){o.callbacks.onOverflowX.call(el[0]);} - d.contentReset.x=null; - } - if(o.snapAmount){ /* scrolling snapping */ - var snapAmount=!(o.snapAmount instanceof Array) ? o.snapAmount : options.dir==="x" ? o.snapAmount[1] : o.snapAmount[0]; - to=_snapAmount(to,snapAmount,o.snapOffset); - } - switch(options.dir){ - case "x": - var mCSB_dragger=$("#mCSB_"+d.idx+"_dragger_horizontal"), - property="left", - contentPos=mCSB_container[0].offsetLeft, - limit=[ - mCustomScrollBox.width()-mCSB_container.outerWidth(false), - mCSB_dragger.parent().width()-mCSB_dragger.width() - ], - scrollTo=[to,to===0 ? 0 : (to/d.scrollRatio.x)], - tso=totalScrollOffsets[1], - tsbo=totalScrollBackOffsets[1], - totalScrollOffset=tso>0 ? tso/d.scrollRatio.x : 0, - totalScrollBackOffset=tsbo>0 ? tsbo/d.scrollRatio.x : 0; - break; - case "y": - var mCSB_dragger=$("#mCSB_"+d.idx+"_dragger_vertical"), - property="top", - contentPos=mCSB_container[0].offsetTop, - limit=[ - mCustomScrollBox.height()-mCSB_container.outerHeight(false), - mCSB_dragger.parent().height()-mCSB_dragger.height() - ], - scrollTo=[to,to===0 ? 0 : (to/d.scrollRatio.y)], - tso=totalScrollOffsets[0], - tsbo=totalScrollBackOffsets[0], - totalScrollOffset=tso>0 ? tso/d.scrollRatio.y : 0, - totalScrollBackOffset=tsbo>0 ? tsbo/d.scrollRatio.y : 0; - break; - } - if(scrollTo[1]<0 || (scrollTo[0]===0 && scrollTo[1]===0)){ - scrollTo=[0,0]; - }else if(scrollTo[1]>=limit[1]){ - scrollTo=[limit[0],limit[1]]; - }else{ - scrollTo[0]=-scrollTo[0]; - } - if(!el[0].mcs){ - _mcs(); /* init mcs object (once) to make it available before callbacks */ - if(_cb("onInit")){o.callbacks.onInit.call(el[0]);} /* callbacks: onInit */ - } - clearTimeout(mCSB_container[0].onCompleteTimeout); - _tweenTo(mCSB_dragger[0],property,Math.round(scrollTo[1]),dur[1],options.scrollEasing); - if(!d.tweenRunning && ((contentPos===0 && scrollTo[0]>=0) || (contentPos===limit[0] && scrollTo[0]<=limit[0]))){return;} - _tweenTo(mCSB_container[0],property,Math.round(scrollTo[0]),dur[0],options.scrollEasing,options.overwrite,{ - onStart:function(){ - if(options.callbacks && options.onStart && !d.tweenRunning){ - /* callbacks: onScrollStart */ - if(_cb("onScrollStart")){_mcs(); o.callbacks.onScrollStart.call(el[0]);} - d.tweenRunning=true; - _onDragClasses(mCSB_dragger); - d.cbOffsets=_cbOffsets(); - } - },onUpdate:function(){ - if(options.callbacks && options.onUpdate){ - /* callbacks: whileScrolling */ - if(_cb("whileScrolling")){_mcs(); o.callbacks.whileScrolling.call(el[0]);} - } - },onComplete:function(){ - if(options.callbacks && options.onComplete){ - if(o.axis==="yx"){clearTimeout(mCSB_container[0].onCompleteTimeout);} - var t=mCSB_container[0].idleTimer || 0; - mCSB_container[0].onCompleteTimeout=setTimeout(function(){ - /* callbacks: onScroll, onTotalScroll, onTotalScrollBack */ - if(_cb("onScroll")){_mcs(); o.callbacks.onScroll.call(el[0]);} - if(_cb("onTotalScroll") && scrollTo[1]>=limit[1]-totalScrollOffset && d.cbOffsets[0]){_mcs(); o.callbacks.onTotalScroll.call(el[0]);} - if(_cb("onTotalScrollBack") && scrollTo[1]<=totalScrollBackOffset && d.cbOffsets[1]){_mcs(); o.callbacks.onTotalScrollBack.call(el[0]);} - d.tweenRunning=false; - mCSB_container[0].idleTimer=0; - _onDragClasses(mCSB_dragger,"hide"); - },t); - } - } - }); - /* checks if callback function exists */ - function _cb(cb){ - return d && o.callbacks[cb] && typeof o.callbacks[cb]==="function"; - } - /* checks whether callback offsets always trigger */ - function _cbOffsets(){ - return [o.callbacks.alwaysTriggerOffsets || contentPos>=limit[0]+tso,o.callbacks.alwaysTriggerOffsets || contentPos<=-tsbo]; - } - /* - populates object with useful values for the user - values: - content: this.mcs.content - content top position: this.mcs.top - content left position: this.mcs.left - dragger top position: this.mcs.draggerTop - dragger left position: this.mcs.draggerLeft - scrolling y percentage: this.mcs.topPct - scrolling x percentage: this.mcs.leftPct - scrolling direction: this.mcs.direction - */ - function _mcs(){ - var cp=[mCSB_container[0].offsetTop,mCSB_container[0].offsetLeft], /* content position */ - dp=[mCSB_dragger[0].offsetTop,mCSB_dragger[0].offsetLeft], /* dragger position */ - cl=[mCSB_container.outerHeight(false),mCSB_container.outerWidth(false)], /* content length */ - pl=[mCustomScrollBox.height(),mCustomScrollBox.width()]; /* content parent length */ - el[0].mcs={ - content:mCSB_container, /* original content wrapper as jquery object */ - top:cp[0],left:cp[1],draggerTop:dp[0],draggerLeft:dp[1], - topPct:Math.round((100*Math.abs(cp[0]))/(Math.abs(cl[0])-pl[0])),leftPct:Math.round((100*Math.abs(cp[1]))/(Math.abs(cl[1])-pl[1])), - direction:options.dir - }; - /* - this refers to the original element containing the scrollbar(s) - usage: this.mcs.top, this.mcs.leftPct etc. - */ - } - }, - /* -------------------- */ - - - /* - CUSTOM JAVASCRIPT ANIMATION TWEEN - Lighter and faster than jquery animate() and css transitions - Animates top/left properties and includes easings - */ - _tweenTo=function(el,prop,to,duration,easing,overwrite,callbacks){ - if(!el._mTween){el._mTween={top:{},left:{}};} - var callbacks=callbacks || {}, - onStart=callbacks.onStart || function(){},onUpdate=callbacks.onUpdate || function(){},onComplete=callbacks.onComplete || function(){}, - startTime=_getTime(),_delay,progress=0,from=el.offsetTop,elStyle=el.style,_request,tobj=el._mTween[prop]; - if(prop==="left"){from=el.offsetLeft;} - var diff=to-from; - tobj.stop=0; - if(overwrite!=="none"){_cancelTween();} - _startTween(); - function _step(){ - if(tobj.stop){return;} - if(!progress){onStart.call();} - progress=_getTime()-startTime; - _tween(); - if(progress>=tobj.time){ - tobj.time=(progress>tobj.time) ? progress+_delay-(progress-tobj.time) : progress+_delay-1; - if(tobj.time0){ - tobj.currVal=_ease(tobj.time,from,diff,duration,easing); - elStyle[prop]=Math.round(tobj.currVal)+"px"; - }else{ - elStyle[prop]=to+"px"; - } - onUpdate.call(); - } - function _startTween(){ - _delay=1000/60; - tobj.time=progress+_delay; - _request=(!window.requestAnimationFrame) ? function(f){_tween(); return setTimeout(f,0.01);} : window.requestAnimationFrame; - tobj.id=_request(_step); - } - function _cancelTween(){ - if(tobj.id==null){return;} - if(!window.requestAnimationFrame){clearTimeout(tobj.id); - }else{window.cancelAnimationFrame(tobj.id);} - tobj.id=null; - } - function _ease(t,b,c,d,type){ - switch(type){ - case "linear": case "mcsLinear": - return c*t/d + b; - break; - case "mcsLinearOut": - t/=d; t--; return c * Math.sqrt(1 - t*t) + b; - break; - case "easeInOutSmooth": - t/=d/2; - if(t<1) return c/2*t*t + b; - t--; - return -c/2 * (t*(t-2) - 1) + b; - break; - case "easeInOutStrong": - t/=d/2; - if(t<1) return c/2 * Math.pow( 2, 10 * (t - 1) ) + b; - t--; - return c/2 * ( -Math.pow( 2, -10 * t) + 2 ) + b; - break; - case "easeInOut": case "mcsEaseInOut": - t/=d/2; - if(t<1) return c/2*t*t*t + b; - t-=2; - return c/2*(t*t*t + 2) + b; - break; - case "easeOutSmooth": - t/=d; t--; - return -c * (t*t*t*t - 1) + b; - break; - case "easeOutStrong": - return c * ( -Math.pow( 2, -10 * t/d ) + 1 ) + b; - break; - case "easeOut": case "mcsEaseOut": default: - var ts=(t/=d)*t,tc=ts*t; - return b+c*(0.499999999999997*tc*ts + -2.5*ts*ts + 5.5*tc + -6.5*ts + 4*t); - } - } - }, - /* -------------------- */ - - - /* returns current time */ - _getTime=function(){ - if(window.performance && window.performance.now){ - return window.performance.now(); - }else{ - if(window.performance && window.performance.webkitNow){ - return window.performance.webkitNow(); - }else{ - if(Date.now){return Date.now();}else{return new Date().getTime();} - } - } - }, - /* -------------------- */ - - - /* stops a tween */ - _stopTween=function(){ - var el=this; - if(!el._mTween){el._mTween={top:{},left:{}};} - var props=["top","left"]; - for(var i=0; i
","
"],s="yx"===i.axis?"mCSB_vertical_horizontal":"x"===i.axis?"mCSB_horizontal":"mCSB_vertical",c="yx"===i.axis?l[0]+l[1]:"x"===i.axis?l[1]:l[0],u="yx"===i.axis?"
":"",f=i.autoHideScrollbar?" "+d[6]:"",h="x"!==i.axis&&"rtl"===n.langDir?" "+d[7]:"";i.setWidth&&t.css("width",i.setWidth),i.setHeight&&t.css("height",i.setHeight),i.setLeft="y"!==i.axis&&"rtl"===n.langDir?"989999px":i.setLeft,t.addClass(o+" _"+a+"_"+n.idx+f+h).wrapInner("
");var m=e("#mCSB_"+n.idx),p=e("#mCSB_"+n.idx+"_container");"y"===i.axis||i.advanced.autoExpandHorizontalScroll||p.css("width",x(p)),"outside"===i.scrollbarPosition?("static"===t.css("position")&&t.css("position","relative"),t.css("overflow","visible"),m.addClass("mCSB_outside").after(c)):(m.addClass("mCSB_inside").append(c),p.wrap(u)),w.call(this);var g=[e("#mCSB_"+n.idx+"_dragger_vertical"),e("#mCSB_"+n.idx+"_dragger_horizontal")];g[0].css("min-height",g[0].height()),g[1].css("min-width",g[1].width())},x=function(t){var o=[t[0].scrollWidth,Math.max.apply(Math,t.children().map(function(){return e(this).outerWidth(!0)}).get())],a=t.parent().width();return o[0]>a?o[0]:o[1]>a?o[1]:"100%"},_=function(){var t=e(this),o=t.data(a),n=o.opt,i=e("#mCSB_"+o.idx+"_container");if(n.advanced.autoExpandHorizontalScroll&&"y"!==n.axis){i.css({width:"auto","min-width":0,"overflow-x":"scroll"});var r=Math.ceil(i[0].scrollWidth);3===n.advanced.autoExpandHorizontalScroll||2!==n.advanced.autoExpandHorizontalScroll&&r>i.parent().width()?i.css({width:r,"min-width":"100%","overflow-x":"inherit"}):i.css({"overflow-x":"inherit",position:"absolute"}).wrap("
").css({width:Math.ceil(i[0].getBoundingClientRect().right+.4)-Math.floor(i[0].getBoundingClientRect().left),"min-width":"100%",position:"relative"}).unwrap()}},w=function(){var t=e(this),o=t.data(a),n=o.opt,i=e(".mCSB_"+o.idx+"_scrollbar:first"),r=oe(n.scrollButtons.tabindex)?"tabindex='"+n.scrollButtons.tabindex+"'":"",l=["","","",""],s=["x"===n.axis?l[2]:l[0],"x"===n.axis?l[3]:l[1],l[2],l[3]];n.scrollButtons.enable&&i.prepend(s[0]).append(s[1]).next(".mCSB_scrollTools").prepend(s[2]).append(s[3])},S=function(){var t=e(this),o=t.data(a),n=e("#mCSB_"+o.idx),i=e("#mCSB_"+o.idx+"_container"),r=[e("#mCSB_"+o.idx+"_dragger_vertical"),e("#mCSB_"+o.idx+"_dragger_horizontal")],l=[n.height()/i.outerHeight(!1),n.width()/i.outerWidth(!1)],c=[parseInt(r[0].css("min-height")),Math.round(l[0]*r[0].parent().height()),parseInt(r[1].css("min-width")),Math.round(l[1]*r[1].parent().width())],d=s&&c[1]r&&(r=s),c>l&&(l=c),[r>n.height(),l>n.width()]},B=function(){var t=e(this),o=t.data(a),n=o.opt,i=e("#mCSB_"+o.idx),r=e("#mCSB_"+o.idx+"_container"),l=[e("#mCSB_"+o.idx+"_dragger_vertical"),e("#mCSB_"+o.idx+"_dragger_horizontal")];if(Q(t),("x"!==n.axis&&!o.overflowed[0]||"y"===n.axis&&o.overflowed[0])&&(l[0].add(r).css("top",0),G(t,"_resetY")),"y"!==n.axis&&!o.overflowed[1]||"x"===n.axis&&o.overflowed[1]){var s=dx=0;"rtl"===o.langDir&&(s=i.width()-r.outerWidth(!1),dx=Math.abs(s/o.scrollRatio.x)),r.css("left",s),l[1].css("left",dx),G(t,"_resetX")}},T=function(){function t(){r=setTimeout(function(){e.event.special.mousewheel?(clearTimeout(r),W.call(o[0])):t()},100)}var o=e(this),n=o.data(a),i=n.opt;if(!n.bindEvents){if(I.call(this),i.contentTouchScroll&&D.call(this),E.call(this),i.mouseWheel.enable){var r;t()}P.call(this),U.call(this),i.advanced.autoScrollOnFocus&&H.call(this),i.scrollButtons.enable&&F.call(this),i.keyboard.enable&&q.call(this),n.bindEvents=!0}},k=function(){var t=e(this),o=t.data(a),n=o.opt,i=a+"_"+o.idx,r=".mCSB_"+o.idx+"_scrollbar",l=e("#mCSB_"+o.idx+",#mCSB_"+o.idx+"_container,#mCSB_"+o.idx+"_container_wrapper,"+r+" ."+d[12]+",#mCSB_"+o.idx+"_dragger_vertical,#mCSB_"+o.idx+"_dragger_horizontal,"+r+">a"),s=e("#mCSB_"+o.idx+"_container");n.advanced.releaseDraggableSelectors&&l.add(e(n.advanced.releaseDraggableSelectors)),n.advanced.extraDraggableSelectors&&l.add(e(n.advanced.extraDraggableSelectors)),o.bindEvents&&(e(document).add(e(!A()||top.document)).unbind("."+i),l.each(function(){e(this).unbind("."+i)}),clearTimeout(t[0]._focusTimeout),$(t[0],"_focusTimeout"),clearTimeout(o.sequential.step),$(o.sequential,"step"),clearTimeout(s[0].onCompleteTimeout),$(s[0],"onCompleteTimeout"),o.bindEvents=!1)},M=function(t){var o=e(this),n=o.data(a),i=n.opt,r=e("#mCSB_"+n.idx+"_container_wrapper"),l=r.length?r:e("#mCSB_"+n.idx+"_container"),s=[e("#mCSB_"+n.idx+"_scrollbar_vertical"),e("#mCSB_"+n.idx+"_scrollbar_horizontal")],c=[s[0].find(".mCSB_dragger"),s[1].find(".mCSB_dragger")];"x"!==i.axis&&(n.overflowed[0]&&!t?(s[0].add(c[0]).add(s[0].children("a")).css("display","block"),l.removeClass(d[8]+" "+d[10])):(i.alwaysShowScrollbar?(2!==i.alwaysShowScrollbar&&c[0].css("display","none"),l.removeClass(d[10])):(s[0].css("display","none"),l.addClass(d[10])),l.addClass(d[8]))),"y"!==i.axis&&(n.overflowed[1]&&!t?(s[1].add(c[1]).add(s[1].children("a")).css("display","block"),l.removeClass(d[9]+" "+d[11])):(i.alwaysShowScrollbar?(2!==i.alwaysShowScrollbar&&c[1].css("display","none"),l.removeClass(d[11])):(s[1].css("display","none"),l.addClass(d[11])),l.addClass(d[9]))),n.overflowed[0]||n.overflowed[1]?o.removeClass(d[5]):o.addClass(d[5])},O=function(t){var o=t.type,a=t.target.ownerDocument!==document&&null!==frameElement?[e(frameElement).offset().top,e(frameElement).offset().left]:null,n=A()&&t.target.ownerDocument!==top.document&&null!==frameElement?[e(t.view.frameElement).offset().top,e(t.view.frameElement).offset().left]:[0,0];switch(o){case"pointerdown":case"MSPointerDown":case"pointermove":case"MSPointerMove":case"pointerup":case"MSPointerUp":return a?[t.originalEvent.pageY-a[0]+n[0],t.originalEvent.pageX-a[1]+n[1],!1]:[t.originalEvent.pageY,t.originalEvent.pageX,!1];case"touchstart":case"touchmove":case"touchend":var i=t.originalEvent.touches[0]||t.originalEvent.changedTouches[0],r=t.originalEvent.touches.length||t.originalEvent.changedTouches.length;return t.target.ownerDocument!==document?[i.screenY,i.screenX,r>1]:[i.pageY,i.pageX,r>1];default:return a?[t.pageY-a[0]+n[0],t.pageX-a[1]+n[1],!1]:[t.pageY,t.pageX,!1]}},I=function(){function t(e,t,a,n){if(h[0].idleTimer=d.scrollInertia<233?250:0,o.attr("id")===f[1])var i="x",s=(o[0].offsetLeft-t+n)*l.scrollRatio.x;else var i="y",s=(o[0].offsetTop-e+a)*l.scrollRatio.y;G(r,s.toString(),{dir:i,drag:!0})}var o,n,i,r=e(this),l=r.data(a),d=l.opt,u=a+"_"+l.idx,f=["mCSB_"+l.idx+"_dragger_vertical","mCSB_"+l.idx+"_dragger_horizontal"],h=e("#mCSB_"+l.idx+"_container"),m=e("#"+f[0]+",#"+f[1]),p=d.advanced.releaseDraggableSelectors?m.add(e(d.advanced.releaseDraggableSelectors)):m,g=d.advanced.extraDraggableSelectors?e(!A()||top.document).add(e(d.advanced.extraDraggableSelectors)):e(!A()||top.document);m.bind("contextmenu."+u,function(e){e.preventDefault()}).bind("mousedown."+u+" touchstart."+u+" pointerdown."+u+" MSPointerDown."+u,function(t){if(t.stopImmediatePropagation(),t.preventDefault(),ee(t)){c=!0,s&&(document.onselectstart=function(){return!1}),L.call(h,!1),Q(r),o=e(this);var a=o.offset(),l=O(t)[0]-a.top,u=O(t)[1]-a.left,f=o.height()+a.top,m=o.width()+a.left;f>l&&l>0&&m>u&&u>0&&(n=l,i=u),C(o,"active",d.autoExpandScrollbar)}}).bind("touchmove."+u,function(e){e.stopImmediatePropagation(),e.preventDefault();var a=o.offset(),r=O(e)[0]-a.top,l=O(e)[1]-a.left;t(n,i,r,l)}),e(document).add(g).bind("mousemove."+u+" pointermove."+u+" MSPointerMove."+u,function(e){if(o){var a=o.offset(),r=O(e)[0]-a.top,l=O(e)[1]-a.left;if(n===r&&i===l)return;t(n,i,r,l)}}).add(p).bind("mouseup."+u+" touchend."+u+" pointerup."+u+" MSPointerUp."+u,function(){o&&(C(o,"active",d.autoExpandScrollbar),o=null),c=!1,s&&(document.onselectstart=null),L.call(h,!0)})},D=function(){function o(e){if(!te(e)||c||O(e)[2])return void(t=0);t=1,b=0,C=0,d=1,y.removeClass("mCS_touch_action");var o=I.offset();u=O(e)[0]-o.top,f=O(e)[1]-o.left,z=[O(e)[0],O(e)[1]]}function n(e){if(te(e)&&!c&&!O(e)[2]&&(T.documentTouchScroll||e.preventDefault(),e.stopImmediatePropagation(),(!C||b)&&d)){g=K();var t=M.offset(),o=O(e)[0]-t.top,a=O(e)[1]-t.left,n="mcsLinearOut";if(E.push(o),W.push(a),z[2]=Math.abs(O(e)[0]-z[0]),z[3]=Math.abs(O(e)[1]-z[1]),B.overflowed[0])var i=D[0].parent().height()-D[0].height(),r=u-o>0&&o-u>-(i*B.scrollRatio.y)&&(2*z[3]0&&a-f>-(l*B.scrollRatio.x)&&(2*z[2]30)){_=1e3/(v-p);var n="mcsEaseOut",i=2.5>_,r=i?[E[E.length-2],W[W.length-2]]:[0,0];x=i?[o-r[0],a-r[1]]:[o-h,a-m];var u=[Math.abs(x[0]),Math.abs(x[1])];_=i?[Math.abs(x[0]/4),Math.abs(x[1]/4)]:[_,_];var f=[Math.abs(I[0].offsetTop)-x[0]*l(u[0]/_[0],_[0]),Math.abs(I[0].offsetLeft)-x[1]*l(u[1]/_[1],_[1])];w="yx"===T.axis?[f[0],f[1]]:"x"===T.axis?[null,f[1]]:[f[0],null],S=[4*u[0]+T.scrollInertia,4*u[1]+T.scrollInertia];var y=parseInt(T.contentTouchScroll)||0;w[0]=u[0]>y?w[0]:0,w[1]=u[1]>y?w[1]:0,B.overflowed[0]&&s(w[0],S[0],n,"y",L,!1),B.overflowed[1]&&s(w[1],S[1],n,"x",L,!1)}}}function l(e,t){var o=[1.5*t,2*t,t/1.5,t/2];return e>90?t>4?o[0]:o[3]:e>60?t>3?o[3]:o[2]:e>30?t>8?o[1]:t>6?o[0]:t>4?t:o[2]:t>8?t:o[3]}function s(e,t,o,a,n,i){e&&G(y,e.toString(),{dur:t,scrollEasing:o,dir:a,overwrite:n,drag:i})}var d,u,f,h,m,p,g,v,x,_,w,S,b,C,y=e(this),B=y.data(a),T=B.opt,k=a+"_"+B.idx,M=e("#mCSB_"+B.idx),I=e("#mCSB_"+B.idx+"_container"),D=[e("#mCSB_"+B.idx+"_dragger_vertical"),e("#mCSB_"+B.idx+"_dragger_horizontal")],E=[],W=[],R=0,L="yx"===T.axis?"none":"all",z=[],P=I.find("iframe"),H=["touchstart."+k+" pointerdown."+k+" MSPointerDown."+k,"touchmove."+k+" pointermove."+k+" MSPointerMove."+k,"touchend."+k+" pointerup."+k+" MSPointerUp."+k],U=void 0!==document.body.style.touchAction&&""!==document.body.style.touchAction;I.bind(H[0],function(e){o(e)}).bind(H[1],function(e){n(e)}),M.bind(H[0],function(e){i(e)}).bind(H[2],function(e){r(e)}),P.length&&P.each(function(){e(this).bind("load",function(){A(this)&&e(this.contentDocument||this.contentWindow.document).bind(H[0],function(e){o(e),i(e)}).bind(H[1],function(e){n(e)}).bind(H[2],function(e){r(e)})})})},E=function(){function o(){return window.getSelection?window.getSelection().toString():document.selection&&"Control"!=document.selection.type?document.selection.createRange().text:0}function n(e,t,o){d.type=o&&i?"stepped":"stepless",d.scrollAmount=10,j(r,e,t,"mcsLinearOut",o?60:null)}var i,r=e(this),l=r.data(a),s=l.opt,d=l.sequential,u=a+"_"+l.idx,f=e("#mCSB_"+l.idx+"_container"),h=f.parent();f.bind("mousedown."+u,function(){t||i||(i=1,c=!0)}).add(document).bind("mousemove."+u,function(e){if(!t&&i&&o()){var a=f.offset(),r=O(e)[0]-a.top+f[0].offsetTop,c=O(e)[1]-a.left+f[0].offsetLeft;r>0&&r0&&cr?n("on",38):r>h.height()&&n("on",40)),"y"!==s.axis&&l.overflowed[1]&&(0>c?n("on",37):c>h.width()&&n("on",39)))}}).bind("mouseup."+u+" dragend."+u,function(){t||(i&&(i=0,n("off",null)),c=!1)})},W=function(){function t(t,a){if(Q(o),!z(o,t.target)){var r="auto"!==i.mouseWheel.deltaFactor?parseInt(i.mouseWheel.deltaFactor):s&&t.deltaFactor<100?100:t.deltaFactor||100,d=i.scrollInertia;if("x"===i.axis||"x"===i.mouseWheel.axis)var u="x",f=[Math.round(r*n.scrollRatio.x),parseInt(i.mouseWheel.scrollAmount)],h="auto"!==i.mouseWheel.scrollAmount?f[1]:f[0]>=l.width()?.9*l.width():f[0],m=Math.abs(e("#mCSB_"+n.idx+"_container")[0].offsetLeft),p=c[1][0].offsetLeft,g=c[1].parent().width()-c[1].width(),v="y"===i.mouseWheel.axis?t.deltaY||a:t.deltaX;else var u="y",f=[Math.round(r*n.scrollRatio.y),parseInt(i.mouseWheel.scrollAmount)],h="auto"!==i.mouseWheel.scrollAmount?f[1]:f[0]>=l.height()?.9*l.height():f[0],m=Math.abs(e("#mCSB_"+n.idx+"_container")[0].offsetTop),p=c[0][0].offsetTop,g=c[0].parent().height()-c[0].height(),v=t.deltaY||a;"y"===u&&!n.overflowed[0]||"x"===u&&!n.overflowed[1]||((i.mouseWheel.invert||t.webkitDirectionInvertedFromDevice)&&(v=-v),i.mouseWheel.normalizeDelta&&(v=0>v?-1:1),(v>0&&0!==p||0>v&&p!==g||i.mouseWheel.preventDefault)&&(t.stopImmediatePropagation(),t.preventDefault()),t.deltaFactor<5&&!i.mouseWheel.normalizeDelta&&(h=t.deltaFactor,d=17),G(o,(m-v*h).toString(),{dir:u,dur:d}))}}if(e(this).data(a)){var o=e(this),n=o.data(a),i=n.opt,r=a+"_"+n.idx,l=e("#mCSB_"+n.idx),c=[e("#mCSB_"+n.idx+"_dragger_vertical"),e("#mCSB_"+n.idx+"_dragger_horizontal")],d=e("#mCSB_"+n.idx+"_container").find("iframe");d.length&&d.each(function(){e(this).bind("load",function(){A(this)&&e(this.contentDocument||this.contentWindow.document).bind("mousewheel."+r,function(e,o){t(e,o)})})}),l.bind("mousewheel."+r,function(e,o){t(e,o)})}},R=new Object,A=function(t){var o=!1,a=!1,n=null;if(void 0===t?a="#empty":void 0!==e(t).attr("id")&&(a=e(t).attr("id")),a!==!1&&void 0!==R[a])return R[a];if(t){try{var i=t.contentDocument||t.contentWindow.document;n=i.body.innerHTML}catch(r){}o=null!==n}else{try{var i=top.document;n=i.body.innerHTML}catch(r){}o=null!==n}return a!==!1&&(R[a]=o),o},L=function(e){var t=this.find("iframe");if(t.length){var o=e?"auto":"none";t.css("pointer-events",o)}},z=function(t,o){var n=o.nodeName.toLowerCase(),i=t.data(a).opt.mouseWheel.disableOver,r=["select","textarea"];return e.inArray(n,i)>-1&&!(e.inArray(n,r)>-1&&!e(o).is(":focus"))},P=function(){var t,o=e(this),n=o.data(a),i=a+"_"+n.idx,r=e("#mCSB_"+n.idx+"_container"),l=r.parent(),s=e(".mCSB_"+n.idx+"_scrollbar ."+d[12]);s.bind("mousedown."+i+" touchstart."+i+" pointerdown."+i+" MSPointerDown."+i,function(o){c=!0,e(o.target).hasClass("mCSB_dragger")||(t=1)}).bind("touchend."+i+" pointerup."+i+" MSPointerUp."+i,function(){c=!1}).bind("click."+i,function(a){if(t&&(t=0,e(a.target).hasClass(d[12])||e(a.target).hasClass("mCSB_draggerRail"))){Q(o);var i=e(this),s=i.find(".mCSB_dragger");if(i.parent(".mCSB_scrollTools_horizontal").length>0){if(!n.overflowed[1])return;var c="x",u=a.pageX>s.offset().left?-1:1,f=Math.abs(r[0].offsetLeft)-u*(.9*l.width())}else{if(!n.overflowed[0])return;var c="y",u=a.pageY>s.offset().top?-1:1,f=Math.abs(r[0].offsetTop)-u*(.9*l.height())}G(o,f.toString(),{dir:c,scrollEasing:"mcsEaseInOut"})}})},H=function(){var t=e(this),o=t.data(a),n=o.opt,i=a+"_"+o.idx,r=e("#mCSB_"+o.idx+"_container"),l=r.parent();r.bind("focusin."+i,function(){var o=e(document.activeElement),a=r.find(".mCustomScrollBox").length,i=0;o.is(n.advanced.autoScrollOnFocus)&&(Q(t),clearTimeout(t[0]._focusTimeout),t[0]._focusTimer=a?(i+17)*a:0,t[0]._focusTimeout=setTimeout(function(){var e=[ae(o)[0],ae(o)[1]],a=[r[0].offsetTop,r[0].offsetLeft],s=[a[0]+e[0]>=0&&a[0]+e[0]=0&&a[0]+e[1]a");s.bind("contextmenu."+r,function(e){e.preventDefault()}).bind("mousedown."+r+" touchstart."+r+" pointerdown."+r+" MSPointerDown."+r+" mouseup."+r+" touchend."+r+" pointerup."+r+" MSPointerUp."+r+" mouseout."+r+" pointerout."+r+" MSPointerOut."+r+" click."+r,function(a){function r(e,o){i.scrollAmount=n.scrollButtons.scrollAmount,j(t,e,o)}if(a.preventDefault(),ee(a)){var l=e(this).attr("class");switch(i.type=n.scrollButtons.scrollType,a.type){case"mousedown":case"touchstart":case"pointerdown":case"MSPointerDown":if("stepped"===i.type)return;c=!0,o.tweenRunning=!1,r("on",l);break;case"mouseup":case"touchend":case"pointerup":case"MSPointerUp":case"mouseout":case"pointerout":case"MSPointerOut":if("stepped"===i.type)return;c=!1,i.dir&&r("off",l);break;case"click":if("stepped"!==i.type||o.tweenRunning)return;r("on",l)}}})},q=function(){function t(t){function a(e,t){r.type=i.keyboard.scrollType,r.scrollAmount=i.keyboard.scrollAmount,"stepped"===r.type&&n.tweenRunning||j(o,e,t)}switch(t.type){case"blur":n.tweenRunning&&r.dir&&a("off",null);break;case"keydown":case"keyup":var l=t.keyCode?t.keyCode:t.which,s="on";if("x"!==i.axis&&(38===l||40===l)||"y"!==i.axis&&(37===l||39===l)){if((38===l||40===l)&&!n.overflowed[0]||(37===l||39===l)&&!n.overflowed[1])return;"keyup"===t.type&&(s="off"),e(document.activeElement).is(u)||(t.preventDefault(),t.stopImmediatePropagation(),a(s,l))}else if(33===l||34===l){if((n.overflowed[0]||n.overflowed[1])&&(t.preventDefault(),t.stopImmediatePropagation()),"keyup"===t.type){Q(o);var f=34===l?-1:1;if("x"===i.axis||"yx"===i.axis&&n.overflowed[1]&&!n.overflowed[0])var h="x",m=Math.abs(c[0].offsetLeft)-f*(.9*d.width());else var h="y",m=Math.abs(c[0].offsetTop)-f*(.9*d.height());G(o,m.toString(),{dir:h,scrollEasing:"mcsEaseInOut"})}}else if((35===l||36===l)&&!e(document.activeElement).is(u)&&((n.overflowed[0]||n.overflowed[1])&&(t.preventDefault(),t.stopImmediatePropagation()),"keyup"===t.type)){if("x"===i.axis||"yx"===i.axis&&n.overflowed[1]&&!n.overflowed[0])var h="x",m=35===l?Math.abs(d.width()-c.outerWidth(!1)):0;else var h="y",m=35===l?Math.abs(d.height()-c.outerHeight(!1)):0;G(o,m.toString(),{dir:h,scrollEasing:"mcsEaseInOut"})}}}var o=e(this),n=o.data(a),i=n.opt,r=n.sequential,l=a+"_"+n.idx,s=e("#mCSB_"+n.idx),c=e("#mCSB_"+n.idx+"_container"),d=c.parent(),u="input,textarea,select,datalist,keygen,[contenteditable='true']",f=c.find("iframe"),h=["blur."+l+" keydown."+l+" keyup."+l];f.length&&f.each(function(){e(this).bind("load",function(){A(this)&&e(this.contentDocument||this.contentWindow.document).bind(h[0],function(e){t(e)})})}),s.attr("tabindex","0").bind(h[0],function(e){t(e)})},j=function(t,o,n,i,r){function l(e){u.snapAmount&&(f.scrollAmount=u.snapAmount instanceof Array?"x"===f.dir[0]?u.snapAmount[1]:u.snapAmount[0]:u.snapAmount);var o="stepped"!==f.type,a=r?r:e?o?p/1.5:g:1e3/60,n=e?o?7.5:40:2.5,s=[Math.abs(h[0].offsetTop),Math.abs(h[0].offsetLeft)],d=[c.scrollRatio.y>10?10:c.scrollRatio.y,c.scrollRatio.x>10?10:c.scrollRatio.x],m="x"===f.dir[0]?s[1]+f.dir[1]*(d[1]*n):s[0]+f.dir[1]*(d[0]*n),v="x"===f.dir[0]?s[1]+f.dir[1]*parseInt(f.scrollAmount):s[0]+f.dir[1]*parseInt(f.scrollAmount),x="auto"!==f.scrollAmount?v:m,_=i?i:e?o?"mcsLinearOut":"mcsEaseInOut":"mcsLinear",w=!!e;return e&&17>a&&(x="x"===f.dir[0]?s[1]:s[0]),G(t,x.toString(),{dir:f.dir[0],scrollEasing:_,dur:a,onComplete:w}),e?void(f.dir=!1):(clearTimeout(f.step),void(f.step=setTimeout(function(){l()},a)))}function s(){clearTimeout(f.step),$(f,"step"),Q(t)}var c=t.data(a),u=c.opt,f=c.sequential,h=e("#mCSB_"+c.idx+"_container"),m="stepped"===f.type,p=u.scrollInertia<26?26:u.scrollInertia,g=u.scrollInertia<1?17:u.scrollInertia;switch(o){case"on":if(f.dir=[n===d[16]||n===d[15]||39===n||37===n?"x":"y",n===d[13]||n===d[15]||38===n||37===n?-1:1],Q(t),oe(n)&&"stepped"===f.type)return;l(m);break;case"off":s(),(m||c.tweenRunning&&f.dir)&&l(!0)}},Y=function(t){var o=e(this).data(a).opt,n=[];return"function"==typeof t&&(t=t()),t instanceof Array?n=t.length>1?[t[0],t[1]]:"x"===o.axis?[null,t[0]]:[t[0],null]:(n[0]=t.y?t.y:t.x||"x"===o.axis?null:t,n[1]=t.x?t.x:t.y||"y"===o.axis?null:t),"function"==typeof n[0]&&(n[0]=n[0]()),"function"==typeof n[1]&&(n[1]=n[1]()),n},X=function(t,o){if(null!=t&&"undefined"!=typeof t){var n=e(this),i=n.data(a),r=i.opt,l=e("#mCSB_"+i.idx+"_container"),s=l.parent(),c=typeof t;o||(o="x"===r.axis?"x":"y");var d="x"===o?l.outerWidth(!1)-s.width():l.outerHeight(!1)-s.height(),f="x"===o?l[0].offsetLeft:l[0].offsetTop,h="x"===o?"left":"top";switch(c){case"function":return t();case"object":var m=t.jquery?t:e(t);if(!m.length)return;return"x"===o?ae(m)[1]:ae(m)[0];case"string":case"number":if(oe(t))return Math.abs(t);if(-1!==t.indexOf("%"))return Math.abs(d*parseInt(t)/100);if(-1!==t.indexOf("-="))return Math.abs(f-parseInt(t.split("-=")[1]));if(-1!==t.indexOf("+=")){var p=f+parseInt(t.split("+=")[1]);return p>=0?0:Math.abs(p)}if(-1!==t.indexOf("px")&&oe(t.split("px")[0]))return Math.abs(t.split("px")[0]);if("top"===t||"left"===t)return 0;if("bottom"===t)return Math.abs(s.height()-l.outerHeight(!1));if("right"===t)return Math.abs(s.width()-l.outerWidth(!1));if("first"===t||"last"===t){var m=l.find(":"+t);return"x"===o?ae(m)[1]:ae(m)[0]}return e(t).length?"x"===o?ae(e(t))[1]:ae(e(t))[0]:(l.css(h,t),void u.update.call(null,n[0]))}}},N=function(t){function o(){return clearTimeout(f[0].autoUpdate),0===l.parents("html").length?void(l=null):void(f[0].autoUpdate=setTimeout(function(){return c.advanced.updateOnSelectorChange&&(s.poll.change.n=i(),s.poll.change.n!==s.poll.change.o)?(s.poll.change.o=s.poll.change.n,void r(3)):c.advanced.updateOnContentResize&&(s.poll.size.n=l[0].scrollHeight+l[0].scrollWidth+f[0].offsetHeight+l[0].offsetHeight+l[0].offsetWidth,s.poll.size.n!==s.poll.size.o)?(s.poll.size.o=s.poll.size.n,void r(1)):!c.advanced.updateOnImageLoad||"auto"===c.advanced.updateOnImageLoad&&"y"===c.axis||(s.poll.img.n=f.find("img").length,s.poll.img.n===s.poll.img.o)?void((c.advanced.updateOnSelectorChange||c.advanced.updateOnContentResize||c.advanced.updateOnImageLoad)&&o()):(s.poll.img.o=s.poll.img.n,void f.find("img").each(function(){n(this)}))},c.advanced.autoUpdateTimeout))}function n(t){function o(e,t){return function(){ -return t.apply(e,arguments)}}function a(){this.onload=null,e(t).addClass(d[2]),r(2)}if(e(t).hasClass(d[2]))return void r();var n=new Image;n.onload=o(n,a),n.src=t.src}function i(){c.advanced.updateOnSelectorChange===!0&&(c.advanced.updateOnSelectorChange="*");var e=0,t=f.find(c.advanced.updateOnSelectorChange);return c.advanced.updateOnSelectorChange&&t.length>0&&t.each(function(){e+=this.offsetHeight+this.offsetWidth}),e}function r(e){clearTimeout(f[0].autoUpdate),u.update.call(null,l[0],e)}var l=e(this),s=l.data(a),c=s.opt,f=e("#mCSB_"+s.idx+"_container");return t?(clearTimeout(f[0].autoUpdate),void $(f[0],"autoUpdate")):void o()},V=function(e,t,o){return Math.round(e/t)*t-o},Q=function(t){var o=t.data(a),n=e("#mCSB_"+o.idx+"_container,#mCSB_"+o.idx+"_container_wrapper,#mCSB_"+o.idx+"_dragger_vertical,#mCSB_"+o.idx+"_dragger_horizontal");n.each(function(){Z.call(this)})},G=function(t,o,n){function i(e){return s&&c.callbacks[e]&&"function"==typeof c.callbacks[e]}function r(){return[c.callbacks.alwaysTriggerOffsets||w>=S[0]+y,c.callbacks.alwaysTriggerOffsets||-B>=w]}function l(){var e=[h[0].offsetTop,h[0].offsetLeft],o=[x[0].offsetTop,x[0].offsetLeft],a=[h.outerHeight(!1),h.outerWidth(!1)],i=[f.height(),f.width()];t[0].mcs={content:h,top:e[0],left:e[1],draggerTop:o[0],draggerLeft:o[1],topPct:Math.round(100*Math.abs(e[0])/(Math.abs(a[0])-i[0])),leftPct:Math.round(100*Math.abs(e[1])/(Math.abs(a[1])-i[1])),direction:n.dir}}var s=t.data(a),c=s.opt,d={trigger:"internal",dir:"y",scrollEasing:"mcsEaseOut",drag:!1,dur:c.scrollInertia,overwrite:"all",callbacks:!0,onStart:!0,onUpdate:!0,onComplete:!0},n=e.extend(d,n),u=[n.dur,n.drag?0:n.dur],f=e("#mCSB_"+s.idx),h=e("#mCSB_"+s.idx+"_container"),m=h.parent(),p=c.callbacks.onTotalScrollOffset?Y.call(t,c.callbacks.onTotalScrollOffset):[0,0],g=c.callbacks.onTotalScrollBackOffset?Y.call(t,c.callbacks.onTotalScrollBackOffset):[0,0];if(s.trigger=n.trigger,0===m.scrollTop()&&0===m.scrollLeft()||(e(".mCSB_"+s.idx+"_scrollbar").css("visibility","visible"),m.scrollTop(0).scrollLeft(0)),"_resetY"!==o||s.contentReset.y||(i("onOverflowYNone")&&c.callbacks.onOverflowYNone.call(t[0]),s.contentReset.y=1),"_resetX"!==o||s.contentReset.x||(i("onOverflowXNone")&&c.callbacks.onOverflowXNone.call(t[0]),s.contentReset.x=1),"_resetY"!==o&&"_resetX"!==o){if(!s.contentReset.y&&t[0].mcs||!s.overflowed[0]||(i("onOverflowY")&&c.callbacks.onOverflowY.call(t[0]),s.contentReset.x=null),!s.contentReset.x&&t[0].mcs||!s.overflowed[1]||(i("onOverflowX")&&c.callbacks.onOverflowX.call(t[0]),s.contentReset.x=null),c.snapAmount){var v=c.snapAmount instanceof Array?"x"===n.dir?c.snapAmount[1]:c.snapAmount[0]:c.snapAmount;o=V(o,v,c.snapOffset)}switch(n.dir){case"x":var x=e("#mCSB_"+s.idx+"_dragger_horizontal"),_="left",w=h[0].offsetLeft,S=[f.width()-h.outerWidth(!1),x.parent().width()-x.width()],b=[o,0===o?0:o/s.scrollRatio.x],y=p[1],B=g[1],T=y>0?y/s.scrollRatio.x:0,k=B>0?B/s.scrollRatio.x:0;break;case"y":var x=e("#mCSB_"+s.idx+"_dragger_vertical"),_="top",w=h[0].offsetTop,S=[f.height()-h.outerHeight(!1),x.parent().height()-x.height()],b=[o,0===o?0:o/s.scrollRatio.y],y=p[0],B=g[0],T=y>0?y/s.scrollRatio.y:0,k=B>0?B/s.scrollRatio.y:0}b[1]<0||0===b[0]&&0===b[1]?b=[0,0]:b[1]>=S[1]?b=[S[0],S[1]]:b[0]=-b[0],t[0].mcs||(l(),i("onInit")&&c.callbacks.onInit.call(t[0])),clearTimeout(h[0].onCompleteTimeout),J(x[0],_,Math.round(b[1]),u[1],n.scrollEasing),!s.tweenRunning&&(0===w&&b[0]>=0||w===S[0]&&b[0]<=S[0])||J(h[0],_,Math.round(b[0]),u[0],n.scrollEasing,n.overwrite,{onStart:function(){n.callbacks&&n.onStart&&!s.tweenRunning&&(i("onScrollStart")&&(l(),c.callbacks.onScrollStart.call(t[0])),s.tweenRunning=!0,C(x),s.cbOffsets=r())},onUpdate:function(){n.callbacks&&n.onUpdate&&i("whileScrolling")&&(l(),c.callbacks.whileScrolling.call(t[0]))},onComplete:function(){if(n.callbacks&&n.onComplete){"yx"===c.axis&&clearTimeout(h[0].onCompleteTimeout);var e=h[0].idleTimer||0;h[0].onCompleteTimeout=setTimeout(function(){i("onScroll")&&(l(),c.callbacks.onScroll.call(t[0])),i("onTotalScroll")&&b[1]>=S[1]-T&&s.cbOffsets[0]&&(l(),c.callbacks.onTotalScroll.call(t[0])),i("onTotalScrollBack")&&b[1]<=k&&s.cbOffsets[1]&&(l(),c.callbacks.onTotalScrollBack.call(t[0])),s.tweenRunning=!1,h[0].idleTimer=0,C(x,"hide")},e)}}})}},J=function(e,t,o,a,n,i,r){function l(){S.stop||(x||m.call(),x=K()-v,s(),x>=S.time&&(S.time=x>S.time?x+f-(x-S.time):x+f-1,S.time0?(S.currVal=u(S.time,_,b,a,n),w[t]=Math.round(S.currVal)+"px"):w[t]=o+"px",p.call()}function c(){f=1e3/60,S.time=x+f,h=window.requestAnimationFrame?window.requestAnimationFrame:function(e){return s(),setTimeout(e,.01)},S.id=h(l)}function d(){null!=S.id&&(window.requestAnimationFrame?window.cancelAnimationFrame(S.id):clearTimeout(S.id),S.id=null)}function u(e,t,o,a,n){switch(n){case"linear":case"mcsLinear":return o*e/a+t;case"mcsLinearOut":return e/=a,e--,o*Math.sqrt(1-e*e)+t;case"easeInOutSmooth":return e/=a/2,1>e?o/2*e*e+t:(e--,-o/2*(e*(e-2)-1)+t);case"easeInOutStrong":return e/=a/2,1>e?o/2*Math.pow(2,10*(e-1))+t:(e--,o/2*(-Math.pow(2,-10*e)+2)+t);case"easeInOut":case"mcsEaseInOut":return e/=a/2,1>e?o/2*e*e*e+t:(e-=2,o/2*(e*e*e+2)+t);case"easeOutSmooth":return e/=a,e--,-o*(e*e*e*e-1)+t;case"easeOutStrong":return o*(-Math.pow(2,-10*e/a)+1)+t;case"easeOut":case"mcsEaseOut":default:var i=(e/=a)*e,r=i*e;return t+o*(.499999999999997*r*i+-2.5*i*i+5.5*r+-6.5*i+4*e)}}e._mTween||(e._mTween={top:{},left:{}});var f,h,r=r||{},m=r.onStart||function(){},p=r.onUpdate||function(){},g=r.onComplete||function(){},v=K(),x=0,_=e.offsetTop,w=e.style,S=e._mTween[t];"left"===t&&(_=e.offsetLeft);var b=o-_;S.stop=0,"none"!==i&&d(),c()},K=function(){return window.performance&&window.performance.now?window.performance.now():window.performance&&window.performance.webkitNow?window.performance.webkitNow():Date.now?Date.now():(new Date).getTime()},Z=function(){var e=this;e._mTween||(e._mTween={top:{},left:{}});for(var t=["top","left"],o=0;o=0&&a[0]+ae(n)[0]=0&&a[1]+ae(n)[1]=0&&r[1]-i[1]*l[1][0]<0&&r[1]+n[1]-i[1]*l[1][1]>=0},mcsOverflow:e.expr[":"].mcsOverflow||function(t){var o=e(t).data(a);if(o)return o.overflowed[0]||o.overflowed[1]}})})})}); \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.css b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.css deleted file mode 100644 index 8f013dafa3..0000000000 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.css +++ /dev/null @@ -1,1267 +0,0 @@ -/* -== malihu jquery custom scrollbar plugin == -Plugin URI: http://manos.malihu.gr/jquery-custom-content-scroller -*/ - - - -/* -CONTENTS: - 1. BASIC STYLE - Plugin's basic/essential CSS properties (normally, should not be edited). - 2. VERTICAL SCROLLBAR - Positioning and dimensions of vertical scrollbar. - 3. HORIZONTAL SCROLLBAR - Positioning and dimensions of horizontal scrollbar. - 4. VERTICAL AND HORIZONTAL SCROLLBARS - Positioning and dimensions of 2-axis scrollbars. - 5. TRANSITIONS - CSS3 transitions for hover events, auto-expanded and auto-hidden scrollbars. - 6. SCROLLBAR COLORS, OPACITY AND BACKGROUNDS - 6.1 THEMES - Scrollbar colors, opacity, dimensions, backgrounds etc. via ready-to-use themes. -*/ - - - -/* ------------------------------------------------------------------------------------------------------------------------- -1. BASIC STYLE ------------------------------------------------------------------------------------------------------------------------- -*/ - - .mCustomScrollbar{ -ms-touch-action: pinch-zoom; touch-action: pinch-zoom; /* direct pointer events to js */ } - .mCustomScrollbar.mCS_no_scrollbar, .mCustomScrollbar.mCS_touch_action{ -ms-touch-action: auto; touch-action: auto; } - - .mCustomScrollBox{ /* contains plugin's markup */ - position: relative; - overflow: hidden; - height: 100%; - max-width: 100%; - outline: none; - direction: ltr; - } - - .mCSB_container{ /* contains the original content */ - overflow: hidden; - width: auto; - height: auto; - } - - - -/* ------------------------------------------------------------------------------------------------------------------------- -2. VERTICAL SCROLLBAR -y-axis ------------------------------------------------------------------------------------------------------------------------- -*/ - - .mCSB_inside > .mCSB_container{ margin-right: 30px; } - - .mCSB_container.mCS_no_scrollbar_y.mCS_y_hidden{ margin-right: 0; } /* non-visible scrollbar */ - - .mCS-dir-rtl > .mCSB_inside > .mCSB_container{ /* RTL direction/left-side scrollbar */ - margin-right: 0; - margin-left: 30px; - } - - .mCS-dir-rtl > .mCSB_inside > .mCSB_container.mCS_no_scrollbar_y.mCS_y_hidden{ margin-left: 0; } /* RTL direction/left-side scrollbar */ - - .mCSB_scrollTools{ /* contains scrollbar markup (draggable element, dragger rail, buttons etc.) */ - position: absolute; - width: 16px; - height: auto; - left: auto; - top: 0; - right: 0; - bottom: 0; - } - - .mCSB_outside + .mCSB_scrollTools{ right: -26px; } /* scrollbar position: outside */ - - .mCS-dir-rtl > .mCSB_inside > .mCSB_scrollTools, - .mCS-dir-rtl > .mCSB_outside + .mCSB_scrollTools{ /* RTL direction/left-side scrollbar */ - right: auto; - left: 0; - } - - .mCS-dir-rtl > .mCSB_outside + .mCSB_scrollTools{ left: -26px; } /* RTL direction/left-side scrollbar (scrollbar position: outside) */ - - .mCSB_scrollTools .mCSB_draggerContainer{ /* contains the draggable element and dragger rail markup */ - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - height: auto; - } - - .mCSB_scrollTools a + .mCSB_draggerContainer{ margin: 20px 0; } - - .mCSB_scrollTools .mCSB_draggerRail{ - width: 2px; - height: 100%; - margin: 0 auto; - -webkit-border-radius: 16px; -moz-border-radius: 16px; border-radius: 16px; - } - - .mCSB_scrollTools .mCSB_dragger{ /* the draggable element */ - cursor: pointer; - width: 100%; - height: 30px; /* minimum dragger height */ - z-index: 1; - } - - .mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ /* the dragger element */ - position: relative; - width: 4px; - height: 100%; - margin: 0 auto; - -webkit-border-radius: 16px; -moz-border-radius: 16px; border-radius: 16px; - text-align: center; - } - - .mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar, - .mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar{ width: 12px; /* auto-expanded scrollbar */ } - - .mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{ width: 8px; /* auto-expanded scrollbar */ } - - .mCSB_scrollTools .mCSB_buttonUp, - .mCSB_scrollTools .mCSB_buttonDown{ - display: block; - position: absolute; - height: 20px; - width: 100%; - overflow: hidden; - margin: 0 auto; - cursor: pointer; - } - - .mCSB_scrollTools .mCSB_buttonDown{ bottom: 0; } - - - -/* ------------------------------------------------------------------------------------------------------------------------- -3. HORIZONTAL SCROLLBAR -x-axis ------------------------------------------------------------------------------------------------------------------------- -*/ - - .mCSB_horizontal.mCSB_inside > .mCSB_container{ - margin-right: 0; - margin-bottom: 30px; - } - - .mCSB_horizontal.mCSB_outside > .mCSB_container{ min-height: 100%; } - - .mCSB_horizontal > .mCSB_container.mCS_no_scrollbar_x.mCS_x_hidden{ margin-bottom: 0; } /* non-visible scrollbar */ - - .mCSB_scrollTools.mCSB_scrollTools_horizontal{ - width: auto; - height: 16px; - top: auto; - right: 0; - bottom: 0; - left: 0; - } - - .mCustomScrollBox + .mCSB_scrollTools.mCSB_scrollTools_horizontal, - .mCustomScrollBox + .mCSB_scrollTools + .mCSB_scrollTools.mCSB_scrollTools_horizontal{ bottom: -26px; } /* scrollbar position: outside */ - - .mCSB_scrollTools.mCSB_scrollTools_horizontal a + .mCSB_draggerContainer{ margin: 0 20px; } - - .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_draggerRail{ - width: 100%; - height: 2px; - margin: 7px 0; - } - - .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_dragger{ - width: 30px; /* minimum dragger width */ - height: 100%; - left: 0; - } - - .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - width: 100%; - height: 4px; - margin: 6px auto; - } - - .mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar, - .mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar{ - height: 12px; /* auto-expanded scrollbar */ - margin: 2px auto; - } - - .mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{ - height: 8px; /* auto-expanded scrollbar */ - margin: 4px 0; - } - - .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_buttonLeft, - .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_buttonRight{ - display: block; - position: absolute; - width: 20px; - height: 100%; - overflow: hidden; - margin: 0 auto; - cursor: pointer; - } - - .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_buttonLeft{ left: 0; } - - .mCSB_scrollTools.mCSB_scrollTools_horizontal .mCSB_buttonRight{ right: 0; } - - - -/* ------------------------------------------------------------------------------------------------------------------------- -4. VERTICAL AND HORIZONTAL SCROLLBARS -yx-axis ------------------------------------------------------------------------------------------------------------------------- -*/ - - .mCSB_container_wrapper{ - position: absolute; - height: auto; - width: auto; - overflow: hidden; - top: 0; - left: 0; - right: 0; - bottom: 0; - margin-right: 30px; - margin-bottom: 30px; - } - - .mCSB_container_wrapper > .mCSB_container{ - padding-right: 30px; - padding-bottom: 30px; - -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; - } - - .mCSB_vertical_horizontal > .mCSB_scrollTools.mCSB_scrollTools_vertical{ bottom: 20px; } - - .mCSB_vertical_horizontal > .mCSB_scrollTools.mCSB_scrollTools_horizontal{ right: 20px; } - - /* non-visible horizontal scrollbar */ - .mCSB_container_wrapper.mCS_no_scrollbar_x.mCS_x_hidden + .mCSB_scrollTools.mCSB_scrollTools_vertical{ bottom: 0; } - - /* non-visible vertical scrollbar/RTL direction/left-side scrollbar */ - .mCSB_container_wrapper.mCS_no_scrollbar_y.mCS_y_hidden + .mCSB_scrollTools ~ .mCSB_scrollTools.mCSB_scrollTools_horizontal, - .mCS-dir-rtl > .mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside > .mCSB_scrollTools.mCSB_scrollTools_horizontal{ right: 0; } - - /* RTL direction/left-side scrollbar */ - .mCS-dir-rtl > .mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside > .mCSB_scrollTools.mCSB_scrollTools_horizontal{ left: 20px; } - - /* non-visible scrollbar/RTL direction/left-side scrollbar */ - .mCS-dir-rtl > .mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside > .mCSB_container_wrapper.mCS_no_scrollbar_y.mCS_y_hidden + .mCSB_scrollTools ~ .mCSB_scrollTools.mCSB_scrollTools_horizontal{ left: 0; } - - .mCS-dir-rtl > .mCSB_inside > .mCSB_container_wrapper{ /* RTL direction/left-side scrollbar */ - margin-right: 0; - margin-left: 30px; - } - - .mCSB_container_wrapper.mCS_no_scrollbar_y.mCS_y_hidden > .mCSB_container{ padding-right: 0; } - - .mCSB_container_wrapper.mCS_no_scrollbar_x.mCS_x_hidden > .mCSB_container{ padding-bottom: 0; } - - .mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside > .mCSB_container_wrapper.mCS_no_scrollbar_y.mCS_y_hidden{ - margin-right: 0; /* non-visible scrollbar */ - margin-left: 0; - } - - /* non-visible horizontal scrollbar */ - .mCustomScrollBox.mCSB_vertical_horizontal.mCSB_inside > .mCSB_container_wrapper.mCS_no_scrollbar_x.mCS_x_hidden{ margin-bottom: 0; } - - - -/* ------------------------------------------------------------------------------------------------------------------------- -5. TRANSITIONS ------------------------------------------------------------------------------------------------------------------------- -*/ - - .mCSB_scrollTools, - .mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCSB_scrollTools .mCSB_buttonUp, - .mCSB_scrollTools .mCSB_buttonDown, - .mCSB_scrollTools .mCSB_buttonLeft, - .mCSB_scrollTools .mCSB_buttonRight{ - -webkit-transition: opacity .2s ease-in-out, background-color .2s ease-in-out; - -moz-transition: opacity .2s ease-in-out, background-color .2s ease-in-out; - -o-transition: opacity .2s ease-in-out, background-color .2s ease-in-out; - transition: opacity .2s ease-in-out, background-color .2s ease-in-out; - } - - .mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger_bar, /* auto-expanded scrollbar */ - .mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerRail, - .mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger_bar, - .mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerRail{ - -webkit-transition: width .2s ease-out .2s, height .2s ease-out .2s, - margin-left .2s ease-out .2s, margin-right .2s ease-out .2s, - margin-top .2s ease-out .2s, margin-bottom .2s ease-out .2s, - opacity .2s ease-in-out, background-color .2s ease-in-out; - -moz-transition: width .2s ease-out .2s, height .2s ease-out .2s, - margin-left .2s ease-out .2s, margin-right .2s ease-out .2s, - margin-top .2s ease-out .2s, margin-bottom .2s ease-out .2s, - opacity .2s ease-in-out, background-color .2s ease-in-out; - -o-transition: width .2s ease-out .2s, height .2s ease-out .2s, - margin-left .2s ease-out .2s, margin-right .2s ease-out .2s, - margin-top .2s ease-out .2s, margin-bottom .2s ease-out .2s, - opacity .2s ease-in-out, background-color .2s ease-in-out; - transition: width .2s ease-out .2s, height .2s ease-out .2s, - margin-left .2s ease-out .2s, margin-right .2s ease-out .2s, - margin-top .2s ease-out .2s, margin-bottom .2s ease-out .2s, - opacity .2s ease-in-out, background-color .2s ease-in-out; - } - - - -/* ------------------------------------------------------------------------------------------------------------------------- -6. SCROLLBAR COLORS, OPACITY AND BACKGROUNDS ------------------------------------------------------------------------------------------------------------------------- -*/ - - /* - ---------------------------------------- - 6.1 THEMES - ---------------------------------------- - */ - - /* default theme ("light") */ - - .mCSB_scrollTools{ opacity: 0.75; filter: "alpha(opacity=75)"; -ms-filter: "alpha(opacity=75)"; } - - .mCS-autoHide > .mCustomScrollBox > .mCSB_scrollTools, - .mCS-autoHide > .mCustomScrollBox ~ .mCSB_scrollTools{ opacity: 0; filter: "alpha(opacity=0)"; -ms-filter: "alpha(opacity=0)"; } - - .mCustomScrollbar > .mCustomScrollBox > .mCSB_scrollTools.mCSB_scrollTools_onDrag, - .mCustomScrollbar > .mCustomScrollBox ~ .mCSB_scrollTools.mCSB_scrollTools_onDrag, - .mCustomScrollBox:hover > .mCSB_scrollTools, - .mCustomScrollBox:hover ~ .mCSB_scrollTools, - .mCS-autoHide:hover > .mCustomScrollBox > .mCSB_scrollTools, - .mCS-autoHide:hover > .mCustomScrollBox ~ .mCSB_scrollTools{ opacity: 1; filter: "alpha(opacity=100)"; -ms-filter: "alpha(opacity=100)"; } - - .mCSB_scrollTools .mCSB_draggerRail{ - background-color: #000; background-color: rgba(0,0,0,0.4); - filter: "alpha(opacity=40)"; -ms-filter: "alpha(opacity=40)"; - } - - .mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - background-color: #fff; background-color: rgba(255,255,255,0.75); - filter: "alpha(opacity=75)"; -ms-filter: "alpha(opacity=75)"; - } - - .mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ - background-color: #fff; background-color: rgba(255,255,255,0.85); - filter: "alpha(opacity=85)"; -ms-filter: "alpha(opacity=85)"; - } - .mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ - background-color: #fff; background-color: rgba(255,255,255,0.9); - filter: "alpha(opacity=90)"; -ms-filter: "alpha(opacity=90)"; - } - - .mCSB_scrollTools .mCSB_buttonUp, - .mCSB_scrollTools .mCSB_buttonDown, - .mCSB_scrollTools .mCSB_buttonLeft, - .mCSB_scrollTools .mCSB_buttonRight{ - background-image: url(mCSB_buttons.png); /* css sprites */ - background-repeat: no-repeat; - opacity: 0.4; filter: "alpha(opacity=40)"; -ms-filter: "alpha(opacity=40)"; - } - - .mCSB_scrollTools .mCSB_buttonUp{ - background-position: 0 0; - /* - sprites locations - light: 0 0, -16px 0, -32px 0, -48px 0, 0 -72px, -16px -72px, -32px -72px - dark: -80px 0, -96px 0, -112px 0, -128px 0, -80px -72px, -96px -72px, -112px -72px - */ - } - - .mCSB_scrollTools .mCSB_buttonDown{ - background-position: 0 -20px; - /* - sprites locations - light: 0 -20px, -16px -20px, -32px -20px, -48px -20px, 0 -92px, -16px -92px, -32px -92px - dark: -80px -20px, -96px -20px, -112px -20px, -128px -20px, -80px -92px, -96px -92px, -112 -92px - */ - } - - .mCSB_scrollTools .mCSB_buttonLeft{ - background-position: 0 -40px; - /* - sprites locations - light: 0 -40px, -20px -40px, -40px -40px, -60px -40px, 0 -112px, -20px -112px, -40px -112px - dark: -80px -40px, -100px -40px, -120px -40px, -140px -40px, -80px -112px, -100px -112px, -120px -112px - */ - } - - .mCSB_scrollTools .mCSB_buttonRight{ - background-position: 0 -56px; - /* - sprites locations - light: 0 -56px, -20px -56px, -40px -56px, -60px -56px, 0 -128px, -20px -128px, -40px -128px - dark: -80px -56px, -100px -56px, -120px -56px, -140px -56px, -80px -128px, -100px -128px, -120px -128px - */ - } - - .mCSB_scrollTools .mCSB_buttonUp:hover, - .mCSB_scrollTools .mCSB_buttonDown:hover, - .mCSB_scrollTools .mCSB_buttonLeft:hover, - .mCSB_scrollTools .mCSB_buttonRight:hover{ opacity: 0.75; filter: "alpha(opacity=75)"; -ms-filter: "alpha(opacity=75)"; } - - .mCSB_scrollTools .mCSB_buttonUp:active, - .mCSB_scrollTools .mCSB_buttonDown:active, - .mCSB_scrollTools .mCSB_buttonLeft:active, - .mCSB_scrollTools .mCSB_buttonRight:active{ opacity: 0.9; filter: "alpha(opacity=90)"; -ms-filter: "alpha(opacity=90)"; } - - - /* theme: "dark" */ - - .mCS-dark.mCSB_scrollTools .mCSB_draggerRail{ background-color: #000; background-color: rgba(0,0,0,0.15); } - - .mCS-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.75); } - - .mCS-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: rgba(0,0,0,0.85); } - - .mCS-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: rgba(0,0,0,0.9); } - - .mCS-dark.mCSB_scrollTools .mCSB_buttonUp{ background-position: -80px 0; } - - .mCS-dark.mCSB_scrollTools .mCSB_buttonDown{ background-position: -80px -20px; } - - .mCS-dark.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -80px -40px; } - - .mCS-dark.mCSB_scrollTools .mCSB_buttonRight{ background-position: -80px -56px; } - - /* ---------------------------------------- */ - - - - /* theme: "light-2", "dark-2" */ - - .mCS-light-2.mCSB_scrollTools .mCSB_draggerRail, - .mCS-dark-2.mCSB_scrollTools .mCSB_draggerRail{ - width: 4px; - background-color: #fff; background-color: rgba(255,255,255,0.1); - -webkit-border-radius: 1px; -moz-border-radius: 1px; border-radius: 1px; - } - - .mCS-light-2.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-dark-2.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - width: 4px; - background-color: #fff; background-color: rgba(255,255,255,0.75); - -webkit-border-radius: 1px; -moz-border-radius: 1px; border-radius: 1px; - } - - .mCS-light-2.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-dark-2.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-light-2.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-dark-2.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - width: 100%; - height: 4px; - margin: 6px auto; - } - - .mCS-light-2.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #fff; background-color: rgba(255,255,255,0.85); } - - .mCS-light-2.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-light-2.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #fff; background-color: rgba(255,255,255,0.9); } - - .mCS-light-2.mCSB_scrollTools .mCSB_buttonUp{ background-position: -32px 0; } - - .mCS-light-2.mCSB_scrollTools .mCSB_buttonDown{ background-position: -32px -20px; } - - .mCS-light-2.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -40px -40px; } - - .mCS-light-2.mCSB_scrollTools .mCSB_buttonRight{ background-position: -40px -56px; } - - - /* theme: "dark-2" */ - - .mCS-dark-2.mCSB_scrollTools .mCSB_draggerRail{ - background-color: #000; background-color: rgba(0,0,0,0.1); - -webkit-border-radius: 1px; -moz-border-radius: 1px; border-radius: 1px; - } - - .mCS-dark-2.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - background-color: #000; background-color: rgba(0,0,0,0.75); - -webkit-border-radius: 1px; -moz-border-radius: 1px; border-radius: 1px; - } - - .mCS-dark-2.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.85); } - - .mCS-dark-2.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-dark-2.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.9); } - - .mCS-dark-2.mCSB_scrollTools .mCSB_buttonUp{ background-position: -112px 0; } - - .mCS-dark-2.mCSB_scrollTools .mCSB_buttonDown{ background-position: -112px -20px; } - - .mCS-dark-2.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -120px -40px; } - - .mCS-dark-2.mCSB_scrollTools .mCSB_buttonRight{ background-position: -120px -56px; } - - /* ---------------------------------------- */ - - - - /* theme: "light-thick", "dark-thick" */ - - .mCS-light-thick.mCSB_scrollTools .mCSB_draggerRail, - .mCS-dark-thick.mCSB_scrollTools .mCSB_draggerRail{ - width: 4px; - background-color: #fff; background-color: rgba(255,255,255,0.1); - -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; - } - - .mCS-light-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-dark-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - width: 6px; - background-color: #fff; background-color: rgba(255,255,255,0.75); - -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; - } - - .mCS-light-thick.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-dark-thick.mCSB_scrollTools_horizontal .mCSB_draggerRail{ - width: 100%; - height: 4px; - margin: 6px 0; - } - - .mCS-light-thick.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-dark-thick.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - width: 100%; - height: 6px; - margin: 5px auto; - } - - .mCS-light-thick.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #fff; background-color: rgba(255,255,255,0.85); } - - .mCS-light-thick.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-light-thick.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #fff; background-color: rgba(255,255,255,0.9); } - - .mCS-light-thick.mCSB_scrollTools .mCSB_buttonUp{ background-position: -16px 0; } - - .mCS-light-thick.mCSB_scrollTools .mCSB_buttonDown{ background-position: -16px -20px; } - - .mCS-light-thick.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -20px -40px; } - - .mCS-light-thick.mCSB_scrollTools .mCSB_buttonRight{ background-position: -20px -56px; } - - - /* theme: "dark-thick" */ - - .mCS-dark-thick.mCSB_scrollTools .mCSB_draggerRail{ - background-color: #000; background-color: rgba(0,0,0,0.1); - -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; - } - - .mCS-dark-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - background-color: #000; background-color: rgba(0,0,0,0.75); - -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; - } - - .mCS-dark-thick.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.85); } - - .mCS-dark-thick.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-dark-thick.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.9); } - - .mCS-dark-thick.mCSB_scrollTools .mCSB_buttonUp{ background-position: -96px 0; } - - .mCS-dark-thick.mCSB_scrollTools .mCSB_buttonDown{ background-position: -96px -20px; } - - .mCS-dark-thick.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -100px -40px; } - - .mCS-dark-thick.mCSB_scrollTools .mCSB_buttonRight{ background-position: -100px -56px; } - - /* ---------------------------------------- */ - - - - /* theme: "light-thin", "dark-thin" */ - - .mCS-light-thin.mCSB_scrollTools .mCSB_draggerRail{ background-color: #fff; background-color: rgba(255,255,255,0.1); } - - .mCS-light-thin.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-dark-thin.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ width: 2px; } - - .mCS-light-thin.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-dark-thin.mCSB_scrollTools_horizontal .mCSB_draggerRail{ width: 100%; } - - .mCS-light-thin.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-dark-thin.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - width: 100%; - height: 2px; - margin: 7px auto; - } - - - /* theme "dark-thin" */ - - .mCS-dark-thin.mCSB_scrollTools .mCSB_draggerRail{ background-color: #000; background-color: rgba(0,0,0,0.15); } - - .mCS-dark-thin.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.75); } - - .mCS-dark-thin.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.85); } - - .mCS-dark-thin.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-dark-thin.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.9); } - - .mCS-dark-thin.mCSB_scrollTools .mCSB_buttonUp{ background-position: -80px 0; } - - .mCS-dark-thin.mCSB_scrollTools .mCSB_buttonDown{ background-position: -80px -20px; } - - .mCS-dark-thin.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -80px -40px; } - - .mCS-dark-thin.mCSB_scrollTools .mCSB_buttonRight{ background-position: -80px -56px; } - - /* ---------------------------------------- */ - - - - /* theme "rounded", "rounded-dark", "rounded-dots", "rounded-dots-dark" */ - - .mCS-rounded.mCSB_scrollTools .mCSB_draggerRail{ background-color: #fff; background-color: rgba(255,255,255,0.15); } - - .mCS-rounded.mCSB_scrollTools .mCSB_dragger, - .mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger, - .mCS-rounded-dots.mCSB_scrollTools .mCSB_dragger, - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger{ height: 14px; } - - .mCS-rounded.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dots.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - width: 14px; - margin: 0 1px; - } - - .mCS-rounded.mCSB_scrollTools_horizontal .mCSB_dragger, - .mCS-rounded-dark.mCSB_scrollTools_horizontal .mCSB_dragger, - .mCS-rounded-dots.mCSB_scrollTools_horizontal .mCSB_dragger, - .mCS-rounded-dots-dark.mCSB_scrollTools_horizontal .mCSB_dragger{ width: 14px; } - - .mCS-rounded.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dots.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dots-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - height: 14px; - margin: 1px 0; - } - - .mCS-rounded.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar, - .mCS-rounded.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dark.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar, - .mCS-rounded-dark.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar{ - width: 16px; /* auto-expanded scrollbar */ - height: 16px; - margin: -1px 0; - } - - .mCS-rounded.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCS-rounded.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail, - .mCS-rounded-dark.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCS-rounded-dark.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{ width: 4px; /* auto-expanded scrollbar */ } - - .mCS-rounded.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar, - .mCS-rounded.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dark.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded .mCSB_dragger_bar, - .mCS-rounded-dark.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_dragger .mCSB_dragger_bar{ - height: 16px; /* auto-expanded scrollbar */ - width: 16px; - margin: 0 -1px; - } - - .mCS-rounded.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCS-rounded.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail, - .mCS-rounded-dark.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCS-rounded-dark.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{ - height: 4px; /* auto-expanded scrollbar */ - margin: 6px 0; - } - - .mCS-rounded.mCSB_scrollTools .mCSB_buttonUp{ background-position: 0 -72px; } - - .mCS-rounded.mCSB_scrollTools .mCSB_buttonDown{ background-position: 0 -92px; } - - .mCS-rounded.mCSB_scrollTools .mCSB_buttonLeft{ background-position: 0 -112px; } - - .mCS-rounded.mCSB_scrollTools .mCSB_buttonRight{ background-position: 0 -128px; } - - - /* theme "rounded-dark", "rounded-dots-dark" */ - - .mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.75); } - - .mCS-rounded-dark.mCSB_scrollTools .mCSB_draggerRail{ background-color: #000; background-color: rgba(0,0,0,0.15); } - - .mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar, - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.85); } - - .mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-rounded-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar, - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.9); } - - .mCS-rounded-dark.mCSB_scrollTools .mCSB_buttonUp{ background-position: -80px -72px; } - - .mCS-rounded-dark.mCSB_scrollTools .mCSB_buttonDown{ background-position: -80px -92px; } - - .mCS-rounded-dark.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -80px -112px; } - - .mCS-rounded-dark.mCSB_scrollTools .mCSB_buttonRight{ background-position: -80px -128px; } - - - /* theme "rounded-dots", "rounded-dots-dark" */ - - .mCS-rounded-dots.mCSB_scrollTools_vertical .mCSB_draggerRail, - .mCS-rounded-dots-dark.mCSB_scrollTools_vertical .mCSB_draggerRail{ width: 4px; } - - .mCS-rounded-dots.mCSB_scrollTools .mCSB_draggerRail, - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_draggerRail, - .mCS-rounded-dots.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-rounded-dots-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail{ - background-color: transparent; - background-position: center; - } - - .mCS-rounded-dots.mCSB_scrollTools .mCSB_draggerRail, - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_draggerRail{ - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAANElEQVQYV2NkIAAYiVbw//9/Y6DiM1ANJoyMjGdBbLgJQAX/kU0DKgDLkaQAvxW4HEvQFwCRcxIJK1XznAAAAABJRU5ErkJggg=="); - background-repeat: repeat-y; - opacity: 0.3; - filter: "alpha(opacity=30)"; -ms-filter: "alpha(opacity=30)"; - } - - .mCS-rounded-dots.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-rounded-dots-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail{ - height: 4px; - margin: 6px 0; - background-repeat: repeat-x; - } - - .mCS-rounded-dots.mCSB_scrollTools .mCSB_buttonUp{ background-position: -16px -72px; } - - .mCS-rounded-dots.mCSB_scrollTools .mCSB_buttonDown{ background-position: -16px -92px; } - - .mCS-rounded-dots.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -20px -112px; } - - .mCS-rounded-dots.mCSB_scrollTools .mCSB_buttonRight{ background-position: -20px -128px; } - - - /* theme "rounded-dots-dark" */ - - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_draggerRail{ - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAALElEQVQYV2NkIAAYSVFgDFR8BqrBBEifBbGRTfiPZhpYjiQFBK3A6l6CvgAAE9kGCd1mvgEAAAAASUVORK5CYII="); - } - - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_buttonUp{ background-position: -96px -72px; } - - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_buttonDown{ background-position: -96px -92px; } - - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -100px -112px; } - - .mCS-rounded-dots-dark.mCSB_scrollTools .mCSB_buttonRight{ background-position: -100px -128px; } - - /* ---------------------------------------- */ - - - - /* theme "3d", "3d-dark", "3d-thick", "3d-thick-dark" */ - - .mCS-3d.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - background-repeat: repeat-y; - background-image: -moz-linear-gradient(left, rgba(255,255,255,0.5) 0%, rgba(255,255,255,0) 100%); - background-image: -webkit-gradient(linear, left top, right top, color-stop(0%,rgba(255,255,255,0.5)), color-stop(100%,rgba(255,255,255,0))); - background-image: -webkit-linear-gradient(left, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 100%); - background-image: -o-linear-gradient(left, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 100%); - background-image: -ms-linear-gradient(left, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 100%); - background-image: linear-gradient(to right, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 100%); - } - - .mCS-3d.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - background-repeat: repeat-x; - background-image: -moz-linear-gradient(top, rgba(255,255,255,0.5) 0%, rgba(255,255,255,0) 100%); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,0.5)), color-stop(100%,rgba(255,255,255,0))); - background-image: -webkit-linear-gradient(top, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 100%); - background-image: -o-linear-gradient(top, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 100%); - background-image: -ms-linear-gradient(top, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 100%); - background-image: linear-gradient(to bottom, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 100%); - } - - - /* theme "3d", "3d-dark" */ - - .mCS-3d.mCSB_scrollTools_vertical .mCSB_dragger, - .mCS-3d-dark.mCSB_scrollTools_vertical .mCSB_dragger{ height: 70px; } - - .mCS-3d.mCSB_scrollTools_horizontal .mCSB_dragger, - .mCS-3d-dark.mCSB_scrollTools_horizontal .mCSB_dragger{ width: 70px; } - - .mCS-3d.mCSB_scrollTools, - .mCS-3d-dark.mCSB_scrollTools{ - opacity: 1; - filter: "alpha(opacity=30)"; -ms-filter: "alpha(opacity=30)"; - } - - .mCS-3d.mCSB_scrollTools .mCSB_draggerRail, - .mCS-3d.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools .mCSB_draggerRail, - .mCS-3d-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ -webkit-border-radius: 16px; -moz-border-radius: 16px; border-radius: 16px; } - - .mCS-3d.mCSB_scrollTools .mCSB_draggerRail, - .mCS-3d-dark.mCSB_scrollTools .mCSB_draggerRail{ - width: 8px; - background-color: #000; background-color: rgba(0,0,0,0.2); - box-shadow: inset 1px 0 1px rgba(0,0,0,0.5), inset -1px 0 1px rgba(255,255,255,0.2); - } - - .mCS-3d.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar, - .mCS-3d.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-3d.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #555; } - - .mCS-3d.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ width: 8px; } - - .mCS-3d.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-3d-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail{ - width: 100%; - height: 8px; - margin: 4px 0; - box-shadow: inset 0 1px 1px rgba(0,0,0,0.5), inset 0 -1px 1px rgba(255,255,255,0.2); - } - - .mCS-3d.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - width: 100%; - height: 8px; - margin: 4px auto; - } - - .mCS-3d.mCSB_scrollTools .mCSB_buttonUp{ background-position: -32px -72px; } - - .mCS-3d.mCSB_scrollTools .mCSB_buttonDown{ background-position: -32px -92px; } - - .mCS-3d.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -40px -112px; } - - .mCS-3d.mCSB_scrollTools .mCSB_buttonRight{ background-position: -40px -128px; } - - - /* theme "3d-dark" */ - - .mCS-3d-dark.mCSB_scrollTools .mCSB_draggerRail{ - background-color: #000; background-color: rgba(0,0,0,0.1); - box-shadow: inset 1px 0 1px rgba(0,0,0,0.1); - } - - .mCS-3d-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail{ box-shadow: inset 0 1px 1px rgba(0,0,0,0.1); } - - .mCS-3d-dark.mCSB_scrollTools .mCSB_buttonUp{ background-position: -112px -72px; } - - .mCS-3d-dark.mCSB_scrollTools .mCSB_buttonDown{ background-position: -112px -92px; } - - .mCS-3d-dark.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -120px -112px; } - - .mCS-3d-dark.mCSB_scrollTools .mCSB_buttonRight{ background-position: -120px -128px; } - - /* ---------------------------------------- */ - - - - /* theme: "3d-thick", "3d-thick-dark" */ - - .mCS-3d-thick.mCSB_scrollTools, - .mCS-3d-thick-dark.mCSB_scrollTools{ - opacity: 1; - filter: "alpha(opacity=30)"; -ms-filter: "alpha(opacity=30)"; - } - - .mCS-3d-thick.mCSB_scrollTools, - .mCS-3d-thick-dark.mCSB_scrollTools, - .mCS-3d-thick.mCSB_scrollTools .mCSB_draggerContainer, - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_draggerContainer{ -webkit-border-radius: 7px; -moz-border-radius: 7px; border-radius: 7px; } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; } - - .mCSB_inside + .mCS-3d-thick.mCSB_scrollTools_vertical, - .mCSB_inside + .mCS-3d-thick-dark.mCSB_scrollTools_vertical{ right: 1px; } - - .mCS-3d-thick.mCSB_scrollTools_vertical, - .mCS-3d-thick-dark.mCSB_scrollTools_vertical{ box-shadow: inset 1px 0 1px rgba(0,0,0,0.1), inset 0 0 14px rgba(0,0,0,0.5); } - - .mCS-3d-thick.mCSB_scrollTools_horizontal, - .mCS-3d-thick-dark.mCSB_scrollTools_horizontal{ - bottom: 1px; - box-shadow: inset 0 1px 1px rgba(0,0,0,0.1), inset 0 0 14px rgba(0,0,0,0.5); - } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - box-shadow: inset 1px 0 0 rgba(255,255,255,0.4); - width: 12px; - margin: 2px; - position: absolute; - height: auto; - top: 0; - bottom: 0; - left: 0; - right: 0; - } - - .mCS-3d-thick.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ box-shadow: inset 0 1px 0 rgba(255,255,255,0.4); } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar, - .mCS-3d-thick.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-3d-thick.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #555; } - - .mCS-3d-thick.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - height: 12px; - width: auto; - } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_draggerContainer{ - background-color: #000; background-color: rgba(0,0,0,0.05); - box-shadow: inset 1px 1px 16px rgba(0,0,0,0.1); - } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_draggerRail{ background-color: transparent; } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_buttonUp{ background-position: -32px -72px; } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_buttonDown{ background-position: -32px -92px; } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -40px -112px; } - - .mCS-3d-thick.mCSB_scrollTools .mCSB_buttonRight{ background-position: -40px -128px; } - - - /* theme: "3d-thick-dark" */ - - .mCS-3d-thick-dark.mCSB_scrollTools{ box-shadow: inset 0 0 14px rgba(0,0,0,0.2); } - - .mCS-3d-thick-dark.mCSB_scrollTools_horizontal{ box-shadow: inset 0 1px 1px rgba(0,0,0,0.1), inset 0 0 14px rgba(0,0,0,0.2); } - - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ box-shadow: inset 1px 0 0 rgba(255,255,255,0.4), inset -1px 0 0 rgba(0,0,0,0.2); } - - .mCS-3d-thick-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ box-shadow: inset 0 1px 0 rgba(255,255,255,0.4), inset 0 -1px 0 rgba(0,0,0,0.2); } - - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #777; } - - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_draggerContainer{ - background-color: #fff; background-color: rgba(0,0,0,0.05); - box-shadow: inset 1px 1px 16px rgba(0,0,0,0.1); - } - - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_draggerRail{ background-color: transparent; } - - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_buttonUp{ background-position: -112px -72px; } - - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_buttonDown{ background-position: -112px -92px; } - - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -120px -112px; } - - .mCS-3d-thick-dark.mCSB_scrollTools .mCSB_buttonRight{ background-position: -120px -128px; } - - /* ---------------------------------------- */ - - - - /* theme: "minimal", "minimal-dark" */ - - .mCSB_outside + .mCS-minimal.mCSB_scrollTools_vertical, - .mCSB_outside + .mCS-minimal-dark.mCSB_scrollTools_vertical{ - right: 0; - margin: 12px 0; - } - - .mCustomScrollBox.mCS-minimal + .mCSB_scrollTools.mCSB_scrollTools_horizontal, - .mCustomScrollBox.mCS-minimal + .mCSB_scrollTools + .mCSB_scrollTools.mCSB_scrollTools_horizontal, - .mCustomScrollBox.mCS-minimal-dark + .mCSB_scrollTools.mCSB_scrollTools_horizontal, - .mCustomScrollBox.mCS-minimal-dark + .mCSB_scrollTools + .mCSB_scrollTools.mCSB_scrollTools_horizontal{ - bottom: 0; - margin: 0 12px; - } - - /* RTL direction/left-side scrollbar */ - .mCS-dir-rtl > .mCSB_outside + .mCS-minimal.mCSB_scrollTools_vertical, - .mCS-dir-rtl > .mCSB_outside + .mCS-minimal-dark.mCSB_scrollTools_vertical{ - left: 0; - right: auto; - } - - .mCS-minimal.mCSB_scrollTools .mCSB_draggerRail, - .mCS-minimal-dark.mCSB_scrollTools .mCSB_draggerRail{ background-color: transparent; } - - .mCS-minimal.mCSB_scrollTools_vertical .mCSB_dragger, - .mCS-minimal-dark.mCSB_scrollTools_vertical .mCSB_dragger{ height: 50px; } - - .mCS-minimal.mCSB_scrollTools_horizontal .mCSB_dragger, - .mCS-minimal-dark.mCSB_scrollTools_horizontal .mCSB_dragger{ width: 50px; } - - .mCS-minimal.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - background-color: #fff; background-color: rgba(255,255,255,0.2); - filter: "alpha(opacity=20)"; -ms-filter: "alpha(opacity=20)"; - } - - .mCS-minimal.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-minimal.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ - background-color: #fff; background-color: rgba(255,255,255,0.5); - filter: "alpha(opacity=50)"; -ms-filter: "alpha(opacity=50)"; - } - - - /* theme: "minimal-dark" */ - - .mCS-minimal-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - background-color: #000; background-color: rgba(0,0,0,0.2); - filter: "alpha(opacity=20)"; -ms-filter: "alpha(opacity=20)"; - } - - .mCS-minimal-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-minimal-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ - background-color: #000; background-color: rgba(0,0,0,0.5); - filter: "alpha(opacity=50)"; -ms-filter: "alpha(opacity=50)"; - } - - /* ---------------------------------------- */ - - - - /* theme "light-3", "dark-3" */ - - .mCS-light-3.mCSB_scrollTools .mCSB_draggerRail, - .mCS-dark-3.mCSB_scrollTools .mCSB_draggerRail{ - width: 6px; - background-color: #000; background-color: rgba(0,0,0,0.2); - } - - .mCS-light-3.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-dark-3.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ width: 6px; } - - .mCS-light-3.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-dark-3.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-light-3.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-dark-3.mCSB_scrollTools_horizontal .mCSB_draggerRail{ - width: 100%; - height: 6px; - margin: 5px 0; - } - - .mCS-light-3.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCS-light-3.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail, - .mCS-dark-3.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCS-dark-3.mCSB_scrollTools_vertical.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{ - width: 12px; - } - - .mCS-light-3.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCS-light-3.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail, - .mCS-dark-3.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_dragger.mCSB_dragger_onDrag_expanded + .mCSB_draggerRail, - .mCS-dark-3.mCSB_scrollTools_horizontal.mCSB_scrollTools_onDrag_expand .mCSB_draggerContainer:hover .mCSB_draggerRail{ - height: 12px; - margin: 2px 0; - } - - .mCS-light-3.mCSB_scrollTools .mCSB_buttonUp{ background-position: -32px -72px; } - - .mCS-light-3.mCSB_scrollTools .mCSB_buttonDown{ background-position: -32px -92px; } - - .mCS-light-3.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -40px -112px; } - - .mCS-light-3.mCSB_scrollTools .mCSB_buttonRight{ background-position: -40px -128px; } - - - /* theme "dark-3" */ - - .mCS-dark-3.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.75); } - - .mCS-dark-3.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.85); } - - .mCS-dark-3.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-dark-3.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.9); } - - .mCS-dark-3.mCSB_scrollTools .mCSB_draggerRail{ background-color: #000; background-color: rgba(0,0,0,0.1); } - - .mCS-dark-3.mCSB_scrollTools .mCSB_buttonUp{ background-position: -112px -72px; } - - .mCS-dark-3.mCSB_scrollTools .mCSB_buttonDown{ background-position: -112px -92px; } - - .mCS-dark-3.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -120px -112px; } - - .mCS-dark-3.mCSB_scrollTools .mCSB_buttonRight{ background-position: -120px -128px; } - - /* ---------------------------------------- */ - - - - /* theme "inset", "inset-dark", "inset-2", "inset-2-dark", "inset-3", "inset-3-dark" */ - - .mCS-inset.mCSB_scrollTools .mCSB_draggerRail, - .mCS-inset-dark.mCSB_scrollTools .mCSB_draggerRail, - .mCS-inset-2.mCSB_scrollTools .mCSB_draggerRail, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_draggerRail, - .mCS-inset-3.mCSB_scrollTools .mCSB_draggerRail, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_draggerRail{ - width: 12px; - background-color: #000; background-color: rgba(0,0,0,0.2); - } - - .mCS-inset.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-2.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-3.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ - width: 6px; - margin: 3px 5px; - position: absolute; - height: auto; - top: 0; - bottom: 0; - left: 0; - right: 0; - } - - .mCS-inset.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-2.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-2-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-3.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-3-dark.mCSB_scrollTools_horizontal .mCSB_dragger .mCSB_dragger_bar{ - height: 6px; - margin: 5px 3px; - position: absolute; - width: auto; - top: 0; - bottom: 0; - left: 0; - right: 0; - } - - .mCS-inset.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-inset-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-inset-2.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-inset-2-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-inset-3.mCSB_scrollTools_horizontal .mCSB_draggerRail, - .mCS-inset-3-dark.mCSB_scrollTools_horizontal .mCSB_draggerRail{ - width: 100%; - height: 12px; - margin: 2px 0; - } - - .mCS-inset.mCSB_scrollTools .mCSB_buttonUp, - .mCS-inset-2.mCSB_scrollTools .mCSB_buttonUp, - .mCS-inset-3.mCSB_scrollTools .mCSB_buttonUp{ background-position: -32px -72px; } - - .mCS-inset.mCSB_scrollTools .mCSB_buttonDown, - .mCS-inset-2.mCSB_scrollTools .mCSB_buttonDown, - .mCS-inset-3.mCSB_scrollTools .mCSB_buttonDown{ background-position: -32px -92px; } - - .mCS-inset.mCSB_scrollTools .mCSB_buttonLeft, - .mCS-inset-2.mCSB_scrollTools .mCSB_buttonLeft, - .mCS-inset-3.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -40px -112px; } - - .mCS-inset.mCSB_scrollTools .mCSB_buttonRight, - .mCS-inset-2.mCSB_scrollTools .mCSB_buttonRight, - .mCS-inset-3.mCSB_scrollTools .mCSB_buttonRight{ background-position: -40px -128px; } - - - /* theme "inset-dark", "inset-2-dark", "inset-3-dark" */ - - .mCS-inset-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.75); } - - .mCS-inset-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.85); } - - .mCS-inset-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-inset-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.9); } - - .mCS-inset-dark.mCSB_scrollTools .mCSB_draggerRail, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_draggerRail, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_draggerRail{ background-color: #000; background-color: rgba(0,0,0,0.1); } - - .mCS-inset-dark.mCSB_scrollTools .mCSB_buttonUp, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_buttonUp, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_buttonUp{ background-position: -112px -72px; } - - .mCS-inset-dark.mCSB_scrollTools .mCSB_buttonDown, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_buttonDown, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_buttonDown{ background-position: -112px -92px; } - - .mCS-inset-dark.mCSB_scrollTools .mCSB_buttonLeft, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_buttonLeft, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_buttonLeft{ background-position: -120px -112px; } - - .mCS-inset-dark.mCSB_scrollTools .mCSB_buttonRight, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_buttonRight, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_buttonRight{ background-position: -120px -128px; } - - - /* theme "inset-2", "inset-2-dark" */ - - .mCS-inset-2.mCSB_scrollTools .mCSB_draggerRail, - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_draggerRail{ - background-color: transparent; - border-width: 1px; - border-style: solid; - border-color: #fff; - border-color: rgba(255,255,255,0.2); - -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; - } - - .mCS-inset-2-dark.mCSB_scrollTools .mCSB_draggerRail{ border-color: #000; border-color: rgba(0,0,0,0.2); } - - - /* theme "inset-3", "inset-3-dark" */ - - .mCS-inset-3.mCSB_scrollTools .mCSB_draggerRail{ background-color: #fff; background-color: rgba(255,255,255,0.6); } - - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_draggerRail{ background-color: #000; background-color: rgba(0,0,0,0.6); } - - .mCS-inset-3.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.75); } - - .mCS-inset-3.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.85); } - - .mCS-inset-3.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-inset-3.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #000; background-color: rgba(0,0,0,0.9); } - - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{ background-color: #fff; background-color: rgba(255,255,255,0.75); } - - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{ background-color: #fff; background-color: rgba(255,255,255,0.85); } - - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar, - .mCS-inset-3-dark.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{ background-color: #fff; background-color: rgba(255,255,255,0.9); } - - /* ---------------------------------------- */ diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.js b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.js deleted file mode 100644 index ff7a7263f8..0000000000 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.js +++ /dev/null @@ -1,2458 +0,0 @@ -/* -== malihu jquery custom scrollbar plugin == -Version: 3.1.5 -Plugin URI: http://manos.malihu.gr/jquery-custom-content-scroller -Author: malihu -Author URI: http://manos.malihu.gr -License: MIT License (MIT) -*/ - -/* -Copyright Manos Malihutsakis (email: manos@malihu.gr) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -/* -The code below is fairly long, fully commented and should be normally used in development. -For production, use either the minified jquery.mCustomScrollbar.min.js script or -the production-ready jquery.mCustomScrollbar.concat.min.js which contains the plugin -and dependencies (minified). -*/ - -(function(factory){ - if(typeof define==="function" && define.amd){ - define(["jquery"],factory); - }else if(typeof module!=="undefined" && module.exports){ - module.exports=factory; - }else{ - factory(jQuery,window,document); - } -}(function($){ -(function(init){ - var _rjs=typeof define==="function" && define.amd, /* RequireJS */ - _njs=typeof module !== "undefined" && module.exports, /* NodeJS */ - _dlp=("https:"==document.location.protocol) ? "https:" : "http:", /* location protocol */ - _url="cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.13/jquery.mousewheel.min.js"; - if(!_rjs){ - if(_njs){ - require("jquery-mousewheel")($); - }else{ - /* load jquery-mousewheel plugin (via CDN) if it's not present or not loaded via RequireJS - (works when mCustomScrollbar fn is called on window load) */ - $.event.special.mousewheel || $("head").append(decodeURI("%3Cscript src="+_dlp+"//"+_url+"%3E%3C/script%3E")); - } - } - init(); -}(function(){ - - /* - ---------------------------------------- - PLUGIN NAMESPACE, PREFIX, DEFAULT SELECTOR(S) - ---------------------------------------- - */ - - var pluginNS="mCustomScrollbar", - pluginPfx="mCS", - defaultSelector=".mCustomScrollbar", - - - - - - /* - ---------------------------------------- - DEFAULT OPTIONS - ---------------------------------------- - */ - - defaults={ - /* - set element/content width/height programmatically - values: boolean, pixels, percentage - option default - ------------------------------------- - setWidth false - setHeight false - */ - /* - set the initial css top property of content - values: string (e.g. "-100px", "10%" etc.) - */ - setTop:0, - /* - set the initial css left property of content - values: string (e.g. "-100px", "10%" etc.) - */ - setLeft:0, - /* - scrollbar axis (vertical and/or horizontal scrollbars) - values (string): "y", "x", "yx" - */ - axis:"y", - /* - position of scrollbar relative to content - values (string): "inside", "outside" ("outside" requires elements with position:relative) - */ - scrollbarPosition:"inside", - /* - scrolling inertia - values: integer (milliseconds) - */ - scrollInertia:950, - /* - auto-adjust scrollbar dragger length - values: boolean - */ - autoDraggerLength:true, - /* - auto-hide scrollbar when idle - values: boolean - option default - ------------------------------------- - autoHideScrollbar false - */ - /* - auto-expands scrollbar on mouse-over and dragging - values: boolean - option default - ------------------------------------- - autoExpandScrollbar false - */ - /* - always show scrollbar, even when there's nothing to scroll - values: integer (0=disable, 1=always show dragger rail and buttons, 2=always show dragger rail, dragger and buttons), boolean - */ - alwaysShowScrollbar:0, - /* - scrolling always snaps to a multiple of this number in pixels - values: integer, array ([y,x]) - option default - ------------------------------------- - snapAmount null - */ - /* - when snapping, snap with this number in pixels as an offset - values: integer - */ - snapOffset:0, - /* - mouse-wheel scrolling - */ - mouseWheel:{ - /* - enable mouse-wheel scrolling - values: boolean - */ - enable:true, - /* - scrolling amount in pixels - values: "auto", integer - */ - scrollAmount:"auto", - /* - mouse-wheel scrolling axis - the default scrolling direction when both vertical and horizontal scrollbars are present - values (string): "y", "x" - */ - axis:"y", - /* - prevent the default behaviour which automatically scrolls the parent element(s) when end of scrolling is reached - values: boolean - option default - ------------------------------------- - preventDefault null - */ - /* - the reported mouse-wheel delta value. The number of lines (translated to pixels) one wheel notch scrolls. - values: "auto", integer - "auto" uses the default OS/browser value - */ - deltaFactor:"auto", - /* - normalize mouse-wheel delta to -1 or 1 (disables mouse-wheel acceleration) - values: boolean - option default - ------------------------------------- - normalizeDelta null - */ - /* - invert mouse-wheel scrolling direction - values: boolean - option default - ------------------------------------- - invert null - */ - /* - the tags that disable mouse-wheel when cursor is over them - */ - disableOver:["select","option","keygen","datalist","textarea"] - }, - /* - scrollbar buttons - */ - scrollButtons:{ - /* - enable scrollbar buttons - values: boolean - option default - ------------------------------------- - enable null - */ - /* - scrollbar buttons scrolling type - values (string): "stepless", "stepped" - */ - scrollType:"stepless", - /* - scrolling amount in pixels - values: "auto", integer - */ - scrollAmount:"auto" - /* - tabindex of the scrollbar buttons - values: false, integer - option default - ------------------------------------- - tabindex null - */ - }, - /* - keyboard scrolling - */ - keyboard:{ - /* - enable scrolling via keyboard - values: boolean - */ - enable:true, - /* - keyboard scrolling type - values (string): "stepless", "stepped" - */ - scrollType:"stepless", - /* - scrolling amount in pixels - values: "auto", integer - */ - scrollAmount:"auto" - }, - /* - enable content touch-swipe scrolling - values: boolean, integer, string (number) - integer values define the axis-specific minimum amount required for scrolling momentum - */ - contentTouchScroll:25, - /* - enable/disable document (default) touch-swipe scrolling - */ - documentTouchScroll:true, - /* - advanced option parameters - */ - advanced:{ - /* - auto-expand content horizontally (for "x" or "yx" axis) - values: boolean, integer (the value 2 forces the non scrollHeight/scrollWidth method, the value 3 forces the scrollHeight/scrollWidth method) - option default - ------------------------------------- - autoExpandHorizontalScroll null - */ - /* - auto-scroll to elements with focus - */ - autoScrollOnFocus:"input,textarea,select,button,datalist,keygen,a[tabindex],area,object,[contenteditable='true']", - /* - auto-update scrollbars on content, element or viewport resize - should be true for fluid layouts/elements, adding/removing content dynamically, hiding/showing elements, content with images etc. - values: boolean - */ - updateOnContentResize:true, - /* - auto-update scrollbars each time each image inside the element is fully loaded - values: "auto", boolean - */ - updateOnImageLoad:"auto", - /* - auto-update scrollbars based on the amount and size changes of specific selectors - useful when you need to update the scrollbar(s) automatically, each time a type of element is added, removed or changes its size - values: boolean, string (e.g. "ul li" will auto-update scrollbars each time list-items inside the element are changed) - a value of true (boolean) will auto-update scrollbars each time any element is changed - option default - ------------------------------------- - updateOnSelectorChange null - */ - /* - extra selectors that'll allow scrollbar dragging upon mousemove/up, pointermove/up, touchend etc. (e.g. "selector-1, selector-2") - option default - ------------------------------------- - extraDraggableSelectors null - */ - /* - extra selectors that'll release scrollbar dragging upon mouseup, pointerup, touchend etc. (e.g. "selector-1, selector-2") - option default - ------------------------------------- - releaseDraggableSelectors null - */ - /* - auto-update timeout - values: integer (milliseconds) - */ - autoUpdateTimeout:60 - }, - /* - scrollbar theme - values: string (see CSS/plugin URI for a list of ready-to-use themes) - */ - theme:"light", - /* - user defined callback functions - */ - callbacks:{ - /* - Available callbacks: - callback default - ------------------------------------- - onCreate null - onInit null - onScrollStart null - onScroll null - onTotalScroll null - onTotalScrollBack null - whileScrolling null - onOverflowY null - onOverflowX null - onOverflowYNone null - onOverflowXNone null - onImageLoad null - onSelectorChange null - onBeforeUpdate null - onUpdate null - */ - onTotalScrollOffset:0, - onTotalScrollBackOffset:0, - alwaysTriggerOffsets:true - } - /* - add scrollbar(s) on all elements matching the current selector, now and in the future - values: boolean, string - string values: "on" (enable), "once" (disable after first invocation), "off" (disable) - liveSelector values: string (selector) - option default - ------------------------------------- - live false - liveSelector null - */ - }, - - - - - - /* - ---------------------------------------- - VARS, CONSTANTS - ---------------------------------------- - */ - - totalInstances=0, /* plugin instances amount */ - liveTimers={}, /* live option timers */ - oldIE=(window.attachEvent && !window.addEventListener) ? 1 : 0, /* detect IE < 9 */ - touchActive=false,touchable, /* global touch vars (for touch and pointer events) */ - /* general plugin classes */ - classes=[ - "mCSB_dragger_onDrag","mCSB_scrollTools_onDrag","mCS_img_loaded","mCS_disabled","mCS_destroyed","mCS_no_scrollbar", - "mCS-autoHide","mCS-dir-rtl","mCS_no_scrollbar_y","mCS_no_scrollbar_x","mCS_y_hidden","mCS_x_hidden","mCSB_draggerContainer", - "mCSB_buttonUp","mCSB_buttonDown","mCSB_buttonLeft","mCSB_buttonRight" - ], - - - - - - /* - ---------------------------------------- - METHODS - ---------------------------------------- - */ - - methods={ - - /* - plugin initialization method - creates the scrollbar(s), plugin data object and options - ---------------------------------------- - */ - - init:function(options){ - - var options=$.extend(true,{},defaults,options), - selector=_selector.call(this); /* validate selector */ - - /* - if live option is enabled, monitor for elements matching the current selector and - apply scrollbar(s) when found (now and in the future) - */ - if(options.live){ - var liveSelector=options.liveSelector || this.selector || defaultSelector, /* live selector(s) */ - $liveSelector=$(liveSelector); /* live selector(s) as jquery object */ - if(options.live==="off"){ - /* - disable live if requested - usage: $(selector).mCustomScrollbar({live:"off"}); - */ - removeLiveTimers(liveSelector); - return; - } - liveTimers[liveSelector]=setTimeout(function(){ - /* call mCustomScrollbar fn on live selector(s) every half-second */ - $liveSelector.mCustomScrollbar(options); - if(options.live==="once" && $liveSelector.length){ - /* disable live after first invocation */ - removeLiveTimers(liveSelector); - } - },500); - }else{ - removeLiveTimers(liveSelector); - } - - /* options backward compatibility (for versions < 3.0.0) and normalization */ - options.setWidth=(options.set_width) ? options.set_width : options.setWidth; - options.setHeight=(options.set_height) ? options.set_height : options.setHeight; - options.axis=(options.horizontalScroll) ? "x" : _findAxis(options.axis); - options.scrollInertia=options.scrollInertia>0 && options.scrollInertia<17 ? 17 : options.scrollInertia; - if(typeof options.mouseWheel!=="object" && options.mouseWheel==true){ /* old school mouseWheel option (non-object) */ - options.mouseWheel={enable:true,scrollAmount:"auto",axis:"y",preventDefault:false,deltaFactor:"auto",normalizeDelta:false,invert:false} - } - options.mouseWheel.scrollAmount=!options.mouseWheelPixels ? options.mouseWheel.scrollAmount : options.mouseWheelPixels; - options.mouseWheel.normalizeDelta=!options.advanced.normalizeMouseWheelDelta ? options.mouseWheel.normalizeDelta : options.advanced.normalizeMouseWheelDelta; - options.scrollButtons.scrollType=_findScrollButtonsType(options.scrollButtons.scrollType); - - _theme(options); /* theme-specific options */ - - /* plugin constructor */ - return $(selector).each(function(){ - - var $this=$(this); - - if(!$this.data(pluginPfx)){ /* prevent multiple instantiations */ - - /* store options and create objects in jquery data */ - $this.data(pluginPfx,{ - idx:++totalInstances, /* instance index */ - opt:options, /* options */ - scrollRatio:{y:null,x:null}, /* scrollbar to content ratio */ - overflowed:null, /* overflowed axis */ - contentReset:{y:null,x:null}, /* object to check when content resets */ - bindEvents:false, /* object to check if events are bound */ - tweenRunning:false, /* object to check if tween is running */ - sequential:{}, /* sequential scrolling object */ - langDir:$this.css("direction"), /* detect/store direction (ltr or rtl) */ - cbOffsets:null, /* object to check whether callback offsets always trigger */ - /* - object to check how scrolling events where last triggered - "internal" (default - triggered by this script), "external" (triggered by other scripts, e.g. via scrollTo method) - usage: object.data("mCS").trigger - */ - trigger:null, - /* - object to check for changes in elements in order to call the update method automatically - */ - poll:{size:{o:0,n:0},img:{o:0,n:0},change:{o:0,n:0}} - }); - - var d=$this.data(pluginPfx),o=d.opt, - /* HTML data attributes */ - htmlDataAxis=$this.data("mcs-axis"),htmlDataSbPos=$this.data("mcs-scrollbar-position"),htmlDataTheme=$this.data("mcs-theme"); - - if(htmlDataAxis){o.axis=htmlDataAxis;} /* usage example: data-mcs-axis="y" */ - if(htmlDataSbPos){o.scrollbarPosition=htmlDataSbPos;} /* usage example: data-mcs-scrollbar-position="outside" */ - if(htmlDataTheme){ /* usage example: data-mcs-theme="minimal" */ - o.theme=htmlDataTheme; - _theme(o); /* theme-specific options */ - } - - _pluginMarkup.call(this); /* add plugin markup */ - - if(d && o.callbacks.onCreate && typeof o.callbacks.onCreate==="function"){o.callbacks.onCreate.call(this);} /* callbacks: onCreate */ - - $("#mCSB_"+d.idx+"_container img:not(."+classes[2]+")").addClass(classes[2]); /* flag loaded images */ - - methods.update.call(null,$this); /* call the update method */ - - } - - }); - - }, - /* ---------------------------------------- */ - - - - /* - plugin update method - updates content and scrollbar(s) values, events and status - ---------------------------------------- - usage: $(selector).mCustomScrollbar("update"); - */ - - update:function(el,cb){ - - var selector=el || _selector.call(this); /* validate selector */ - - return $(selector).each(function(){ - - var $this=$(this); - - if($this.data(pluginPfx)){ /* check if plugin has initialized */ - - var d=$this.data(pluginPfx),o=d.opt, - mCSB_container=$("#mCSB_"+d.idx+"_container"), - mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_dragger=[$("#mCSB_"+d.idx+"_dragger_vertical"),$("#mCSB_"+d.idx+"_dragger_horizontal")]; - - if(!mCSB_container.length){return;} - - if(d.tweenRunning){_stop($this);} /* stop any running tweens while updating */ - - if(cb && d && o.callbacks.onBeforeUpdate && typeof o.callbacks.onBeforeUpdate==="function"){o.callbacks.onBeforeUpdate.call(this);} /* callbacks: onBeforeUpdate */ - - /* if element was disabled or destroyed, remove class(es) */ - if($this.hasClass(classes[3])){$this.removeClass(classes[3]);} - if($this.hasClass(classes[4])){$this.removeClass(classes[4]);} - - /* css flexbox fix, detect/set max-height */ - mCustomScrollBox.css("max-height","none"); - if(mCustomScrollBox.height()!==$this.height()){mCustomScrollBox.css("max-height",$this.height());} - - _expandContentHorizontally.call(this); /* expand content horizontally */ - - if(o.axis!=="y" && !o.advanced.autoExpandHorizontalScroll){ - mCSB_container.css("width",_contentWidth(mCSB_container)); - } - - d.overflowed=_overflowed.call(this); /* determine if scrolling is required */ - - _scrollbarVisibility.call(this); /* show/hide scrollbar(s) */ - - /* auto-adjust scrollbar dragger length analogous to content */ - if(o.autoDraggerLength){_setDraggerLength.call(this);} - - _scrollRatio.call(this); /* calculate and store scrollbar to content ratio */ - - _bindEvents.call(this); /* bind scrollbar events */ - - /* reset scrolling position and/or events */ - var to=[Math.abs(mCSB_container[0].offsetTop),Math.abs(mCSB_container[0].offsetLeft)]; - if(o.axis!=="x"){ /* y/yx axis */ - if(!d.overflowed[0]){ /* y scrolling is not required */ - _resetContentPosition.call(this); /* reset content position */ - if(o.axis==="y"){ - _unbindEvents.call(this); - }else if(o.axis==="yx" && d.overflowed[1]){ - _scrollTo($this,to[1].toString(),{dir:"x",dur:0,overwrite:"none"}); - } - }else if(mCSB_dragger[0].height()>mCSB_dragger[0].parent().height()){ - _resetContentPosition.call(this); /* reset content position */ - }else{ /* y scrolling is required */ - _scrollTo($this,to[0].toString(),{dir:"y",dur:0,overwrite:"none"}); - d.contentReset.y=null; - } - } - if(o.axis!=="y"){ /* x/yx axis */ - if(!d.overflowed[1]){ /* x scrolling is not required */ - _resetContentPosition.call(this); /* reset content position */ - if(o.axis==="x"){ - _unbindEvents.call(this); - }else if(o.axis==="yx" && d.overflowed[0]){ - _scrollTo($this,to[0].toString(),{dir:"y",dur:0,overwrite:"none"}); - } - }else if(mCSB_dragger[1].width()>mCSB_dragger[1].parent().width()){ - _resetContentPosition.call(this); /* reset content position */ - }else{ /* x scrolling is required */ - _scrollTo($this,to[1].toString(),{dir:"x",dur:0,overwrite:"none"}); - d.contentReset.x=null; - } - } - - /* callbacks: onImageLoad, onSelectorChange, onUpdate */ - if(cb && d){ - if(cb===2 && o.callbacks.onImageLoad && typeof o.callbacks.onImageLoad==="function"){ - o.callbacks.onImageLoad.call(this); - }else if(cb===3 && o.callbacks.onSelectorChange && typeof o.callbacks.onSelectorChange==="function"){ - o.callbacks.onSelectorChange.call(this); - }else if(o.callbacks.onUpdate && typeof o.callbacks.onUpdate==="function"){ - o.callbacks.onUpdate.call(this); - } - } - - _autoUpdate.call(this); /* initialize automatic updating (for dynamic content, fluid layouts etc.) */ - - } - - }); - - }, - /* ---------------------------------------- */ - - - - /* - plugin scrollTo method - triggers a scrolling event to a specific value - ---------------------------------------- - usage: $(selector).mCustomScrollbar("scrollTo",value,options); - */ - - scrollTo:function(val,options){ - - /* prevent silly things like $(selector).mCustomScrollbar("scrollTo",undefined); */ - if(typeof val=="undefined" || val==null){return;} - - var selector=_selector.call(this); /* validate selector */ - - return $(selector).each(function(){ - - var $this=$(this); - - if($this.data(pluginPfx)){ /* check if plugin has initialized */ - - var d=$this.data(pluginPfx),o=d.opt, - /* method default options */ - methodDefaults={ - trigger:"external", /* method is by default triggered externally (e.g. from other scripts) */ - scrollInertia:o.scrollInertia, /* scrolling inertia (animation duration) */ - scrollEasing:"mcsEaseInOut", /* animation easing */ - moveDragger:false, /* move dragger instead of content */ - timeout:60, /* scroll-to delay */ - callbacks:true, /* enable/disable callbacks */ - onStart:true, - onUpdate:true, - onComplete:true - }, - methodOptions=$.extend(true,{},methodDefaults,options), - to=_arr.call(this,val),dur=methodOptions.scrollInertia>0 && methodOptions.scrollInertia<17 ? 17 : methodOptions.scrollInertia; - - /* translate yx values to actual scroll-to positions */ - to[0]=_to.call(this,to[0],"y"); - to[1]=_to.call(this,to[1],"x"); - - /* - check if scroll-to value moves the dragger instead of content. - Only pixel values apply on dragger (e.g. 100, "100px", "-=100" etc.) - */ - if(methodOptions.moveDragger){ - to[0]*=d.scrollRatio.y; - to[1]*=d.scrollRatio.x; - } - - methodOptions.dur=_isTabHidden() ? 0 : dur; //skip animations if browser tab is hidden - - setTimeout(function(){ - /* do the scrolling */ - if(to[0]!==null && typeof to[0]!=="undefined" && o.axis!=="x" && d.overflowed[0]){ /* scroll y */ - methodOptions.dir="y"; - methodOptions.overwrite="all"; - _scrollTo($this,to[0].toString(),methodOptions); - } - if(to[1]!==null && typeof to[1]!=="undefined" && o.axis!=="y" && d.overflowed[1]){ /* scroll x */ - methodOptions.dir="x"; - methodOptions.overwrite="none"; - _scrollTo($this,to[1].toString(),methodOptions); - } - },methodOptions.timeout); - - } - - }); - - }, - /* ---------------------------------------- */ - - - - /* - plugin stop method - stops scrolling animation - ---------------------------------------- - usage: $(selector).mCustomScrollbar("stop"); - */ - stop:function(){ - - var selector=_selector.call(this); /* validate selector */ - - return $(selector).each(function(){ - - var $this=$(this); - - if($this.data(pluginPfx)){ /* check if plugin has initialized */ - - _stop($this); - - } - - }); - - }, - /* ---------------------------------------- */ - - - - /* - plugin disable method - temporarily disables the scrollbar(s) - ---------------------------------------- - usage: $(selector).mCustomScrollbar("disable",reset); - reset (boolean): resets content position to 0 - */ - disable:function(r){ - - var selector=_selector.call(this); /* validate selector */ - - return $(selector).each(function(){ - - var $this=$(this); - - if($this.data(pluginPfx)){ /* check if plugin has initialized */ - - var d=$this.data(pluginPfx); - - _autoUpdate.call(this,"remove"); /* remove automatic updating */ - - _unbindEvents.call(this); /* unbind events */ - - if(r){_resetContentPosition.call(this);} /* reset content position */ - - _scrollbarVisibility.call(this,true); /* show/hide scrollbar(s) */ - - $this.addClass(classes[3]); /* add disable class */ - - } - - }); - - }, - /* ---------------------------------------- */ - - - - /* - plugin destroy method - completely removes the scrollbar(s) and returns the element to its original state - ---------------------------------------- - usage: $(selector).mCustomScrollbar("destroy"); - */ - destroy:function(){ - - var selector=_selector.call(this); /* validate selector */ - - return $(selector).each(function(){ - - var $this=$(this); - - if($this.data(pluginPfx)){ /* check if plugin has initialized */ - - var d=$this.data(pluginPfx),o=d.opt, - mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_container=$("#mCSB_"+d.idx+"_container"), - scrollbar=$(".mCSB_"+d.idx+"_scrollbar"); - - if(o.live){removeLiveTimers(o.liveSelector || $(selector).selector);} /* remove live timers */ - - _autoUpdate.call(this,"remove"); /* remove automatic updating */ - - _unbindEvents.call(this); /* unbind events */ - - _resetContentPosition.call(this); /* reset content position */ - - $this.removeData(pluginPfx); /* remove plugin data object */ - - _delete(this,"mcs"); /* delete callbacks object */ - - /* remove plugin markup */ - scrollbar.remove(); /* remove scrollbar(s) first (those can be either inside or outside plugin's inner wrapper) */ - mCSB_container.find("img."+classes[2]).removeClass(classes[2]); /* remove loaded images flag */ - mCustomScrollBox.replaceWith(mCSB_container.contents()); /* replace plugin's inner wrapper with the original content */ - /* remove plugin classes from the element and add destroy class */ - $this.removeClass(pluginNS+" _"+pluginPfx+"_"+d.idx+" "+classes[6]+" "+classes[7]+" "+classes[5]+" "+classes[3]).addClass(classes[4]); - - } - - }); - - } - /* ---------------------------------------- */ - - }, - - - - - - /* - ---------------------------------------- - FUNCTIONS - ---------------------------------------- - */ - - /* validates selector (if selector is invalid or undefined uses the default one) */ - _selector=function(){ - return (typeof $(this)!=="object" || $(this).length<1) ? defaultSelector : this; - }, - /* -------------------- */ - - - /* changes options according to theme */ - _theme=function(obj){ - var fixedSizeScrollbarThemes=["rounded","rounded-dark","rounded-dots","rounded-dots-dark"], - nonExpandedScrollbarThemes=["rounded-dots","rounded-dots-dark","3d","3d-dark","3d-thick","3d-thick-dark","inset","inset-dark","inset-2","inset-2-dark","inset-3","inset-3-dark"], - disabledScrollButtonsThemes=["minimal","minimal-dark"], - enabledAutoHideScrollbarThemes=["minimal","minimal-dark"], - scrollbarPositionOutsideThemes=["minimal","minimal-dark"]; - obj.autoDraggerLength=$.inArray(obj.theme,fixedSizeScrollbarThemes) > -1 ? false : obj.autoDraggerLength; - obj.autoExpandScrollbar=$.inArray(obj.theme,nonExpandedScrollbarThemes) > -1 ? false : obj.autoExpandScrollbar; - obj.scrollButtons.enable=$.inArray(obj.theme,disabledScrollButtonsThemes) > -1 ? false : obj.scrollButtons.enable; - obj.autoHideScrollbar=$.inArray(obj.theme,enabledAutoHideScrollbarThemes) > -1 ? true : obj.autoHideScrollbar; - obj.scrollbarPosition=$.inArray(obj.theme,scrollbarPositionOutsideThemes) > -1 ? "outside" : obj.scrollbarPosition; - }, - /* -------------------- */ - - - /* live option timers removal */ - removeLiveTimers=function(selector){ - if(liveTimers[selector]){ - clearTimeout(liveTimers[selector]); - _delete(liveTimers,selector); - } - }, - /* -------------------- */ - - - /* normalizes axis option to valid values: "y", "x", "yx" */ - _findAxis=function(val){ - return (val==="yx" || val==="xy" || val==="auto") ? "yx" : (val==="x" || val==="horizontal") ? "x" : "y"; - }, - /* -------------------- */ - - - /* normalizes scrollButtons.scrollType option to valid values: "stepless", "stepped" */ - _findScrollButtonsType=function(val){ - return (val==="stepped" || val==="pixels" || val==="step" || val==="click") ? "stepped" : "stepless"; - }, - /* -------------------- */ - - - /* generates plugin markup */ - _pluginMarkup=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - expandClass=o.autoExpandScrollbar ? " "+classes[1]+"_expand" : "", - scrollbar=["
","
"], - wrapperClass=o.axis==="yx" ? "mCSB_vertical_horizontal" : o.axis==="x" ? "mCSB_horizontal" : "mCSB_vertical", - scrollbars=o.axis==="yx" ? scrollbar[0]+scrollbar[1] : o.axis==="x" ? scrollbar[1] : scrollbar[0], - contentWrapper=o.axis==="yx" ? "
" : "", - autoHideClass=o.autoHideScrollbar ? " "+classes[6] : "", - scrollbarDirClass=(o.axis!=="x" && d.langDir==="rtl") ? " "+classes[7] : ""; - if(o.setWidth){$this.css("width",o.setWidth);} /* set element width */ - if(o.setHeight){$this.css("height",o.setHeight);} /* set element height */ - o.setLeft=(o.axis!=="y" && d.langDir==="rtl") ? "989999px" : o.setLeft; /* adjust left position for rtl direction */ - $this.addClass(pluginNS+" _"+pluginPfx+"_"+d.idx+autoHideClass+scrollbarDirClass).wrapInner("
"); - var mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_container=$("#mCSB_"+d.idx+"_container"); - if(o.axis!=="y" && !o.advanced.autoExpandHorizontalScroll){ - mCSB_container.css("width",_contentWidth(mCSB_container)); - } - if(o.scrollbarPosition==="outside"){ - if($this.css("position")==="static"){ /* requires elements with non-static position */ - $this.css("position","relative"); - } - $this.css("overflow","visible"); - mCustomScrollBox.addClass("mCSB_outside").after(scrollbars); - }else{ - mCustomScrollBox.addClass("mCSB_inside").append(scrollbars); - mCSB_container.wrap(contentWrapper); - } - _scrollButtons.call(this); /* add scrollbar buttons */ - /* minimum dragger length */ - var mCSB_dragger=[$("#mCSB_"+d.idx+"_dragger_vertical"),$("#mCSB_"+d.idx+"_dragger_horizontal")]; - mCSB_dragger[0].css("min-height",mCSB_dragger[0].height()); - mCSB_dragger[1].css("min-width",mCSB_dragger[1].width()); - }, - /* -------------------- */ - - - /* calculates content width */ - _contentWidth=function(el){ - var val=[el[0].scrollWidth,Math.max.apply(Math,el.children().map(function(){return $(this).outerWidth(true);}).get())],w=el.parent().width(); - return val[0]>w ? val[0] : val[1]>w ? val[1] : "100%"; - }, - /* -------------------- */ - - - /* expands content horizontally */ - _expandContentHorizontally=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - mCSB_container=$("#mCSB_"+d.idx+"_container"); - if(o.advanced.autoExpandHorizontalScroll && o.axis!=="y"){ - /* calculate scrollWidth */ - mCSB_container.css({"width":"auto","min-width":0,"overflow-x":"scroll"}); - var w=Math.ceil(mCSB_container[0].scrollWidth); - if(o.advanced.autoExpandHorizontalScroll===3 || (o.advanced.autoExpandHorizontalScroll!==2 && w>mCSB_container.parent().width())){ - mCSB_container.css({"width":w,"min-width":"100%","overflow-x":"inherit"}); - }else{ - /* - wrap content with an infinite width div and set its position to absolute and width to auto. - Setting width to auto before calculating the actual width is important! - We must let the browser set the width as browser zoom values are impossible to calculate. - */ - mCSB_container.css({"overflow-x":"inherit","position":"absolute"}) - .wrap("
") - .css({ /* set actual width, original position and un-wrap */ - /* - get the exact width (with decimals) and then round-up. - Using jquery outerWidth() will round the width value which will mess up with inner elements that have non-integer width - */ - "width":(Math.ceil(mCSB_container[0].getBoundingClientRect().right+0.4)-Math.floor(mCSB_container[0].getBoundingClientRect().left)), - "min-width":"100%", - "position":"relative" - }).unwrap(); - } - } - }, - /* -------------------- */ - - - /* adds scrollbar buttons */ - _scrollButtons=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - mCSB_scrollTools=$(".mCSB_"+d.idx+"_scrollbar:first"), - tabindex=!_isNumeric(o.scrollButtons.tabindex) ? "" : "tabindex='"+o.scrollButtons.tabindex+"'", - btnHTML=[ - "", - "", - "", - "" - ], - btn=[(o.axis==="x" ? btnHTML[2] : btnHTML[0]),(o.axis==="x" ? btnHTML[3] : btnHTML[1]),btnHTML[2],btnHTML[3]]; - if(o.scrollButtons.enable){ - mCSB_scrollTools.prepend(btn[0]).append(btn[1]).next(".mCSB_scrollTools").prepend(btn[2]).append(btn[3]); - } - }, - /* -------------------- */ - - - /* auto-adjusts scrollbar dragger length */ - _setDraggerLength=function(){ - var $this=$(this),d=$this.data(pluginPfx), - mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_container=$("#mCSB_"+d.idx+"_container"), - mCSB_dragger=[$("#mCSB_"+d.idx+"_dragger_vertical"),$("#mCSB_"+d.idx+"_dragger_horizontal")], - ratio=[mCustomScrollBox.height()/mCSB_container.outerHeight(false),mCustomScrollBox.width()/mCSB_container.outerWidth(false)], - l=[ - parseInt(mCSB_dragger[0].css("min-height")),Math.round(ratio[0]*mCSB_dragger[0].parent().height()), - parseInt(mCSB_dragger[1].css("min-width")),Math.round(ratio[1]*mCSB_dragger[1].parent().width()) - ], - h=oldIE && (l[1]contentHeight){contentHeight=h;} - if(w>contentWidth){contentWidth=w;} - return [contentHeight>mCustomScrollBox.height(),contentWidth>mCustomScrollBox.width()]; - }, - /* -------------------- */ - - - /* resets content position to 0 */ - _resetContentPosition=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_container=$("#mCSB_"+d.idx+"_container"), - mCSB_dragger=[$("#mCSB_"+d.idx+"_dragger_vertical"),$("#mCSB_"+d.idx+"_dragger_horizontal")]; - _stop($this); /* stop any current scrolling before resetting */ - if((o.axis!=="x" && !d.overflowed[0]) || (o.axis==="y" && d.overflowed[0])){ /* reset y */ - mCSB_dragger[0].add(mCSB_container).css("top",0); - _scrollTo($this,"_resetY"); - } - if((o.axis!=="y" && !d.overflowed[1]) || (o.axis==="x" && d.overflowed[1])){ /* reset x */ - var cx=dx=0; - if(d.langDir==="rtl"){ /* adjust left position for rtl direction */ - cx=mCustomScrollBox.width()-mCSB_container.outerWidth(false); - dx=Math.abs(cx/d.scrollRatio.x); - } - mCSB_container.css("left",cx); - mCSB_dragger[1].css("left",dx); - _scrollTo($this,"_resetX"); - } - }, - /* -------------------- */ - - - /* binds scrollbar events */ - _bindEvents=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt; - if(!d.bindEvents){ /* check if events are already bound */ - _draggable.call(this); - if(o.contentTouchScroll){_contentDraggable.call(this);} - _selectable.call(this); - if(o.mouseWheel.enable){ /* bind mousewheel fn when plugin is available */ - function _mwt(){ - mousewheelTimeout=setTimeout(function(){ - if(!$.event.special.mousewheel){ - _mwt(); - }else{ - clearTimeout(mousewheelTimeout); - _mousewheel.call($this[0]); - } - },100); - } - var mousewheelTimeout; - _mwt(); - } - _draggerRail.call(this); - _wrapperScroll.call(this); - if(o.advanced.autoScrollOnFocus){_focus.call(this);} - if(o.scrollButtons.enable){_buttons.call(this);} - if(o.keyboard.enable){_keyboard.call(this);} - d.bindEvents=true; - } - }, - /* -------------------- */ - - - /* unbinds scrollbar events */ - _unbindEvents=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - namespace=pluginPfx+"_"+d.idx, - sb=".mCSB_"+d.idx+"_scrollbar", - sel=$("#mCSB_"+d.idx+",#mCSB_"+d.idx+"_container,#mCSB_"+d.idx+"_container_wrapper,"+sb+" ."+classes[12]+",#mCSB_"+d.idx+"_dragger_vertical,#mCSB_"+d.idx+"_dragger_horizontal,"+sb+">a"), - mCSB_container=$("#mCSB_"+d.idx+"_container"); - if(o.advanced.releaseDraggableSelectors){sel.add($(o.advanced.releaseDraggableSelectors));} - if(o.advanced.extraDraggableSelectors){sel.add($(o.advanced.extraDraggableSelectors));} - if(d.bindEvents){ /* check if events are bound */ - /* unbind namespaced events from document/selectors */ - $(document).add($(!_canAccessIFrame() || top.document)).unbind("."+namespace); - sel.each(function(){ - $(this).unbind("."+namespace); - }); - /* clear and delete timeouts/objects */ - clearTimeout($this[0]._focusTimeout); _delete($this[0],"_focusTimeout"); - clearTimeout(d.sequential.step); _delete(d.sequential,"step"); - clearTimeout(mCSB_container[0].onCompleteTimeout); _delete(mCSB_container[0],"onCompleteTimeout"); - d.bindEvents=false; - } - }, - /* -------------------- */ - - - /* toggles scrollbar visibility */ - _scrollbarVisibility=function(disabled){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - contentWrapper=$("#mCSB_"+d.idx+"_container_wrapper"), - content=contentWrapper.length ? contentWrapper : $("#mCSB_"+d.idx+"_container"), - scrollbar=[$("#mCSB_"+d.idx+"_scrollbar_vertical"),$("#mCSB_"+d.idx+"_scrollbar_horizontal")], - mCSB_dragger=[scrollbar[0].find(".mCSB_dragger"),scrollbar[1].find(".mCSB_dragger")]; - if(o.axis!=="x"){ - if(d.overflowed[0] && !disabled){ - scrollbar[0].add(mCSB_dragger[0]).add(scrollbar[0].children("a")).css("display","block"); - content.removeClass(classes[8]+" "+classes[10]); - }else{ - if(o.alwaysShowScrollbar){ - if(o.alwaysShowScrollbar!==2){mCSB_dragger[0].css("display","none");} - content.removeClass(classes[10]); - }else{ - scrollbar[0].css("display","none"); - content.addClass(classes[10]); - } - content.addClass(classes[8]); - } - } - if(o.axis!=="y"){ - if(d.overflowed[1] && !disabled){ - scrollbar[1].add(mCSB_dragger[1]).add(scrollbar[1].children("a")).css("display","block"); - content.removeClass(classes[9]+" "+classes[11]); - }else{ - if(o.alwaysShowScrollbar){ - if(o.alwaysShowScrollbar!==2){mCSB_dragger[1].css("display","none");} - content.removeClass(classes[11]); - }else{ - scrollbar[1].css("display","none"); - content.addClass(classes[11]); - } - content.addClass(classes[9]); - } - } - if(!d.overflowed[0] && !d.overflowed[1]){ - $this.addClass(classes[5]); - }else{ - $this.removeClass(classes[5]); - } - }, - /* -------------------- */ - - - /* returns input coordinates of pointer, touch and mouse events (relative to document) */ - _coordinates=function(e){ - var t=e.type,o=e.target.ownerDocument!==document && frameElement!==null ? [$(frameElement).offset().top,$(frameElement).offset().left] : null, - io=_canAccessIFrame() && e.target.ownerDocument!==top.document && frameElement!==null ? [$(e.view.frameElement).offset().top,$(e.view.frameElement).offset().left] : [0,0]; - switch(t){ - case "pointerdown": case "MSPointerDown": case "pointermove": case "MSPointerMove": case "pointerup": case "MSPointerUp": - return o ? [e.originalEvent.pageY-o[0]+io[0],e.originalEvent.pageX-o[1]+io[1],false] : [e.originalEvent.pageY,e.originalEvent.pageX,false]; - break; - case "touchstart": case "touchmove": case "touchend": - var touch=e.originalEvent.touches[0] || e.originalEvent.changedTouches[0], - touches=e.originalEvent.touches.length || e.originalEvent.changedTouches.length; - return e.target.ownerDocument!==document ? [touch.screenY,touch.screenX,touches>1] : [touch.pageY,touch.pageX,touches>1]; - break; - default: - return o ? [e.pageY-o[0]+io[0],e.pageX-o[1]+io[1],false] : [e.pageY,e.pageX,false]; - } - }, - /* -------------------- */ - - - /* - SCROLLBAR DRAG EVENTS - scrolls content via scrollbar dragging - */ - _draggable=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - namespace=pluginPfx+"_"+d.idx, - draggerId=["mCSB_"+d.idx+"_dragger_vertical","mCSB_"+d.idx+"_dragger_horizontal"], - mCSB_container=$("#mCSB_"+d.idx+"_container"), - mCSB_dragger=$("#"+draggerId[0]+",#"+draggerId[1]), - draggable,dragY,dragX, - rds=o.advanced.releaseDraggableSelectors ? mCSB_dragger.add($(o.advanced.releaseDraggableSelectors)) : mCSB_dragger, - eds=o.advanced.extraDraggableSelectors ? $(!_canAccessIFrame() || top.document).add($(o.advanced.extraDraggableSelectors)) : $(!_canAccessIFrame() || top.document); - mCSB_dragger.bind("contextmenu."+namespace,function(e){ - e.preventDefault(); //prevent right click - }).bind("mousedown."+namespace+" touchstart."+namespace+" pointerdown."+namespace+" MSPointerDown."+namespace,function(e){ - e.stopImmediatePropagation(); - e.preventDefault(); - if(!_mouseBtnLeft(e)){return;} /* left mouse button only */ - touchActive=true; - if(oldIE){document.onselectstart=function(){return false;}} /* disable text selection for IE < 9 */ - _iframe.call(mCSB_container,false); /* enable scrollbar dragging over iframes by disabling their events */ - _stop($this); - draggable=$(this); - var offset=draggable.offset(),y=_coordinates(e)[0]-offset.top,x=_coordinates(e)[1]-offset.left, - h=draggable.height()+offset.top,w=draggable.width()+offset.left; - if(y0 && x0){ - dragY=y; - dragX=x; - } - _onDragClasses(draggable,"active",o.autoExpandScrollbar); - }).bind("touchmove."+namespace,function(e){ - e.stopImmediatePropagation(); - e.preventDefault(); - var offset=draggable.offset(),y=_coordinates(e)[0]-offset.top,x=_coordinates(e)[1]-offset.left; - _drag(dragY,dragX,y,x); - }); - $(document).add(eds).bind("mousemove."+namespace+" pointermove."+namespace+" MSPointerMove."+namespace,function(e){ - if(draggable){ - var offset=draggable.offset(),y=_coordinates(e)[0]-offset.top,x=_coordinates(e)[1]-offset.left; - if(dragY===y && dragX===x){return;} /* has it really moved? */ - _drag(dragY,dragX,y,x); - } - }).add(rds).bind("mouseup."+namespace+" touchend."+namespace+" pointerup."+namespace+" MSPointerUp."+namespace,function(e){ - if(draggable){ - _onDragClasses(draggable,"active",o.autoExpandScrollbar); - draggable=null; - } - touchActive=false; - if(oldIE){document.onselectstart=null;} /* enable text selection for IE < 9 */ - _iframe.call(mCSB_container,true); /* enable iframes events */ - }); - function _drag(dragY,dragX,y,x){ - mCSB_container[0].idleTimer=o.scrollInertia<233 ? 250 : 0; - if(draggable.attr("id")===draggerId[1]){ - var dir="x",to=((draggable[0].offsetLeft-dragX)+x)*d.scrollRatio.x; - }else{ - var dir="y",to=((draggable[0].offsetTop-dragY)+y)*d.scrollRatio.y; - } - _scrollTo($this,to.toString(),{dir:dir,drag:true}); - } - }, - /* -------------------- */ - - - /* - TOUCH SWIPE EVENTS - scrolls content via touch swipe - Emulates the native touch-swipe scrolling with momentum found in iOS, Android and WP devices - */ - _contentDraggable=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - namespace=pluginPfx+"_"+d.idx, - mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_container=$("#mCSB_"+d.idx+"_container"), - mCSB_dragger=[$("#mCSB_"+d.idx+"_dragger_vertical"),$("#mCSB_"+d.idx+"_dragger_horizontal")], - draggable,dragY,dragX,touchStartY,touchStartX,touchMoveY=[],touchMoveX=[],startTime,runningTime,endTime,distance,speed,amount, - durA=0,durB,overwrite=o.axis==="yx" ? "none" : "all",touchIntent=[],touchDrag,docDrag, - iframe=mCSB_container.find("iframe"), - events=[ - "touchstart."+namespace+" pointerdown."+namespace+" MSPointerDown."+namespace, //start - "touchmove."+namespace+" pointermove."+namespace+" MSPointerMove."+namespace, //move - "touchend."+namespace+" pointerup."+namespace+" MSPointerUp."+namespace //end - ], - touchAction=document.body.style.touchAction!==undefined && document.body.style.touchAction!==""; - mCSB_container.bind(events[0],function(e){ - _onTouchstart(e); - }).bind(events[1],function(e){ - _onTouchmove(e); - }); - mCustomScrollBox.bind(events[0],function(e){ - _onTouchstart2(e); - }).bind(events[2],function(e){ - _onTouchend(e); - }); - if(iframe.length){ - iframe.each(function(){ - $(this).bind("load",function(){ - /* bind events on accessible iframes */ - if(_canAccessIFrame(this)){ - $(this.contentDocument || this.contentWindow.document).bind(events[0],function(e){ - _onTouchstart(e); - _onTouchstart2(e); - }).bind(events[1],function(e){ - _onTouchmove(e); - }).bind(events[2],function(e){ - _onTouchend(e); - }); - } - }); - }); - } - function _onTouchstart(e){ - if(!_pointerTouch(e) || touchActive || _coordinates(e)[2]){touchable=0; return;} - touchable=1; touchDrag=0; docDrag=0; draggable=1; - $this.removeClass("mCS_touch_action"); - var offset=mCSB_container.offset(); - dragY=_coordinates(e)[0]-offset.top; - dragX=_coordinates(e)[1]-offset.left; - touchIntent=[_coordinates(e)[0],_coordinates(e)[1]]; - } - function _onTouchmove(e){ - if(!_pointerTouch(e) || touchActive || _coordinates(e)[2]){return;} - if(!o.documentTouchScroll){e.preventDefault();} - e.stopImmediatePropagation(); - if(docDrag && !touchDrag){return;} - if(draggable){ - runningTime=_getTime(); - var offset=mCustomScrollBox.offset(),y=_coordinates(e)[0]-offset.top,x=_coordinates(e)[1]-offset.left, - easing="mcsLinearOut"; - touchMoveY.push(y); - touchMoveX.push(x); - touchIntent[2]=Math.abs(_coordinates(e)[0]-touchIntent[0]); touchIntent[3]=Math.abs(_coordinates(e)[1]-touchIntent[1]); - if(d.overflowed[0]){ - var limit=mCSB_dragger[0].parent().height()-mCSB_dragger[0].height(), - prevent=((dragY-y)>0 && (y-dragY)>-(limit*d.scrollRatio.y) && (touchIntent[3]*20 && (x-dragX)>-(limitX*d.scrollRatio.x) && (touchIntent[2]*230){return;} - speed=1000/(endTime-startTime); - var easing="mcsEaseOut",slow=speed<2.5, - diff=slow ? [touchMoveY[touchMoveY.length-2],touchMoveX[touchMoveX.length-2]] : [0,0]; - distance=slow ? [(y-diff[0]),(x-diff[1])] : [y-touchStartY,x-touchStartX]; - var absDistance=[Math.abs(distance[0]),Math.abs(distance[1])]; - speed=slow ? [Math.abs(distance[0]/4),Math.abs(distance[1]/4)] : [speed,speed]; - var a=[ - Math.abs(mCSB_container[0].offsetTop)-(distance[0]*_m((absDistance[0]/speed[0]),speed[0])), - Math.abs(mCSB_container[0].offsetLeft)-(distance[1]*_m((absDistance[1]/speed[1]),speed[1])) - ]; - amount=o.axis==="yx" ? [a[0],a[1]] : o.axis==="x" ? [null,a[1]] : [a[0],null]; - durB=[(absDistance[0]*4)+o.scrollInertia,(absDistance[1]*4)+o.scrollInertia]; - var md=parseInt(o.contentTouchScroll) || 0; /* absolute minimum distance required */ - amount[0]=absDistance[0]>md ? amount[0] : 0; - amount[1]=absDistance[1]>md ? amount[1] : 0; - if(d.overflowed[0]){_drag(amount[0],durB[0],easing,"y",overwrite,false);} - if(d.overflowed[1]){_drag(amount[1],durB[1],easing,"x",overwrite,false);} - } - function _m(ds,s){ - var r=[s*1.5,s*2,s/1.5,s/2]; - if(ds>90){ - return s>4 ? r[0] : r[3]; - }else if(ds>60){ - return s>3 ? r[3] : r[2]; - }else if(ds>30){ - return s>8 ? r[1] : s>6 ? r[0] : s>4 ? s : r[2]; - }else{ - return s>8 ? s : r[3]; - } - } - function _drag(amount,dur,easing,dir,overwrite,drag){ - if(!amount){return;} - _scrollTo($this,amount.toString(),{dur:dur,scrollEasing:easing,dir:dir,overwrite:overwrite,drag:drag}); - } - }, - /* -------------------- */ - - - /* - SELECT TEXT EVENTS - scrolls content when text is selected - */ - _selectable=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt,seq=d.sequential, - namespace=pluginPfx+"_"+d.idx, - mCSB_container=$("#mCSB_"+d.idx+"_container"), - wrapper=mCSB_container.parent(), - action; - mCSB_container.bind("mousedown."+namespace,function(e){ - if(touchable){return;} - if(!action){action=1; touchActive=true;} - }).add(document).bind("mousemove."+namespace,function(e){ - if(!touchable && action && _sel()){ - var offset=mCSB_container.offset(), - y=_coordinates(e)[0]-offset.top+mCSB_container[0].offsetTop,x=_coordinates(e)[1]-offset.left+mCSB_container[0].offsetLeft; - if(y>0 && y0 && xwrapper.height()){ - _seq("on",40); - } - } - if(o.axis!=="y" && d.overflowed[1]){ - if(x<0){ - _seq("on",37); - }else if(x>wrapper.width()){ - _seq("on",39); - } - } - } - } - }).bind("mouseup."+namespace+" dragend."+namespace,function(e){ - if(touchable){return;} - if(action){action=0; _seq("off",null);} - touchActive=false; - }); - function _sel(){ - return window.getSelection ? window.getSelection().toString() : - document.selection && document.selection.type!="Control" ? document.selection.createRange().text : 0; - } - function _seq(a,c,s){ - seq.type=s && action ? "stepped" : "stepless"; - seq.scrollAmount=10; - _sequentialScroll($this,a,c,"mcsLinearOut",s ? 60 : null); - } - }, - /* -------------------- */ - - - /* - MOUSE WHEEL EVENT - scrolls content via mouse-wheel - via mouse-wheel plugin (https://github.com/brandonaaron/jquery-mousewheel) - */ - _mousewheel=function(){ - if(!$(this).data(pluginPfx)){return;} /* Check if the scrollbar is ready to use mousewheel events (issue: #185) */ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - namespace=pluginPfx+"_"+d.idx, - mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_dragger=[$("#mCSB_"+d.idx+"_dragger_vertical"),$("#mCSB_"+d.idx+"_dragger_horizontal")], - iframe=$("#mCSB_"+d.idx+"_container").find("iframe"); - if(iframe.length){ - iframe.each(function(){ - $(this).bind("load",function(){ - /* bind events on accessible iframes */ - if(_canAccessIFrame(this)){ - $(this.contentDocument || this.contentWindow.document).bind("mousewheel."+namespace,function(e,delta){ - _onMousewheel(e,delta); - }); - } - }); - }); - } - mCustomScrollBox.bind("mousewheel."+namespace,function(e,delta){ - _onMousewheel(e,delta); - }); - function _onMousewheel(e,delta){ - _stop($this); - if(_disableMousewheel($this,e.target)){return;} /* disables mouse-wheel when hovering specific elements */ - var deltaFactor=o.mouseWheel.deltaFactor!=="auto" ? parseInt(o.mouseWheel.deltaFactor) : (oldIE && e.deltaFactor<100) ? 100 : e.deltaFactor || 100, - dur=o.scrollInertia; - if(o.axis==="x" || o.mouseWheel.axis==="x"){ - var dir="x", - px=[Math.round(deltaFactor*d.scrollRatio.x),parseInt(o.mouseWheel.scrollAmount)], - amount=o.mouseWheel.scrollAmount!=="auto" ? px[1] : px[0]>=mCustomScrollBox.width() ? mCustomScrollBox.width()*0.9 : px[0], - contentPos=Math.abs($("#mCSB_"+d.idx+"_container")[0].offsetLeft), - draggerPos=mCSB_dragger[1][0].offsetLeft, - limit=mCSB_dragger[1].parent().width()-mCSB_dragger[1].width(), - dlt=o.mouseWheel.axis==="y" ? (e.deltaY || delta) : e.deltaX; - }else{ - var dir="y", - px=[Math.round(deltaFactor*d.scrollRatio.y),parseInt(o.mouseWheel.scrollAmount)], - amount=o.mouseWheel.scrollAmount!=="auto" ? px[1] : px[0]>=mCustomScrollBox.height() ? mCustomScrollBox.height()*0.9 : px[0], - contentPos=Math.abs($("#mCSB_"+d.idx+"_container")[0].offsetTop), - draggerPos=mCSB_dragger[0][0].offsetTop, - limit=mCSB_dragger[0].parent().height()-mCSB_dragger[0].height(), - dlt=e.deltaY || delta; - } - if((dir==="y" && !d.overflowed[0]) || (dir==="x" && !d.overflowed[1])){return;} - if(o.mouseWheel.invert || e.webkitDirectionInvertedFromDevice){dlt=-dlt;} - if(o.mouseWheel.normalizeDelta){dlt=dlt<0 ? -1 : 1;} - if((dlt>0 && draggerPos!==0) || (dlt<0 && draggerPos!==limit) || o.mouseWheel.preventDefault){ - e.stopImmediatePropagation(); - e.preventDefault(); - } - if(e.deltaFactor<5 && !o.mouseWheel.normalizeDelta){ - //very low deltaFactor values mean some kind of delta acceleration (e.g. osx trackpad), so adjusting scrolling accordingly - amount=e.deltaFactor; dur=17; - } - _scrollTo($this,(contentPos-(dlt*amount)).toString(),{dir:dir,dur:dur}); - } - }, - /* -------------------- */ - - - /* checks if iframe can be accessed */ - _canAccessIFrameCache=new Object(), - _canAccessIFrame=function(iframe){ - var result=false,cacheKey=false,html=null; - if(iframe===undefined){ - cacheKey="#empty"; - }else if($(iframe).attr("id")!==undefined){ - cacheKey=$(iframe).attr("id"); - } - if(cacheKey!==false && _canAccessIFrameCache[cacheKey]!==undefined){ - return _canAccessIFrameCache[cacheKey]; - } - if(!iframe){ - try{ - var doc=top.document; - html=doc.body.innerHTML; - }catch(err){/* do nothing */} - result=(html!==null); - }else{ - try{ - var doc=iframe.contentDocument || iframe.contentWindow.document; - html=doc.body.innerHTML; - }catch(err){/* do nothing */} - result=(html!==null); - } - if(cacheKey!==false){_canAccessIFrameCache[cacheKey]=result;} - return result; - }, - /* -------------------- */ - - - /* switches iframe's pointer-events property (drag, mousewheel etc. over cross-domain iframes) */ - _iframe=function(evt){ - var el=this.find("iframe"); - if(!el.length){return;} /* check if content contains iframes */ - var val=!evt ? "none" : "auto"; - el.css("pointer-events",val); /* for IE11, iframe's display property should not be "block" */ - }, - /* -------------------- */ - - - /* disables mouse-wheel when hovering specific elements like select, datalist etc. */ - _disableMousewheel=function(el,target){ - var tag=target.nodeName.toLowerCase(), - tags=el.data(pluginPfx).opt.mouseWheel.disableOver, - /* elements that require focus */ - focusTags=["select","textarea"]; - return $.inArray(tag,tags) > -1 && !($.inArray(tag,focusTags) > -1 && !$(target).is(":focus")); - }, - /* -------------------- */ - - - /* - DRAGGER RAIL CLICK EVENT - scrolls content via dragger rail - */ - _draggerRail=function(){ - var $this=$(this),d=$this.data(pluginPfx), - namespace=pluginPfx+"_"+d.idx, - mCSB_container=$("#mCSB_"+d.idx+"_container"), - wrapper=mCSB_container.parent(), - mCSB_draggerContainer=$(".mCSB_"+d.idx+"_scrollbar ."+classes[12]), - clickable; - mCSB_draggerContainer.bind("mousedown."+namespace+" touchstart."+namespace+" pointerdown."+namespace+" MSPointerDown."+namespace,function(e){ - touchActive=true; - if(!$(e.target).hasClass("mCSB_dragger")){clickable=1;} - }).bind("touchend."+namespace+" pointerup."+namespace+" MSPointerUp."+namespace,function(e){ - touchActive=false; - }).bind("click."+namespace,function(e){ - if(!clickable){return;} - clickable=0; - if($(e.target).hasClass(classes[12]) || $(e.target).hasClass("mCSB_draggerRail")){ - _stop($this); - var el=$(this),mCSB_dragger=el.find(".mCSB_dragger"); - if(el.parent(".mCSB_scrollTools_horizontal").length>0){ - if(!d.overflowed[1]){return;} - var dir="x", - clickDir=e.pageX>mCSB_dragger.offset().left ? -1 : 1, - to=Math.abs(mCSB_container[0].offsetLeft)-(clickDir*(wrapper.width()*0.9)); - }else{ - if(!d.overflowed[0]){return;} - var dir="y", - clickDir=e.pageY>mCSB_dragger.offset().top ? -1 : 1, - to=Math.abs(mCSB_container[0].offsetTop)-(clickDir*(wrapper.height()*0.9)); - } - _scrollTo($this,to.toString(),{dir:dir,scrollEasing:"mcsEaseInOut"}); - } - }); - }, - /* -------------------- */ - - - /* - FOCUS EVENT - scrolls content via element focus (e.g. clicking an input, pressing TAB key etc.) - */ - _focus=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - namespace=pluginPfx+"_"+d.idx, - mCSB_container=$("#mCSB_"+d.idx+"_container"), - wrapper=mCSB_container.parent(); - mCSB_container.bind("focusin."+namespace,function(e){ - var el=$(document.activeElement), - nested=mCSB_container.find(".mCustomScrollBox").length, - dur=0; - if(!el.is(o.advanced.autoScrollOnFocus)){return;} - _stop($this); - clearTimeout($this[0]._focusTimeout); - $this[0]._focusTimer=nested ? (dur+17)*nested : 0; - $this[0]._focusTimeout=setTimeout(function(){ - var to=[_childPos(el)[0],_childPos(el)[1]], - contentPos=[mCSB_container[0].offsetTop,mCSB_container[0].offsetLeft], - isVisible=[ - (contentPos[0]+to[0]>=0 && contentPos[0]+to[0]=0 && contentPos[0]+to[1]a"); - btn.bind("contextmenu."+namespace,function(e){ - e.preventDefault(); //prevent right click - }).bind("mousedown."+namespace+" touchstart."+namespace+" pointerdown."+namespace+" MSPointerDown."+namespace+" mouseup."+namespace+" touchend."+namespace+" pointerup."+namespace+" MSPointerUp."+namespace+" mouseout."+namespace+" pointerout."+namespace+" MSPointerOut."+namespace+" click."+namespace,function(e){ - e.preventDefault(); - if(!_mouseBtnLeft(e)){return;} /* left mouse button only */ - var btnClass=$(this).attr("class"); - seq.type=o.scrollButtons.scrollType; - switch(e.type){ - case "mousedown": case "touchstart": case "pointerdown": case "MSPointerDown": - if(seq.type==="stepped"){return;} - touchActive=true; - d.tweenRunning=false; - _seq("on",btnClass); - break; - case "mouseup": case "touchend": case "pointerup": case "MSPointerUp": - case "mouseout": case "pointerout": case "MSPointerOut": - if(seq.type==="stepped"){return;} - touchActive=false; - if(seq.dir){_seq("off",btnClass);} - break; - case "click": - if(seq.type!=="stepped" || d.tweenRunning){return;} - _seq("on",btnClass); - break; - } - function _seq(a,c){ - seq.scrollAmount=o.scrollButtons.scrollAmount; - _sequentialScroll($this,a,c); - } - }); - }, - /* -------------------- */ - - - /* - KEYBOARD EVENTS - scrolls content via keyboard - Keys: up arrow, down arrow, left arrow, right arrow, PgUp, PgDn, Home, End - */ - _keyboard=function(){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt,seq=d.sequential, - namespace=pluginPfx+"_"+d.idx, - mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_container=$("#mCSB_"+d.idx+"_container"), - wrapper=mCSB_container.parent(), - editables="input,textarea,select,datalist,keygen,[contenteditable='true']", - iframe=mCSB_container.find("iframe"), - events=["blur."+namespace+" keydown."+namespace+" keyup."+namespace]; - if(iframe.length){ - iframe.each(function(){ - $(this).bind("load",function(){ - /* bind events on accessible iframes */ - if(_canAccessIFrame(this)){ - $(this.contentDocument || this.contentWindow.document).bind(events[0],function(e){ - _onKeyboard(e); - }); - } - }); - }); - } - mCustomScrollBox.attr("tabindex","0").bind(events[0],function(e){ - _onKeyboard(e); - }); - function _onKeyboard(e){ - switch(e.type){ - case "blur": - if(d.tweenRunning && seq.dir){_seq("off",null);} - break; - case "keydown": case "keyup": - var code=e.keyCode ? e.keyCode : e.which,action="on"; - if((o.axis!=="x" && (code===38 || code===40)) || (o.axis!=="y" && (code===37 || code===39))){ - /* up (38), down (40), left (37), right (39) arrows */ - if(((code===38 || code===40) && !d.overflowed[0]) || ((code===37 || code===39) && !d.overflowed[1])){return;} - if(e.type==="keyup"){action="off";} - if(!$(document.activeElement).is(editables)){ - e.preventDefault(); - e.stopImmediatePropagation(); - _seq(action,code); - } - }else if(code===33 || code===34){ - /* PgUp (33), PgDn (34) */ - if(d.overflowed[0] || d.overflowed[1]){ - e.preventDefault(); - e.stopImmediatePropagation(); - } - if(e.type==="keyup"){ - _stop($this); - var keyboardDir=code===34 ? -1 : 1; - if(o.axis==="x" || (o.axis==="yx" && d.overflowed[1] && !d.overflowed[0])){ - var dir="x",to=Math.abs(mCSB_container[0].offsetLeft)-(keyboardDir*(wrapper.width()*0.9)); - }else{ - var dir="y",to=Math.abs(mCSB_container[0].offsetTop)-(keyboardDir*(wrapper.height()*0.9)); - } - _scrollTo($this,to.toString(),{dir:dir,scrollEasing:"mcsEaseInOut"}); - } - }else if(code===35 || code===36){ - /* End (35), Home (36) */ - if(!$(document.activeElement).is(editables)){ - if(d.overflowed[0] || d.overflowed[1]){ - e.preventDefault(); - e.stopImmediatePropagation(); - } - if(e.type==="keyup"){ - if(o.axis==="x" || (o.axis==="yx" && d.overflowed[1] && !d.overflowed[0])){ - var dir="x",to=code===35 ? Math.abs(wrapper.width()-mCSB_container.outerWidth(false)) : 0; - }else{ - var dir="y",to=code===35 ? Math.abs(wrapper.height()-mCSB_container.outerHeight(false)) : 0; - } - _scrollTo($this,to.toString(),{dir:dir,scrollEasing:"mcsEaseInOut"}); - } - } - } - break; - } - function _seq(a,c){ - seq.type=o.keyboard.scrollType; - seq.scrollAmount=o.keyboard.scrollAmount; - if(seq.type==="stepped" && d.tweenRunning){return;} - _sequentialScroll($this,a,c); - } - } - }, - /* -------------------- */ - - - /* scrolls content sequentially (used when scrolling via buttons, keyboard arrows etc.) */ - _sequentialScroll=function(el,action,trigger,e,s){ - var d=el.data(pluginPfx),o=d.opt,seq=d.sequential, - mCSB_container=$("#mCSB_"+d.idx+"_container"), - once=seq.type==="stepped" ? true : false, - steplessSpeed=o.scrollInertia < 26 ? 26 : o.scrollInertia, /* 26/1.5=17 */ - steppedSpeed=o.scrollInertia < 1 ? 17 : o.scrollInertia; - switch(action){ - case "on": - seq.dir=[ - (trigger===classes[16] || trigger===classes[15] || trigger===39 || trigger===37 ? "x" : "y"), - (trigger===classes[13] || trigger===classes[15] || trigger===38 || trigger===37 ? -1 : 1) - ]; - _stop(el); - if(_isNumeric(trigger) && seq.type==="stepped"){return;} - _on(once); - break; - case "off": - _off(); - if(once || (d.tweenRunning && seq.dir)){ - _on(true); - } - break; - } - - /* starts sequence */ - function _on(once){ - if(o.snapAmount){seq.scrollAmount=!(o.snapAmount instanceof Array) ? o.snapAmount : seq.dir[0]==="x" ? o.snapAmount[1] : o.snapAmount[0];} /* scrolling snapping */ - var c=seq.type!=="stepped", /* continuous scrolling */ - t=s ? s : !once ? 1000/60 : c ? steplessSpeed/1.5 : steppedSpeed, /* timer */ - m=!once ? 2.5 : c ? 7.5 : 40, /* multiplier */ - contentPos=[Math.abs(mCSB_container[0].offsetTop),Math.abs(mCSB_container[0].offsetLeft)], - ratio=[d.scrollRatio.y>10 ? 10 : d.scrollRatio.y,d.scrollRatio.x>10 ? 10 : d.scrollRatio.x], - amount=seq.dir[0]==="x" ? contentPos[1]+(seq.dir[1]*(ratio[1]*m)) : contentPos[0]+(seq.dir[1]*(ratio[0]*m)), - px=seq.dir[0]==="x" ? contentPos[1]+(seq.dir[1]*parseInt(seq.scrollAmount)) : contentPos[0]+(seq.dir[1]*parseInt(seq.scrollAmount)), - to=seq.scrollAmount!=="auto" ? px : amount, - easing=e ? e : !once ? "mcsLinear" : c ? "mcsLinearOut" : "mcsEaseInOut", - onComplete=!once ? false : true; - if(once && t<17){ - to=seq.dir[0]==="x" ? contentPos[1] : contentPos[0]; - } - _scrollTo(el,to.toString(),{dir:seq.dir[0],scrollEasing:easing,dur:t,onComplete:onComplete}); - if(once){ - seq.dir=false; - return; - } - clearTimeout(seq.step); - seq.step=setTimeout(function(){ - _on(); - },t); - } - /* stops sequence */ - function _off(){ - clearTimeout(seq.step); - _delete(seq,"step"); - _stop(el); - } - }, - /* -------------------- */ - - - /* returns a yx array from value */ - _arr=function(val){ - var o=$(this).data(pluginPfx).opt,vals=[]; - if(typeof val==="function"){val=val();} /* check if the value is a single anonymous function */ - /* check if value is object or array, its length and create an array with yx values */ - if(!(val instanceof Array)){ /* object value (e.g. {y:"100",x:"100"}, 100 etc.) */ - vals[0]=val.y ? val.y : val.x || o.axis==="x" ? null : val; - vals[1]=val.x ? val.x : val.y || o.axis==="y" ? null : val; - }else{ /* array value (e.g. [100,100]) */ - vals=val.length>1 ? [val[0],val[1]] : o.axis==="x" ? [null,val[0]] : [val[0],null]; - } - /* check if array values are anonymous functions */ - if(typeof vals[0]==="function"){vals[0]=vals[0]();} - if(typeof vals[1]==="function"){vals[1]=vals[1]();} - return vals; - }, - /* -------------------- */ - - - /* translates values (e.g. "top", 100, "100px", "#id") to actual scroll-to positions */ - _to=function(val,dir){ - if(val==null || typeof val=="undefined"){return;} - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - mCSB_container=$("#mCSB_"+d.idx+"_container"), - wrapper=mCSB_container.parent(), - t=typeof val; - if(!dir){dir=o.axis==="x" ? "x" : "y";} - var contentLength=dir==="x" ? mCSB_container.outerWidth(false)-wrapper.width() : mCSB_container.outerHeight(false)-wrapper.height(), - contentPos=dir==="x" ? mCSB_container[0].offsetLeft : mCSB_container[0].offsetTop, - cssProp=dir==="x" ? "left" : "top"; - switch(t){ - case "function": /* this currently is not used. Consider removing it */ - return val(); - break; - case "object": /* js/jquery object */ - var obj=val.jquery ? val : $(val); - if(!obj.length){return;} - return dir==="x" ? _childPos(obj)[1] : _childPos(obj)[0]; - break; - case "string": case "number": - if(_isNumeric(val)){ /* numeric value */ - return Math.abs(val); - }else if(val.indexOf("%")!==-1){ /* percentage value */ - return Math.abs(contentLength*parseInt(val)/100); - }else if(val.indexOf("-=")!==-1){ /* decrease value */ - return Math.abs(contentPos-parseInt(val.split("-=")[1])); - }else if(val.indexOf("+=")!==-1){ /* inrease value */ - var p=(contentPos+parseInt(val.split("+=")[1])); - return p>=0 ? 0 : Math.abs(p); - }else if(val.indexOf("px")!==-1 && _isNumeric(val.split("px")[0])){ /* pixels string value (e.g. "100px") */ - return Math.abs(val.split("px")[0]); - }else{ - if(val==="top" || val==="left"){ /* special strings */ - return 0; - }else if(val==="bottom"){ - return Math.abs(wrapper.height()-mCSB_container.outerHeight(false)); - }else if(val==="right"){ - return Math.abs(wrapper.width()-mCSB_container.outerWidth(false)); - }else if(val==="first" || val==="last"){ - var obj=mCSB_container.find(":"+val); - return dir==="x" ? _childPos(obj)[1] : _childPos(obj)[0]; - }else{ - if($(val).length){ /* jquery selector */ - return dir==="x" ? _childPos($(val))[1] : _childPos($(val))[0]; - }else{ /* other values (e.g. "100em") */ - mCSB_container.css(cssProp,val); - methods.update.call(null,$this[0]); - return; - } - } - } - break; - } - }, - /* -------------------- */ - - - /* calls the update method automatically */ - _autoUpdate=function(rem){ - var $this=$(this),d=$this.data(pluginPfx),o=d.opt, - mCSB_container=$("#mCSB_"+d.idx+"_container"); - if(rem){ - /* - removes autoUpdate timer - usage: _autoUpdate.call(this,"remove"); - */ - clearTimeout(mCSB_container[0].autoUpdate); - _delete(mCSB_container[0],"autoUpdate"); - return; - } - upd(); - function upd(){ - clearTimeout(mCSB_container[0].autoUpdate); - if($this.parents("html").length===0){ - /* check element in dom tree */ - $this=null; - return; - } - mCSB_container[0].autoUpdate=setTimeout(function(){ - /* update on specific selector(s) length and size change */ - if(o.advanced.updateOnSelectorChange){ - d.poll.change.n=sizesSum(); - if(d.poll.change.n!==d.poll.change.o){ - d.poll.change.o=d.poll.change.n; - doUpd(3); - return; - } - } - /* update on main element and scrollbar size changes */ - if(o.advanced.updateOnContentResize){ - d.poll.size.n=$this[0].scrollHeight+$this[0].scrollWidth+mCSB_container[0].offsetHeight+$this[0].offsetHeight+$this[0].offsetWidth; - if(d.poll.size.n!==d.poll.size.o){ - d.poll.size.o=d.poll.size.n; - doUpd(1); - return; - } - } - /* update on image load */ - if(o.advanced.updateOnImageLoad){ - if(!(o.advanced.updateOnImageLoad==="auto" && o.axis==="y")){ //by default, it doesn't run on vertical content - d.poll.img.n=mCSB_container.find("img").length; - if(d.poll.img.n!==d.poll.img.o){ - d.poll.img.o=d.poll.img.n; - mCSB_container.find("img").each(function(){ - imgLoader(this); - }); - return; - } - } - } - if(o.advanced.updateOnSelectorChange || o.advanced.updateOnContentResize || o.advanced.updateOnImageLoad){upd();} - },o.advanced.autoUpdateTimeout); - } - /* a tiny image loader */ - function imgLoader(el){ - if($(el).hasClass(classes[2])){doUpd(); return;} - var img=new Image(); - function createDelegate(contextObject,delegateMethod){ - return function(){return delegateMethod.apply(contextObject,arguments);} - } - function imgOnLoad(){ - this.onload=null; - $(el).addClass(classes[2]); - doUpd(2); - } - img.onload=createDelegate(img,imgOnLoad); - img.src=el.src; - } - /* returns the total height and width sum of all elements matching the selector */ - function sizesSum(){ - if(o.advanced.updateOnSelectorChange===true){o.advanced.updateOnSelectorChange="*";} - var total=0,sel=mCSB_container.find(o.advanced.updateOnSelectorChange); - if(o.advanced.updateOnSelectorChange && sel.length>0){sel.each(function(){total+=this.offsetHeight+this.offsetWidth;});} - return total; - } - /* calls the update method */ - function doUpd(cb){ - clearTimeout(mCSB_container[0].autoUpdate); - methods.update.call(null,$this[0],cb); - } - }, - /* -------------------- */ - - - /* snaps scrolling to a multiple of a pixels number */ - _snapAmount=function(to,amount,offset){ - return (Math.round(to/amount)*amount-offset); - }, - /* -------------------- */ - - - /* stops content and scrollbar animations */ - _stop=function(el){ - var d=el.data(pluginPfx), - sel=$("#mCSB_"+d.idx+"_container,#mCSB_"+d.idx+"_container_wrapper,#mCSB_"+d.idx+"_dragger_vertical,#mCSB_"+d.idx+"_dragger_horizontal"); - sel.each(function(){ - _stopTween.call(this); - }); - }, - /* -------------------- */ - - - /* - ANIMATES CONTENT - This is where the actual scrolling happens - */ - _scrollTo=function(el,to,options){ - var d=el.data(pluginPfx),o=d.opt, - defaults={ - trigger:"internal", - dir:"y", - scrollEasing:"mcsEaseOut", - drag:false, - dur:o.scrollInertia, - overwrite:"all", - callbacks:true, - onStart:true, - onUpdate:true, - onComplete:true - }, - options=$.extend(defaults,options), - dur=[options.dur,(options.drag ? 0 : options.dur)], - mCustomScrollBox=$("#mCSB_"+d.idx), - mCSB_container=$("#mCSB_"+d.idx+"_container"), - wrapper=mCSB_container.parent(), - totalScrollOffsets=o.callbacks.onTotalScrollOffset ? _arr.call(el,o.callbacks.onTotalScrollOffset) : [0,0], - totalScrollBackOffsets=o.callbacks.onTotalScrollBackOffset ? _arr.call(el,o.callbacks.onTotalScrollBackOffset) : [0,0]; - d.trigger=options.trigger; - if(wrapper.scrollTop()!==0 || wrapper.scrollLeft()!==0){ /* always reset scrollTop/Left */ - $(".mCSB_"+d.idx+"_scrollbar").css("visibility","visible"); - wrapper.scrollTop(0).scrollLeft(0); - } - if(to==="_resetY" && !d.contentReset.y){ - /* callbacks: onOverflowYNone */ - if(_cb("onOverflowYNone")){o.callbacks.onOverflowYNone.call(el[0]);} - d.contentReset.y=1; - } - if(to==="_resetX" && !d.contentReset.x){ - /* callbacks: onOverflowXNone */ - if(_cb("onOverflowXNone")){o.callbacks.onOverflowXNone.call(el[0]);} - d.contentReset.x=1; - } - if(to==="_resetY" || to==="_resetX"){return;} - if((d.contentReset.y || !el[0].mcs) && d.overflowed[0]){ - /* callbacks: onOverflowY */ - if(_cb("onOverflowY")){o.callbacks.onOverflowY.call(el[0]);} - d.contentReset.x=null; - } - if((d.contentReset.x || !el[0].mcs) && d.overflowed[1]){ - /* callbacks: onOverflowX */ - if(_cb("onOverflowX")){o.callbacks.onOverflowX.call(el[0]);} - d.contentReset.x=null; - } - if(o.snapAmount){ /* scrolling snapping */ - var snapAmount=!(o.snapAmount instanceof Array) ? o.snapAmount : options.dir==="x" ? o.snapAmount[1] : o.snapAmount[0]; - to=_snapAmount(to,snapAmount,o.snapOffset); - } - switch(options.dir){ - case "x": - var mCSB_dragger=$("#mCSB_"+d.idx+"_dragger_horizontal"), - property="left", - contentPos=mCSB_container[0].offsetLeft, - limit=[ - mCustomScrollBox.width()-mCSB_container.outerWidth(false), - mCSB_dragger.parent().width()-mCSB_dragger.width() - ], - scrollTo=[to,to===0 ? 0 : (to/d.scrollRatio.x)], - tso=totalScrollOffsets[1], - tsbo=totalScrollBackOffsets[1], - totalScrollOffset=tso>0 ? tso/d.scrollRatio.x : 0, - totalScrollBackOffset=tsbo>0 ? tsbo/d.scrollRatio.x : 0; - break; - case "y": - var mCSB_dragger=$("#mCSB_"+d.idx+"_dragger_vertical"), - property="top", - contentPos=mCSB_container[0].offsetTop, - limit=[ - mCustomScrollBox.height()-mCSB_container.outerHeight(false), - mCSB_dragger.parent().height()-mCSB_dragger.height() - ], - scrollTo=[to,to===0 ? 0 : (to/d.scrollRatio.y)], - tso=totalScrollOffsets[0], - tsbo=totalScrollBackOffsets[0], - totalScrollOffset=tso>0 ? tso/d.scrollRatio.y : 0, - totalScrollBackOffset=tsbo>0 ? tsbo/d.scrollRatio.y : 0; - break; - } - if(scrollTo[1]<0 || (scrollTo[0]===0 && scrollTo[1]===0)){ - scrollTo=[0,0]; - }else if(scrollTo[1]>=limit[1]){ - scrollTo=[limit[0],limit[1]]; - }else{ - scrollTo[0]=-scrollTo[0]; - } - if(!el[0].mcs){ - _mcs(); /* init mcs object (once) to make it available before callbacks */ - if(_cb("onInit")){o.callbacks.onInit.call(el[0]);} /* callbacks: onInit */ - } - clearTimeout(mCSB_container[0].onCompleteTimeout); - _tweenTo(mCSB_dragger[0],property,Math.round(scrollTo[1]),dur[1],options.scrollEasing); - if(!d.tweenRunning && ((contentPos===0 && scrollTo[0]>=0) || (contentPos===limit[0] && scrollTo[0]<=limit[0]))){return;} - _tweenTo(mCSB_container[0],property,Math.round(scrollTo[0]),dur[0],options.scrollEasing,options.overwrite,{ - onStart:function(){ - if(options.callbacks && options.onStart && !d.tweenRunning){ - /* callbacks: onScrollStart */ - if(_cb("onScrollStart")){_mcs(); o.callbacks.onScrollStart.call(el[0]);} - d.tweenRunning=true; - _onDragClasses(mCSB_dragger); - d.cbOffsets=_cbOffsets(); - } - },onUpdate:function(){ - if(options.callbacks && options.onUpdate){ - /* callbacks: whileScrolling */ - if(_cb("whileScrolling")){_mcs(); o.callbacks.whileScrolling.call(el[0]);} - } - },onComplete:function(){ - if(options.callbacks && options.onComplete){ - if(o.axis==="yx"){clearTimeout(mCSB_container[0].onCompleteTimeout);} - var t=mCSB_container[0].idleTimer || 0; - mCSB_container[0].onCompleteTimeout=setTimeout(function(){ - /* callbacks: onScroll, onTotalScroll, onTotalScrollBack */ - if(_cb("onScroll")){_mcs(); o.callbacks.onScroll.call(el[0]);} - if(_cb("onTotalScroll") && scrollTo[1]>=limit[1]-totalScrollOffset && d.cbOffsets[0]){_mcs(); o.callbacks.onTotalScroll.call(el[0]);} - if(_cb("onTotalScrollBack") && scrollTo[1]<=totalScrollBackOffset && d.cbOffsets[1]){_mcs(); o.callbacks.onTotalScrollBack.call(el[0]);} - d.tweenRunning=false; - mCSB_container[0].idleTimer=0; - _onDragClasses(mCSB_dragger,"hide"); - },t); - } - } - }); - /* checks if callback function exists */ - function _cb(cb){ - return d && o.callbacks[cb] && typeof o.callbacks[cb]==="function"; - } - /* checks whether callback offsets always trigger */ - function _cbOffsets(){ - return [o.callbacks.alwaysTriggerOffsets || contentPos>=limit[0]+tso,o.callbacks.alwaysTriggerOffsets || contentPos<=-tsbo]; - } - /* - populates object with useful values for the user - values: - content: this.mcs.content - content top position: this.mcs.top - content left position: this.mcs.left - dragger top position: this.mcs.draggerTop - dragger left position: this.mcs.draggerLeft - scrolling y percentage: this.mcs.topPct - scrolling x percentage: this.mcs.leftPct - scrolling direction: this.mcs.direction - */ - function _mcs(){ - var cp=[mCSB_container[0].offsetTop,mCSB_container[0].offsetLeft], /* content position */ - dp=[mCSB_dragger[0].offsetTop,mCSB_dragger[0].offsetLeft], /* dragger position */ - cl=[mCSB_container.outerHeight(false),mCSB_container.outerWidth(false)], /* content length */ - pl=[mCustomScrollBox.height(),mCustomScrollBox.width()]; /* content parent length */ - el[0].mcs={ - content:mCSB_container, /* original content wrapper as jquery object */ - top:cp[0],left:cp[1],draggerTop:dp[0],draggerLeft:dp[1], - topPct:Math.round((100*Math.abs(cp[0]))/(Math.abs(cl[0])-pl[0])),leftPct:Math.round((100*Math.abs(cp[1]))/(Math.abs(cl[1])-pl[1])), - direction:options.dir - }; - /* - this refers to the original element containing the scrollbar(s) - usage: this.mcs.top, this.mcs.leftPct etc. - */ - } - }, - /* -------------------- */ - - - /* - CUSTOM JAVASCRIPT ANIMATION TWEEN - Lighter and faster than jquery animate() and css transitions - Animates top/left properties and includes easings - */ - _tweenTo=function(el,prop,to,duration,easing,overwrite,callbacks){ - if(!el._mTween){el._mTween={top:{},left:{}};} - var callbacks=callbacks || {}, - onStart=callbacks.onStart || function(){},onUpdate=callbacks.onUpdate || function(){},onComplete=callbacks.onComplete || function(){}, - startTime=_getTime(),_delay,progress=0,from=el.offsetTop,elStyle=el.style,_request,tobj=el._mTween[prop]; - if(prop==="left"){from=el.offsetLeft;} - var diff=to-from; - tobj.stop=0; - if(overwrite!=="none"){_cancelTween();} - _startTween(); - function _step(){ - if(tobj.stop){return;} - if(!progress){onStart.call();} - progress=_getTime()-startTime; - _tween(); - if(progress>=tobj.time){ - tobj.time=(progress>tobj.time) ? progress+_delay-(progress-tobj.time) : progress+_delay-1; - if(tobj.time0){ - tobj.currVal=_ease(tobj.time,from,diff,duration,easing); - elStyle[prop]=Math.round(tobj.currVal)+"px"; - }else{ - elStyle[prop]=to+"px"; - } - onUpdate.call(); - } - function _startTween(){ - _delay=1000/60; - tobj.time=progress+_delay; - _request=(!window.requestAnimationFrame) ? function(f){_tween(); return setTimeout(f,0.01);} : window.requestAnimationFrame; - tobj.id=_request(_step); - } - function _cancelTween(){ - if(tobj.id==null){return;} - if(!window.requestAnimationFrame){clearTimeout(tobj.id); - }else{window.cancelAnimationFrame(tobj.id);} - tobj.id=null; - } - function _ease(t,b,c,d,type){ - switch(type){ - case "linear": case "mcsLinear": - return c*t/d + b; - break; - case "mcsLinearOut": - t/=d; t--; return c * Math.sqrt(1 - t*t) + b; - break; - case "easeInOutSmooth": - t/=d/2; - if(t<1) return c/2*t*t + b; - t--; - return -c/2 * (t*(t-2) - 1) + b; - break; - case "easeInOutStrong": - t/=d/2; - if(t<1) return c/2 * Math.pow( 2, 10 * (t - 1) ) + b; - t--; - return c/2 * ( -Math.pow( 2, -10 * t) + 2 ) + b; - break; - case "easeInOut": case "mcsEaseInOut": - t/=d/2; - if(t<1) return c/2*t*t*t + b; - t-=2; - return c/2*(t*t*t + 2) + b; - break; - case "easeOutSmooth": - t/=d; t--; - return -c * (t*t*t*t - 1) + b; - break; - case "easeOutStrong": - return c * ( -Math.pow( 2, -10 * t/d ) + 1 ) + b; - break; - case "easeOut": case "mcsEaseOut": default: - var ts=(t/=d)*t,tc=ts*t; - return b+c*(0.499999999999997*tc*ts + -2.5*ts*ts + 5.5*tc + -6.5*ts + 4*t); - } - } - }, - /* -------------------- */ - - - /* returns current time */ - _getTime=function(){ - if(window.performance && window.performance.now){ - return window.performance.now(); - }else{ - if(window.performance && window.performance.webkitNow){ - return window.performance.webkitNow(); - }else{ - if(Date.now){return Date.now();}else{return new Date().getTime();} - } - } - }, - /* -------------------- */ - - - /* stops a tween */ - _stopTween=function(){ - var el=this; - if(!el._mTween){el._mTween={top:{},left:{}};} - var props=["top","left"]; - for(var i=0; i