From b379c97142bcf5a9b30e93f77e63fea868377ed5 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Tue, 26 Nov 2024 12:54:52 +0100 Subject: [PATCH] Index UI (#1145) * Temp * Temp * Indexes UI. * UI for indexes finalized. * Revert text * More formatting. --- backend/i18n/frontend_en.json | 26 +- backend/i18n/frontend_fr.json | 26 +- backend/i18n/frontend_it.json | 26 +- backend/i18n/frontend_nl.json | 26 +- backend/i18n/frontend_pt.json | 26 +- backend/i18n/frontend_zh.json | 26 +- backend/i18n/source/backend_en.json | 5 - backend/i18n/source/frontend_en.json | 26 +- .../Processes/CheckFrontend.cs | 3 +- .../Processes/TranslateBackend.cs | 7 +- .../Processes/TranslateTemplates.cs | 4 +- .../Contents/MongoContentRepository.cs | 4 +- .../Contents/ContentCache.cs | 2 +- .../{ContentOptions.cs => ContentsOptions.cs} | 2 +- .../Contents/Queries/ContentQueryParser.cs | 4 +- .../Contents/Queries/ContentQueryService.cs | 4 +- backend/src/Squidex.Shared/Texts.fr.resx | 15 - backend/src/Squidex.Shared/Texts.it.resx | 15 - backend/src/Squidex.Shared/Texts.nl.resx | 15 - backend/src/Squidex.Shared/Texts.pt.resx | 15 - backend/src/Squidex.Shared/Texts.resx | 15 - backend/src/Squidex.Shared/Texts.zh.resx | 15 - .../src/Squidex.Web/Services/UrlGenerator.cs | 4 +- .../Schemas/SchemaIndexesController.cs | 2 +- .../Frontend/Middlewares/IndexExtensions.cs | 1 - .../Squidex/Config/Domain/ContentsServices.cs | 2 +- .../Squidex/Config/Domain/FontendServices.cs | 8 + backend/src/Squidex/appsettings.json | 3 + .../Contents/GraphQL/GraphQLTestBase.cs | 2 +- .../Contents/MongoDb/ContentsQueryFixture.cs | 2 +- .../Queries/ContentQueryParserTests.cs | 2 +- .../Queries/ContentQueryServiceTests.cs | 2 +- frontend/.prettierrc | 1 + frontend/package-lock.json | 8 +- frontend/package.json | 2 +- frontend/src/app/_theme.html | 2694 +++++---- .../apps/pages/apps-page.component.html | 2 +- .../pages/onboarding-dialog.component.html | 6 +- .../apps/pages/onboarding-dialog.component.ts | 5 +- .../assets/pages/assets-page.component.html | 2 +- .../editor/content-editor.component.html | 10 +- .../editor/content-editor.component.ts | 5 +- .../shared/forms/assets-editor.component.html | 11 +- .../pages/dashboard-page.component.html | 4 +- .../pages/dashboard-page.component.ts | 5 +- .../rules/pages/rules/rule.component.html | 2 +- .../fields/schema-fields.component.html | 1 + .../fields/types/assets-ui.component.html | 4 +- .../fields/types/assets-ui.component.ts | 5 +- .../fields/types/date-time-ui.component.html | 2 +- .../fields/types/date-time-ui.component.ts | 5 +- .../schema/indexes/index-form.component.html | 73 + .../schema/indexes/index-form.component.scss | 2 + .../schema/indexes/index-form.component.ts | 100 + .../pages/schema/indexes/index.component.html | 57 + .../pages/schema/indexes/index.component.scss | 12 + .../pages/schema/indexes/index.component.ts | 39 + .../indexes/schema-indexes.component.html | 55 + .../indexes/schema-indexes.component.scss | 22 + .../indexes/schema-indexes.component.ts | 57 + .../pages/schema/schema-page.component.html | 10 + .../pages/schema/schema-page.component.ts | 8 +- .../client-connect-form.component.html | 8 +- .../clients/client-connect-form.component.ts | 5 +- .../pages/clients/client.component.html | 4 +- .../settings/pages/jobs/job.component.html | 4 +- .../settings/pages/jobs/job.component.ts | 8 +- .../pages/plans/plans-page.component.html | 6 +- .../pages/plans/plans-page.component.ts | 5 +- .../pages/templates/template.component.html | 2 +- .../pages/templates/template.component.ts | 5 +- .../templates/templates-page.component.html | 2 +- .../templates/templates-page.component.ts | 5 +- .../dashboard/dashboard-page.component.html | 4 +- .../dashboard/dashboard-page.component.ts | 5 +- .../teams/pages/more/more-page.component.html | 2 +- .../pages/plans/plans-page.component.html | 6 +- .../teams/pages/plans/plans-page.component.ts | 5 +- .../forms/editors/autocomplete.component.scss | 7 - .../angular/forms/form-error.component.html | 2 +- .../angular/forms/form-error.component.ts | 6 +- .../modals/dialog-renderer.component.html | 4 +- .../modals/dialog-renderer.component.ts | 6 +- .../modals/modal-dialog.component.html | 6 +- .../modals/tour-template.component.html | 4 +- .../angular/modals/tour-template.component.ts | 7 +- frontend/src/app/framework/index.ts | 1 - .../components/chat-dialog.component.ts | 3 +- .../components/chat-item.component.html | 6 +- .../comments/comment.component.html | 6 +- .../components/comments/comment.component.ts | 5 +- .../search/search-form.component.html | 2 +- .../search/search-form.component.ts | 5 +- frontend/src/app/shared/internal.ts | 7 +- .../shared/services/indexes.service.spec.ts | 116 + .../app/shared/services/indexes.service.ts | 93 + .../app/shared/services/jobs.service.spec.ts | 4 +- .../src/app/shared/state/indexes.forms.ts | 36 + .../app/shared/state/indexes.state.spec.ts | 132 + .../src/app/shared/state/indexes.state.ts | 114 + frontend/src/app/theme/icomoon/demo.html | 5016 ++++++++--------- frontend/src/index.html | 98 +- 102 files changed, 5090 insertions(+), 4216 deletions(-) rename backend/src/Squidex.Domain.Apps.Entities/Contents/{ContentOptions.cs => ContentsOptions.cs} (95%) create mode 100644 frontend/src/app/features/schemas/pages/schema/indexes/index-form.component.html create mode 100644 frontend/src/app/features/schemas/pages/schema/indexes/index-form.component.scss create mode 100644 frontend/src/app/features/schemas/pages/schema/indexes/index-form.component.ts create mode 100644 frontend/src/app/features/schemas/pages/schema/indexes/index.component.html create mode 100644 frontend/src/app/features/schemas/pages/schema/indexes/index.component.scss create mode 100644 frontend/src/app/features/schemas/pages/schema/indexes/index.component.ts create mode 100644 frontend/src/app/features/schemas/pages/schema/indexes/schema-indexes.component.html create mode 100644 frontend/src/app/features/schemas/pages/schema/indexes/schema-indexes.component.scss create mode 100644 frontend/src/app/features/schemas/pages/schema/indexes/schema-indexes.component.ts create mode 100644 frontend/src/app/shared/services/indexes.service.spec.ts create mode 100644 frontend/src/app/shared/services/indexes.service.ts create mode 100644 frontend/src/app/shared/state/indexes.forms.ts create mode 100644 frontend/src/app/shared/state/indexes.state.spec.ts create mode 100644 frontend/src/app/shared/state/indexes.state.ts diff --git a/backend/i18n/frontend_en.json b/backend/i18n/frontend_en.json index 5489001582..b8f0921685 100644 --- a/backend/i18n/frontend_en.json +++ b/backend/i18n/frontend_en.json @@ -136,16 +136,11 @@ "assets.uploadHint": "Drop file on existing item to replace the asset with a newer version.", "assets.viewReferences": "View all content items referencing this asset.", "assetScripts.reloaded": "Asset Scripts reloaded.", - "chat.answer": "Here is my answer:", - "chat.answersEmpty": "The ChatBot does not provide an answer or has not been configured yet.", "chat.ask": "Ask", - "chat.describeFormat": "Also add the desired format (for example Markdown or HTML) to your prompt, dependending on the editor that you use.", - "chat.description": "Use the ChatBot (usually OpenAI) to generate content. Just write a prompt and describe the content you want to generate.", "chat.failed": "Failed to answer your request.", "chat.prompt": "Describe the content you want to generate", "chat.title": "Chat Bot", "chat.use": "Use", - "chatBot.questionFailed": "", "clients.add": "Add Client", "clients.add.description": "Add a client to give other applications access to your content.", "clients.add.title": "Add a new Client", @@ -322,6 +317,7 @@ "common.openAPI": "Open API", "common.options": "Options", "common.or": "or", + "common.order": "Order", "common.pagerInfo": "{itemFirst}-{itemLast} of {numberOfItems}", "common.pagerInfoNoTotal": "{itemFirst}-{itemLast} of total?", "common.pagerReload": "Click to reload view and get total number of items", @@ -353,6 +349,7 @@ "common.references": "References", "common.refresh": "Refresh", "common.remember": "Don't ask again", + "common.remove": "remove", "common.rename": "Rename", "common.renameTag": "Rename Tag", "common.reply": "Reply", @@ -957,6 +954,23 @@ "schemas.fieldTypes.ui.description": "Separator for editing UI.", "schemas.hideFieldFailed": "Failed to hide field. Please reload.", "schemas.import": "Import schema", + "schemas.indexes.addIndex": "Add Index", + "schemas.indexes.addIndexButton": "Add Index", + "schemas.indexes.addTitle": "Add Index", + "schemas.indexes.created": "Index has been scheduled for creation. It might take a while, depending on the number of existing content items. This page is updated automatically.", + "schemas.indexes.createFailed": "Failed to create index. Please reload.", + "schemas.indexes.deleteConfirmText": "Delete index", + "schemas.indexes.deleteConfirmTitle": "Do you really want to remove the index?", + "schemas.indexes.deleted": "Index has been scheduled for deletion. It might take a while, depending on the number of existing content items. This page is updated automatically.", + "schemas.indexes.deleteFailed": "Failed to create index. Please reload.", + "schemas.indexes.deleteFieldConfirmText": "Remove field", + "schemas.indexes.deleteFieldConfirmTitle": "Do you really want to remove the field?", + "schemas.indexes.empty": "No index created yet.", + "schemas.indexes.hint": "Indexes can be expensive. Analyze your queries and read the following documentation before creating an index: [Documentation](https://www.mongodb.com/docs/manual/core/indexes/index-types/index-compound/)", + "schemas.indexes.loadFailed": "Failed to load indexes. Please reload.", + "schemas.indexes.notEnableHint1": "Indexes cannot be created. You have turn on a feature, which creates a dedicated collection per schema. Use the following environment variable:", + "schemas.indexes.notEnableHint2": "Please note, that this does not insert your existing content items to the new collections automatically. You have to run a migration by setting the following environment variable. Start Squidex with rebuild setting once and wait for completion. Then turn it off again.", + "schemas.indexes.reloaded": "Indexes reloaded.", "schemas.listFields": "List Fields", "schemas.listFieldsEmpty": "Drop field here or reorder them to show the fields in the content list. When no list field is defined, the first field is used.", "schemas.loadFailed": "Failed to load schemas. Please reload.", @@ -986,6 +1000,7 @@ "schemas.rules.title": "Field Rules", "schemas.rules.when": "when", "schemas.saved": "Schema saved successfully.", + "schemas.saveField": "Save Field", "schemas.saveFieldAndClose": "Save and close", "schemas.saveFieldAndNew": "Save and add field", "schemas.schemaHintsHint": "Describe this schema for documentation and user interfaces.", @@ -998,6 +1013,7 @@ "schemas.synchronized": "Schema synchronized successfully.", "schemas.synchronizeFailed": "Failed to synchronize schema. Please reload.", "schemas.tabFields": "Fields", + "schemas.tabIndexes": "Indexes", "schemas.tabJson": "Json", "schemas.tableHeaders.created": "Created", "schemas.tableHeaders.created_title": "Created", diff --git a/backend/i18n/frontend_fr.json b/backend/i18n/frontend_fr.json index 8ddc7fd065..5b29735ba6 100644 --- a/backend/i18n/frontend_fr.json +++ b/backend/i18n/frontend_fr.json @@ -136,16 +136,11 @@ "assets.uploadHint": "Déposez le fichier sur un élément existant pour remplacer l'actif par une version plus récente.", "assets.viewReferences": "Afficher tous les éléments de contenu faisant référence à cet élément.", "assetScripts.reloaded": "Scripts d'actif rechargés.", - "chat.answer": "Here is my answer:", - "chat.answersEmpty": "The ChatBot does not provide an answer or has not been configured yet.", "chat.ask": "Ask", - "chat.describeFormat": "Also add the desired format (for example Markdown or HTML) to your prompt, dependending on the editor that you use.", - "chat.description": "Use the ChatBot (usually OpenAI) to generate content. Just write a prompt and describe the content you want to generate.", "chat.failed": "Failed to answer your request.", "chat.prompt": "Describe the content you want to generate", "chat.title": "Chat Bot", "chat.use": "Use", - "chatBot.questionFailed": "", "clients.add": "Ajouter un client", "clients.add.description": "Ajoutez un client pour permettre à d'autres applications d'accéder à votre contenu.", "clients.add.title": "Ajouter un nouveau client", @@ -322,6 +317,7 @@ "common.openAPI": "Ouvrir l'API", "common.options": "Options", "common.or": "ou", + "common.order": "Order", "common.pagerInfo": "{itemFirst}-{itemLast} de {numberOfItems}", "common.pagerInfoNoTotal": "{itemFirst}-{itemLast} Du total?", "common.pagerReload": "Cliquez pour recharger la vue et obtenir le nombre total d'articles", @@ -353,6 +349,7 @@ "common.references": "Les références", "common.refresh": "Rafraîchir", "common.remember": "Ne demande plus", + "common.remove": "remove", "common.rename": "Renommer", "common.renameTag": "Renommer la balise", "common.reply": "Reply", @@ -957,6 +954,23 @@ "schemas.fieldTypes.ui.description": "Séparateur pour l'édition de l'interface utilisateur.", "schemas.hideFieldFailed": "Impossible de masquer le champ. Veuillez recharger.", "schemas.import": "Calendrier d'importation", + "schemas.indexes.addIndex": "Add Index", + "schemas.indexes.addIndexButton": "Add Index", + "schemas.indexes.addTitle": "Add Index", + "schemas.indexes.created": "Index has been scheduled for creation. It might take a while, depending on the number of existing content items. This page is updated automatically.", + "schemas.indexes.createFailed": "Failed to create index. Please reload.", + "schemas.indexes.deleteConfirmText": "Delete index", + "schemas.indexes.deleteConfirmTitle": "Do you really want to remove the index?", + "schemas.indexes.deleted": "Index has been scheduled for deletion. It might take a while, depending on the number of existing content items. This page is updated automatically.", + "schemas.indexes.deleteFailed": "Failed to create index. Please reload.", + "schemas.indexes.deleteFieldConfirmText": "Remove field", + "schemas.indexes.deleteFieldConfirmTitle": "Do you really want to remove the field?", + "schemas.indexes.empty": "No index created yet.", + "schemas.indexes.hint": "Indexes can be expensive. Analyze your queries and read the following documentation before creating an index: [Documentation](https://www.mongodb.com/docs/manual/core/indexes/index-types/index-compound/)", + "schemas.indexes.loadFailed": "Failed to load indexes. Please reload.", + "schemas.indexes.notEnableHint1": "Indexes cannot be created. You have turn on a feature, which creates a dedicated collection per schema. Use the following environment variable:", + "schemas.indexes.notEnableHint2": "Please note, that this does not insert your existing content items to the new collections automatically. You have to run a migration by setting the following environment variable. Start Squidex with rebuild setting once and wait for completion. Then turn it off again.", + "schemas.indexes.reloaded": "Indexes reloaded.", "schemas.listFields": "Champs de liste", "schemas.listFieldsEmpty": "Déposez le champ ici ou réorganisez-les pour afficher les champs dans la liste de contenu. Lorsqu'aucun champ de liste n'est défini, le premier champ est utilisé.", "schemas.loadFailed": "Échec du chargement des schémas. Veuillez recharger.", @@ -986,6 +1000,7 @@ "schemas.rules.title": "Règles de champ", "schemas.rules.when": "quand", "schemas.saved": "Schéma enregistré avec succès.", + "schemas.saveField": "Save Field", "schemas.saveFieldAndClose": "Sauver et fermer", "schemas.saveFieldAndNew": "Enregistrer et ajouter un champ", "schemas.schemaHintsHint": "Décrivez ce schéma pour la documentation et les interfaces utilisateur.", @@ -998,6 +1013,7 @@ "schemas.synchronized": "Schéma synchronisé avec succès.", "schemas.synchronizeFailed": "Échec de la synchronisation du schéma. Veuillez recharger.", "schemas.tabFields": "Des champs", + "schemas.tabIndexes": "Indexes", "schemas.tabJson": "Json", "schemas.tableHeaders.created": "Créé", "schemas.tableHeaders.created_title": "Created", diff --git a/backend/i18n/frontend_it.json b/backend/i18n/frontend_it.json index 35f0de45fd..3009efb599 100644 --- a/backend/i18n/frontend_it.json +++ b/backend/i18n/frontend_it.json @@ -136,16 +136,11 @@ "assets.uploadHint": "Trascina il file sull'elemento esistente per poterlo sostituire con una versione più recente.", "assets.viewReferences": "View all content items referencing this asset.", "assetScripts.reloaded": "Asset Scripts reloaded.", - "chat.answer": "Here is my answer:", - "chat.answersEmpty": "The ChatBot does not provide an answer or has not been configured yet.", "chat.ask": "Ask", - "chat.describeFormat": "Also add the desired format (for example Markdown or HTML) to your prompt, dependending on the editor that you use.", - "chat.description": "Use the ChatBot (usually OpenAI) to generate content. Just write a prompt and describe the content you want to generate.", "chat.failed": "Failed to answer your request.", "chat.prompt": "Describe the content you want to generate", "chat.title": "Chat Bot", "chat.use": "Use", - "chatBot.questionFailed": "", "clients.add": "Aggiungi un Client", "clients.add.description": "Add a client to give other applications access to your content.", "clients.add.title": "Add a new Client", @@ -322,6 +317,7 @@ "common.openAPI": "Open API", "common.options": "Options", "common.or": "o", + "common.order": "Order", "common.pagerInfo": "{itemFirst}-{itemLast} of {numberOfItems}", "common.pagerInfoNoTotal": "{itemFirst}-{itemLast} of total?", "common.pagerReload": "Click to reload view and get total number of items", @@ -353,6 +349,7 @@ "common.references": "References", "common.refresh": "Aggiorna", "common.remember": "Ricorda la mia decisione", + "common.remove": "remove", "common.rename": "Rinomina", "common.renameTag": "Rename Tag", "common.reply": "Reply", @@ -957,6 +954,23 @@ "schemas.fieldTypes.ui.description": "Separatore per il pannello delle modifiche della UI.", "schemas.hideFieldFailed": "Non è stato possibile nascondere il campo. Per favore ricarica.", "schemas.import": "Importa uno schema", + "schemas.indexes.addIndex": "Add Index", + "schemas.indexes.addIndexButton": "Add Index", + "schemas.indexes.addTitle": "Add Index", + "schemas.indexes.created": "Index has been scheduled for creation. It might take a while, depending on the number of existing content items. This page is updated automatically.", + "schemas.indexes.createFailed": "Failed to create index. Please reload.", + "schemas.indexes.deleteConfirmText": "Delete index", + "schemas.indexes.deleteConfirmTitle": "Do you really want to remove the index?", + "schemas.indexes.deleted": "Index has been scheduled for deletion. It might take a while, depending on the number of existing content items. This page is updated automatically.", + "schemas.indexes.deleteFailed": "Failed to create index. Please reload.", + "schemas.indexes.deleteFieldConfirmText": "Remove field", + "schemas.indexes.deleteFieldConfirmTitle": "Do you really want to remove the field?", + "schemas.indexes.empty": "No index created yet.", + "schemas.indexes.hint": "Indexes can be expensive. Analyze your queries and read the following documentation before creating an index: [Documentation](https://www.mongodb.com/docs/manual/core/indexes/index-types/index-compound/)", + "schemas.indexes.loadFailed": "Failed to load indexes. Please reload.", + "schemas.indexes.notEnableHint1": "Indexes cannot be created. You have turn on a feature, which creates a dedicated collection per schema. Use the following environment variable:", + "schemas.indexes.notEnableHint2": "Please note, that this does not insert your existing content items to the new collections automatically. You have to run a migration by setting the following environment variable. Start Squidex with rebuild setting once and wait for completion. Then turn it off again.", + "schemas.indexes.reloaded": "Indexes reloaded.", "schemas.listFields": "Lista dei Campi", "schemas.listFieldsEmpty": "Incolla qui il campo o riordina i campi da mostrare nella lista dei contenuti. Se non imposti una lista di campi da visualizzare, viene utilizzato il primo della lista.", "schemas.loadFailed": "Non è stato possibile caricare gli schemi. Per favore ricarica.", @@ -986,6 +1000,7 @@ "schemas.rules.title": "Regole per il campo", "schemas.rules.when": "quando", "schemas.saved": "Lo Schema è stato salvato con successo.", + "schemas.saveField": "Save Field", "schemas.saveFieldAndClose": "Salva e chiudi", "schemas.saveFieldAndNew": "Salva e aggiungi un campo", "schemas.schemaHintsHint": "Descrivi questo schema per la documentazione e le interfacce utente.", @@ -998,6 +1013,7 @@ "schemas.synchronized": "Lo Schema è stato sincronizzato con successo.", "schemas.synchronizeFailed": "Non è stato possibile sincronizzare lo schema. Per favore ricarica.", "schemas.tabFields": "Campi", + "schemas.tabIndexes": "Indexes", "schemas.tabJson": "Json", "schemas.tableHeaders.created": "Creato", "schemas.tableHeaders.created_title": "Created", diff --git a/backend/i18n/frontend_nl.json b/backend/i18n/frontend_nl.json index 28b2e13f90..5c781cf473 100644 --- a/backend/i18n/frontend_nl.json +++ b/backend/i18n/frontend_nl.json @@ -136,16 +136,11 @@ "assets.uploadHint": "Zet het bestand neer op bestaand item om het bestand te vervangen door een nieuwere versie.", "assets.viewReferences": "View all content items referencing this asset.", "assetScripts.reloaded": "Asset Scripts reloaded.", - "chat.answer": "Here is my answer:", - "chat.answersEmpty": "The ChatBot does not provide an answer or has not been configured yet.", "chat.ask": "Ask", - "chat.describeFormat": "Also add the desired format (for example Markdown or HTML) to your prompt, dependending on the editor that you use.", - "chat.description": "Use the ChatBot (usually OpenAI) to generate content. Just write a prompt and describe the content you want to generate.", "chat.failed": "Failed to answer your request.", "chat.prompt": "Describe the content you want to generate", "chat.title": "Chat Bot", "chat.use": "Use", - "chatBot.questionFailed": "", "clients.add": "Client toevoegen", "clients.add.description": "Voeg een client toe om andere applicaties toegang te geven tot uw inhoud.", "clients.add.title": "Client toevoegen", @@ -322,6 +317,7 @@ "common.openAPI": "Open API", "common.options": "Options", "common.or": "of", + "common.order": "Order", "common.pagerInfo": "{itemFirst} - {itemLast} van {numberOfItems}", "common.pagerInfoNoTotal": "{itemFirst}-{itemLast} of total?", "common.pagerReload": "Click to reload view and get total number of items", @@ -353,6 +349,7 @@ "common.references": "References", "common.refresh": "Vernieuwen", "common.remember": "Onthoud mijn keuze", + "common.remove": "remove", "common.rename": "Hernoemen", "common.renameTag": "Hernoem Tag", "common.reply": "Reply", @@ -957,6 +954,23 @@ "schemas.fieldTypes.ui.description": "Scheidingsteken voor het bewerken van gebruikersinterface.", "schemas.hideFieldFailed": "Kan veld niet verbergen. Laad opnieuw.", "schemas.import": "Importeer schema", + "schemas.indexes.addIndex": "Add Index", + "schemas.indexes.addIndexButton": "Add Index", + "schemas.indexes.addTitle": "Add Index", + "schemas.indexes.created": "Index has been scheduled for creation. It might take a while, depending on the number of existing content items. This page is updated automatically.", + "schemas.indexes.createFailed": "Failed to create index. Please reload.", + "schemas.indexes.deleteConfirmText": "Delete index", + "schemas.indexes.deleteConfirmTitle": "Do you really want to remove the index?", + "schemas.indexes.deleted": "Index has been scheduled for deletion. It might take a while, depending on the number of existing content items. This page is updated automatically.", + "schemas.indexes.deleteFailed": "Failed to create index. Please reload.", + "schemas.indexes.deleteFieldConfirmText": "Remove field", + "schemas.indexes.deleteFieldConfirmTitle": "Do you really want to remove the field?", + "schemas.indexes.empty": "No index created yet.", + "schemas.indexes.hint": "Indexes can be expensive. Analyze your queries and read the following documentation before creating an index: [Documentation](https://www.mongodb.com/docs/manual/core/indexes/index-types/index-compound/)", + "schemas.indexes.loadFailed": "Failed to load indexes. Please reload.", + "schemas.indexes.notEnableHint1": "Indexes cannot be created. You have turn on a feature, which creates a dedicated collection per schema. Use the following environment variable:", + "schemas.indexes.notEnableHint2": "Please note, that this does not insert your existing content items to the new collections automatically. You have to run a migration by setting the following environment variable. Start Squidex with rebuild setting once and wait for completion. Then turn it off again.", + "schemas.indexes.reloaded": "Indexes reloaded.", "schemas.listFields": "Lijstvelden", "schemas.listFieldsEmpty": "Zet het veld hier neer of rangschik ze opnieuw om de velden in de inhoudslijst weer te geven. Als er geen lijstveld is gedefinieerd, wordt het eerste veld gebruikt.", "schemas.loadFailed": "Kan schema's niet laden. Laad opnieuw.", @@ -986,6 +1000,7 @@ "schemas.rules.title": "Veldregels", "schemas.rules.when": "Wanneer", "schemas.saved": "Schema succesvol opgeslagen.", + "schemas.saveField": "Save Field", "schemas.saveFieldAndClose": "Opslaan en sluiten", "schemas.saveFieldAndNew": "Opslaan en veld toevoegen", "schemas.schemaHintsHint": "Beschrijf dit schema voor documentatie en gebruikersinterfaces.", @@ -998,6 +1013,7 @@ "schemas.synchronized": "Schema is succesvol gesynchroniseerd.", "schemas.synchronizeFailed": "Synchroniseren van schema is mislukt. Laad opnieuw.", "schemas.tabFields": "Velden", + "schemas.tabIndexes": "Indexes", "schemas.tabJson": "Json", "schemas.tableHeaders.created": "Gemaakt", "schemas.tableHeaders.created_title": "Created", diff --git a/backend/i18n/frontend_pt.json b/backend/i18n/frontend_pt.json index 5558e0acbe..ec04a0e251 100644 --- a/backend/i18n/frontend_pt.json +++ b/backend/i18n/frontend_pt.json @@ -136,16 +136,11 @@ "assets.uploadHint": "Deixe cair o ficheiro no item existente para substituir o ficheiro por uma versão mais recente.", "assets.viewReferences": "View all content items referencing this asset.", "assetScripts.reloaded": "Scripts de ficheiros recarregados.", - "chat.answer": "Here is my answer:", - "chat.answersEmpty": "The ChatBot does not provide an answer or has not been configured yet.", "chat.ask": "Ask", - "chat.describeFormat": "Also add the desired format (for example Markdown or HTML) to your prompt, dependending on the editor that you use.", - "chat.description": "Use the ChatBot (usually OpenAI) to generate content. Just write a prompt and describe the content you want to generate.", "chat.failed": "Failed to answer your request.", "chat.prompt": "Describe the content you want to generate", "chat.title": "Chat Bot", "chat.use": "Use", - "chatBot.questionFailed": "", "clients.add": "Adicionar Cliente", "clients.add.description": "Adicione um cliente para dar a outras aplicações acesso ao seu conteúdo.", "clients.add.title": "Adicionar um novo Cliente", @@ -322,6 +317,7 @@ "common.openAPI": "Open API", "common.options": "Options", "common.or": "ou", + "common.order": "Order", "common.pagerInfo": "{itemFirst}-{itemLast} de {numberOfItems}", "common.pagerInfoNoTotal": "{itemFirst}-{itemLast} do total?", "common.pagerReload": "Clique para recarregar a vista e obter o número total de itens", @@ -353,6 +349,7 @@ "common.references": "References", "common.refresh": "Refrescar", "common.remember": "Não pergunte de novo.", + "common.remove": "remove", "common.rename": "Renomear", "common.renameTag": "Renomear Etiqueta", "common.reply": "Reply", @@ -957,6 +954,23 @@ "schemas.fieldTypes.ui.description": "Separador para edição de UI.", "schemas.hideFieldFailed": "Falhou em esconder o campo. Por favor, recarregue.", "schemas.import": "Esquema de importação", + "schemas.indexes.addIndex": "Add Index", + "schemas.indexes.addIndexButton": "Add Index", + "schemas.indexes.addTitle": "Add Index", + "schemas.indexes.created": "Index has been scheduled for creation. It might take a while, depending on the number of existing content items. This page is updated automatically.", + "schemas.indexes.createFailed": "Failed to create index. Please reload.", + "schemas.indexes.deleteConfirmText": "Delete index", + "schemas.indexes.deleteConfirmTitle": "Do you really want to remove the index?", + "schemas.indexes.deleted": "Index has been scheduled for deletion. It might take a while, depending on the number of existing content items. This page is updated automatically.", + "schemas.indexes.deleteFailed": "Failed to create index. Please reload.", + "schemas.indexes.deleteFieldConfirmText": "Remove field", + "schemas.indexes.deleteFieldConfirmTitle": "Do you really want to remove the field?", + "schemas.indexes.empty": "No index created yet.", + "schemas.indexes.hint": "Indexes can be expensive. Analyze your queries and read the following documentation before creating an index: [Documentation](https://www.mongodb.com/docs/manual/core/indexes/index-types/index-compound/)", + "schemas.indexes.loadFailed": "Failed to load indexes. Please reload.", + "schemas.indexes.notEnableHint1": "Indexes cannot be created. You have turn on a feature, which creates a dedicated collection per schema. Use the following environment variable:", + "schemas.indexes.notEnableHint2": "Please note, that this does not insert your existing content items to the new collections automatically. You have to run a migration by setting the following environment variable. Start Squidex with rebuild setting once and wait for completion. Then turn it off again.", + "schemas.indexes.reloaded": "Indexes reloaded.", "schemas.listFields": "Campos de Lista", "schemas.listFieldsEmpty": "Deixe cair aqui ou reencomenda-os para mostrar os campos na lista de conteúdos. Quando não é definido nenhum campo de lista, o primeiro campo é utilizado.", "schemas.loadFailed": "Falhou em carregar esquemas. Por favor, recarregue.", @@ -986,6 +1000,7 @@ "schemas.rules.title": "Regras de Campo", "schemas.rules.when": "quando", "schemas.saved": "Esqquema salvo com sucesso.", + "schemas.saveField": "Save Field", "schemas.saveFieldAndClose": "Salvar e fechar", "schemas.saveFieldAndNew": "Guardar e adicionar campo", "schemas.schemaHintsHint": "Descreva este esquema para documentação e interfaces de utilizador.", @@ -998,6 +1013,7 @@ "schemas.synchronized": "Esquema sincronizado com sucesso.", "schemas.synchronizeFailed": "Falhou em sincronizar o esquema. Por favor, recarregue.", "schemas.tabFields": "Campos", + "schemas.tabIndexes": "Indexes", "schemas.tabJson": "Json", "schemas.tableHeaders.created": "Criado", "schemas.tableHeaders.created_title": "Created", diff --git a/backend/i18n/frontend_zh.json b/backend/i18n/frontend_zh.json index 4d6114a7dd..161dfaef72 100644 --- a/backend/i18n/frontend_zh.json +++ b/backend/i18n/frontend_zh.json @@ -136,16 +136,11 @@ "assets.uploadHint": "在现有项目上放置文件以使用更新版本替换资源。", "assets.viewReferences": "View all content items referencing this asset.", "assetScripts.reloaded": "Asset Scripts reloaded.", - "chat.answer": "Here is my answer:", - "chat.answersEmpty": "The ChatBot does not provide an answer or has not been configured yet.", "chat.ask": "Ask", - "chat.describeFormat": "Also add the desired format (for example Markdown or HTML) to your prompt, dependending on the editor that you use.", - "chat.description": "Use the ChatBot (usually OpenAI) to generate content. Just write a prompt and describe the content you want to generate.", "chat.failed": "Failed to answer your request.", "chat.prompt": "Describe the content you want to generate", "chat.title": "Chat Bot", "chat.use": "Use", - "chatBot.questionFailed": "", "clients.add": "添加客户端", "clients.add.description": "Add a client to give other applications access to your content.", "clients.add.title": "Add a new Client", @@ -322,6 +317,7 @@ "common.openAPI": "Open API", "common.options": "Options", "common.or": "或", + "common.order": "Order", "common.pagerInfo": "{itemFirst}-{itemLast} 的 {numberOfItems}", "common.pagerInfoNoTotal": "{itemFirst}-{itemLast} of total?", "common.pagerReload": "Click to reload view and get total number of items", @@ -353,6 +349,7 @@ "common.references": "References", "common.refresh": "刷新", "common.remember": "不要再问了", + "common.remove": "remove", "common.rename": "重命名", "common.renameTag": "Rename Tag", "common.reply": "Reply", @@ -957,6 +954,23 @@ "schemas.fieldTypes.ui.description": "编辑 UI 的分隔符。", "schemas.hideFieldFailed": "隐藏字段失败。请重新加载。", "schemas.import": "导入Schemas", + "schemas.indexes.addIndex": "Add Index", + "schemas.indexes.addIndexButton": "Add Index", + "schemas.indexes.addTitle": "Add Index", + "schemas.indexes.created": "Index has been scheduled for creation. It might take a while, depending on the number of existing content items. This page is updated automatically.", + "schemas.indexes.createFailed": "Failed to create index. Please reload.", + "schemas.indexes.deleteConfirmText": "Delete index", + "schemas.indexes.deleteConfirmTitle": "Do you really want to remove the index?", + "schemas.indexes.deleted": "Index has been scheduled for deletion. It might take a while, depending on the number of existing content items. This page is updated automatically.", + "schemas.indexes.deleteFailed": "Failed to create index. Please reload.", + "schemas.indexes.deleteFieldConfirmText": "Remove field", + "schemas.indexes.deleteFieldConfirmTitle": "Do you really want to remove the field?", + "schemas.indexes.empty": "No index created yet.", + "schemas.indexes.hint": "Indexes can be expensive. Analyze your queries and read the following documentation before creating an index: [Documentation](https://www.mongodb.com/docs/manual/core/indexes/index-types/index-compound/)", + "schemas.indexes.loadFailed": "Failed to load indexes. Please reload.", + "schemas.indexes.notEnableHint1": "Indexes cannot be created. You have turn on a feature, which creates a dedicated collection per schema. Use the following environment variable:", + "schemas.indexes.notEnableHint2": "Please note, that this does not insert your existing content items to the new collections automatically. You have to run a migration by setting the following environment variable. Start Squidex with rebuild setting once and wait for completion. Then turn it off again.", + "schemas.indexes.reloaded": "Indexes reloaded.", "schemas.listFields": "列表字段", "schemas.listFieldsEmpty": "将字段拖放到此处或重新排序以显示内容列表中的字段。当未定义列表字段时,使用第一个字段。", "schemas.loadFailed": "加载Schemas失败。请重新加载。", @@ -986,6 +1000,7 @@ "schemas.rules.title": "字段规则", "schemas.rules.when": "何时", "schemas.saved": "Schema saved successfully.", + "schemas.saveField": "Save Field", "schemas.saveFieldAndClose": "保存并关闭", "schemas.saveFieldAndNew": "保存并添加字段", "schemas.schemaHintsHint": "为文档和用户界面描述这个Schemas。", @@ -998,6 +1013,7 @@ "schemas.synchronized": "Schema synchronized successfully.", "schemas.synchronizeFailed": "同步Schemas失败。请重新加载。", "schemas.tabFields": "字段", + "schemas.tabIndexes": "Indexes", "schemas.tabJson": "Json", "schemas.tableHeaders.created": "创建", "schemas.tableHeaders.created_title": "Created", diff --git a/backend/i18n/source/backend_en.json b/backend/i18n/source/backend_en.json index 534f5b2d97..810a6650fc 100644 --- a/backend/i18n/source/backend_en.json +++ b/backend/i18n/source/backend_en.json @@ -349,16 +349,11 @@ "users.lockedOutText": "Your account is locked, please contact the administrator.", "users.lockedOutTitle": "Account locked", "users.lockYourselfError": "You cannot lock yourself.", - "users.login.askAdmin": "", "users.login.custom": "Enter your E-Mail Address to login with your Company Account and single sign on (SSO).", "users.login.emailBusinessPlaceholder": "Enter Business Email", "users.login.emailPlaceholder": "Enter Email", "users.login.error": "Email or password not correct", "users.login.loginWith": "{action} with {provider}", - "users.login.noAccountLoginAction": "Click here to login", - "users.login.noAccountLoginQuestion": "Already registered?", - "users.login.noAccountSignupAction": "Click here to signup", - "users.login.noAccountSignupQuestion": "No account yet?", "users.login.passwordPlaceholder": "Enter Password", "users.login.separator": "OR", "users.logout.headline": "Logged out!", diff --git a/backend/i18n/source/frontend_en.json b/backend/i18n/source/frontend_en.json index 5489001582..b8f0921685 100644 --- a/backend/i18n/source/frontend_en.json +++ b/backend/i18n/source/frontend_en.json @@ -136,16 +136,11 @@ "assets.uploadHint": "Drop file on existing item to replace the asset with a newer version.", "assets.viewReferences": "View all content items referencing this asset.", "assetScripts.reloaded": "Asset Scripts reloaded.", - "chat.answer": "Here is my answer:", - "chat.answersEmpty": "The ChatBot does not provide an answer or has not been configured yet.", "chat.ask": "Ask", - "chat.describeFormat": "Also add the desired format (for example Markdown or HTML) to your prompt, dependending on the editor that you use.", - "chat.description": "Use the ChatBot (usually OpenAI) to generate content. Just write a prompt and describe the content you want to generate.", "chat.failed": "Failed to answer your request.", "chat.prompt": "Describe the content you want to generate", "chat.title": "Chat Bot", "chat.use": "Use", - "chatBot.questionFailed": "", "clients.add": "Add Client", "clients.add.description": "Add a client to give other applications access to your content.", "clients.add.title": "Add a new Client", @@ -322,6 +317,7 @@ "common.openAPI": "Open API", "common.options": "Options", "common.or": "or", + "common.order": "Order", "common.pagerInfo": "{itemFirst}-{itemLast} of {numberOfItems}", "common.pagerInfoNoTotal": "{itemFirst}-{itemLast} of total?", "common.pagerReload": "Click to reload view and get total number of items", @@ -353,6 +349,7 @@ "common.references": "References", "common.refresh": "Refresh", "common.remember": "Don't ask again", + "common.remove": "remove", "common.rename": "Rename", "common.renameTag": "Rename Tag", "common.reply": "Reply", @@ -957,6 +954,23 @@ "schemas.fieldTypes.ui.description": "Separator for editing UI.", "schemas.hideFieldFailed": "Failed to hide field. Please reload.", "schemas.import": "Import schema", + "schemas.indexes.addIndex": "Add Index", + "schemas.indexes.addIndexButton": "Add Index", + "schemas.indexes.addTitle": "Add Index", + "schemas.indexes.created": "Index has been scheduled for creation. It might take a while, depending on the number of existing content items. This page is updated automatically.", + "schemas.indexes.createFailed": "Failed to create index. Please reload.", + "schemas.indexes.deleteConfirmText": "Delete index", + "schemas.indexes.deleteConfirmTitle": "Do you really want to remove the index?", + "schemas.indexes.deleted": "Index has been scheduled for deletion. It might take a while, depending on the number of existing content items. This page is updated automatically.", + "schemas.indexes.deleteFailed": "Failed to create index. Please reload.", + "schemas.indexes.deleteFieldConfirmText": "Remove field", + "schemas.indexes.deleteFieldConfirmTitle": "Do you really want to remove the field?", + "schemas.indexes.empty": "No index created yet.", + "schemas.indexes.hint": "Indexes can be expensive. Analyze your queries and read the following documentation before creating an index: [Documentation](https://www.mongodb.com/docs/manual/core/indexes/index-types/index-compound/)", + "schemas.indexes.loadFailed": "Failed to load indexes. Please reload.", + "schemas.indexes.notEnableHint1": "Indexes cannot be created. You have turn on a feature, which creates a dedicated collection per schema. Use the following environment variable:", + "schemas.indexes.notEnableHint2": "Please note, that this does not insert your existing content items to the new collections automatically. You have to run a migration by setting the following environment variable. Start Squidex with rebuild setting once and wait for completion. Then turn it off again.", + "schemas.indexes.reloaded": "Indexes reloaded.", "schemas.listFields": "List Fields", "schemas.listFieldsEmpty": "Drop field here or reorder them to show the fields in the content list. When no list field is defined, the first field is used.", "schemas.loadFailed": "Failed to load schemas. Please reload.", @@ -986,6 +1000,7 @@ "schemas.rules.title": "Field Rules", "schemas.rules.when": "when", "schemas.saved": "Schema saved successfully.", + "schemas.saveField": "Save Field", "schemas.saveFieldAndClose": "Save and close", "schemas.saveFieldAndNew": "Save and add field", "schemas.schemaHintsHint": "Describe this schema for documentation and user interfaces.", @@ -998,6 +1013,7 @@ "schemas.synchronized": "Schema synchronized successfully.", "schemas.synchronizeFailed": "Failed to synchronize schema. Please reload.", "schemas.tabFields": "Fields", + "schemas.tabIndexes": "Indexes", "schemas.tabJson": "Json", "schemas.tableHeaders.created": "Created", "schemas.tableHeaders.created_title": "Created", diff --git a/backend/i18n/translator/Squidex.Translator/Processes/CheckFrontend.cs b/backend/i18n/translator/Squidex.Translator/Processes/CheckFrontend.cs index 0bb25864bf..c99fec479a 100644 --- a/backend/i18n/translator/Squidex.Translator/Processes/CheckFrontend.cs +++ b/backend/i18n/translator/Squidex.Translator/Processes/CheckFrontend.cs @@ -92,7 +92,8 @@ void AddTranslations(string regex) AddTranslations("\"i18n\\:(?[^\"]+)\""); AddTranslations("\'i18n\\:(?[^\']+)\'"); - AddTranslations("'(?[^\']+)' \\| sqxTranslate"); + AddTranslations("\"(?[^\"]+)\"[\\s]*\\|[\\s]*sqxTranslate"); + AddTranslations("\'(?[^\']+)\'[\\s]*\\|[\\s]*sqxTranslate"); return translations; } diff --git a/backend/i18n/translator/Squidex.Translator/Processes/TranslateBackend.cs b/backend/i18n/translator/Squidex.Translator/Processes/TranslateBackend.cs index e3aa99de68..fbeafd26ea 100644 --- a/backend/i18n/translator/Squidex.Translator/Processes/TranslateBackend.cs +++ b/backend/i18n/translator/Squidex.Translator/Processes/TranslateBackend.cs @@ -10,7 +10,7 @@ namespace Squidex.Translator.Processes; -public class TranslateBackend(DirectoryInfo folder, TranslationService service) +public partial class TranslateBackend(DirectoryInfo folder, TranslationService service) { private readonly DirectoryInfo folder = Backend.GetFolder(folder); @@ -22,7 +22,7 @@ public void Run() var isReplaced = false; - content = Regex.Replace(content, "\"[^\"]*\"", match => + content = VariableRegex().Replace(content, match => { var value = match.Value[1..^1]; @@ -50,4 +50,7 @@ public void Run() } } } + + [GeneratedRegex("\"[^\"]*\"")] + private static partial Regex VariableRegex(); } diff --git a/backend/i18n/translator/Squidex.Translator/Processes/TranslateTemplates.cs b/backend/i18n/translator/Squidex.Translator/Processes/TranslateTemplates.cs index 1df15de85c..cf570cc79b 100644 --- a/backend/i18n/translator/Squidex.Translator/Processes/TranslateTemplates.cs +++ b/backend/i18n/translator/Squidex.Translator/Processes/TranslateTemplates.cs @@ -114,7 +114,7 @@ private void Traverse(string fileName, HtmlNode node) else { // Keep our original indentation. - textNode.Text = originalPrefix + $"{{{{ '{key}' | sqxTranslate }}}}" + originalSuffix; + textNode.Text = originalPrefix + $"{{{{ \"{key}\" | sqxTranslate }}}}" + originalSuffix; isReplaced = true; } @@ -140,7 +140,7 @@ private void Traverse(string fileName, HtmlNode node) { if (attribute.Name.Contains('[', StringComparison.Ordinal)) { - node.SetAttributeValue(attribute.Name, $"{{{{ '{key}' | sqxTranslate }}}}"); + node.SetAttributeValue(attribute.Name, $"{{{{ \"{key}\" | sqxTranslate }}}}"); } else { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs index 7a956dc8c9..eec20accf2 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs @@ -28,7 +28,7 @@ public partial class MongoContentRepository( IMongoDatabase database, IAppProvider appProvider, string shardKey, - IOptions options, + IOptions options, ILogger log) : MongoBase, IContentRepository, IInitializable { @@ -38,7 +38,7 @@ public partial class MongoContentRepository( private readonly MongoContentCollection collectionPublished = new MongoContentCollection($"States_Contents_Published3{shardKey}", database, log, ReadPreference.Secondary, options.Value.OptimizeForSelfHosting); - private readonly ContentOptions options = options.Value; + private readonly ContentsOptions options = options.Value; public bool CanUseTransactions { get; private set; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentCache.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentCache.cs index adcbb8b72a..3049648036 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentCache.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentCache.cs @@ -12,6 +12,6 @@ namespace Squidex.Domain.Apps.Entities.Contents; -public sealed class ContentCache(IMemoryCache? memoryCache, IOptions options) : QueryCache(options.Value.CanCache ? memoryCache : null), IContentCache +public sealed class ContentCache(IMemoryCache? memoryCache, IOptions options) : QueryCache(options.Value.CanCache ? memoryCache : null), IContentCache { } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentOptions.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentsOptions.cs similarity index 95% rename from backend/src/Squidex.Domain.Apps.Entities/Contents/ContentOptions.cs rename to backend/src/Squidex.Domain.Apps.Entities/Contents/ContentsOptions.cs index 54bf13745c..13d98283df 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentOptions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentsOptions.cs @@ -7,7 +7,7 @@ namespace Squidex.Domain.Apps.Entities.Contents; -public sealed class ContentOptions +public sealed class ContentsOptions { public bool CanCache { get; set; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs index 49ac54d9fc..756ad666fa 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs @@ -27,12 +27,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries; public class ContentQueryParser( IAppProvider appprovider, ITextIndex textIndex, - IOptions options, + IOptions options, IMemoryCache cache, IJsonSerializer serializer) { private static readonly TimeSpan CacheTime = TimeSpan.FromMinutes(60); - private readonly ContentOptions options = options.Value; + private readonly ContentsOptions options = options.Value; public virtual async Task ParseAsync(Context context, Q q, Schema? schema = null, CancellationToken ct = default) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs index 48b2748c7a..39f60bd209 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs @@ -22,12 +22,12 @@ public sealed class ContentQueryService( IContentEnricher contentEnricher, IContentRepository contentRepository, IContentLoader contentLoader, - IOptions options, + IOptions options, ContentQueryParser queryParser) : IContentQueryService { private const string SingletonId = "_schemaId_"; - private readonly ContentOptions options = options.Value; + private readonly ContentsOptions options = options.Value; public async IAsyncEnumerable StreamAsync(Context context, string schemaIdOrName, int skip, [EnumeratorCancellation] CancellationToken ct = default) diff --git a/backend/src/Squidex.Shared/Texts.fr.resx b/backend/src/Squidex.Shared/Texts.fr.resx index b7420196ea..7a2a7a86bd 100644 --- a/backend/src/Squidex.Shared/Texts.fr.resx +++ b/backend/src/Squidex.Shared/Texts.fr.resx @@ -1132,9 +1132,6 @@ Vous ne pouvez pas vous verrouiller. - - - Enter your E-Mail Address to login with your Company Account and single sign on (SSO). @@ -1150,18 +1147,6 @@ {action} avec <strong>{provider}</strong> - - Cliquez ici pour vous identifier - - - Déjà enregistré? - - - Cliquez ici pour vous inscrire - - - Pas encore de compte ? - Entrer le mot de passe diff --git a/backend/src/Squidex.Shared/Texts.it.resx b/backend/src/Squidex.Shared/Texts.it.resx index f1a640ac2c..fd23686443 100644 --- a/backend/src/Squidex.Shared/Texts.it.resx +++ b/backend/src/Squidex.Shared/Texts.it.resx @@ -1132,9 +1132,6 @@ Non puoi bloccare te stesso. - - - Enter your E-Mail Address to login with your Company Account and single sign on (SSO). @@ -1150,18 +1147,6 @@ {action} con <strong>{provider}</strong> - - Clicca qui per accedere - - - Sei già registrato? - - - Clicca qui per registrarti - - - Non hai ancora un account? - Inserisci la password diff --git a/backend/src/Squidex.Shared/Texts.nl.resx b/backend/src/Squidex.Shared/Texts.nl.resx index 4777b468d9..16b022f673 100644 --- a/backend/src/Squidex.Shared/Texts.nl.resx +++ b/backend/src/Squidex.Shared/Texts.nl.resx @@ -1132,9 +1132,6 @@ Je kunt jezelf niet vergrendelen. - - - Enter your E-Mail Address to login with your Company Account and single sign on (SSO). @@ -1150,18 +1147,6 @@ {action} met <strong> {provider} </strong> - - Klik hier om in te loggen - - - Al geregistreerd? - - - Klik hier om in te schrijven - - - Nog geen account? - Voer wachtwoord in diff --git a/backend/src/Squidex.Shared/Texts.pt.resx b/backend/src/Squidex.Shared/Texts.pt.resx index 0d43a597e8..eb556c0333 100644 --- a/backend/src/Squidex.Shared/Texts.pt.resx +++ b/backend/src/Squidex.Shared/Texts.pt.resx @@ -1132,9 +1132,6 @@ Você não pode se trancar. - - - Enter your E-Mail Address to login with your Company Account and single sign on (SSO). @@ -1150,18 +1147,6 @@ {action} com <strong>{provider}</strong> - - Carregue aqui para entrar - - - Já registado? - - - Carregue aqui para registar - - - Sem conta? - Introduzir Password diff --git a/backend/src/Squidex.Shared/Texts.resx b/backend/src/Squidex.Shared/Texts.resx index b5a7f9ea35..9c27ac834f 100644 --- a/backend/src/Squidex.Shared/Texts.resx +++ b/backend/src/Squidex.Shared/Texts.resx @@ -1132,9 +1132,6 @@ You cannot lock yourself. - - - Enter your E-Mail Address to login with your Company Account and single sign on (SSO). @@ -1150,18 +1147,6 @@ {action} with <strong>{provider}</strong> - - Click here to login - - - Already registered? - - - Click here to signup - - - No account yet? - Enter Password diff --git a/backend/src/Squidex.Shared/Texts.zh.resx b/backend/src/Squidex.Shared/Texts.zh.resx index 144156c01e..8a1756dabf 100644 --- a/backend/src/Squidex.Shared/Texts.zh.resx +++ b/backend/src/Squidex.Shared/Texts.zh.resx @@ -1132,9 +1132,6 @@ 你不能锁定自己。 - - - Enter your E-Mail Address to login with your Company Account and single sign on (SSO). @@ -1150,18 +1147,6 @@ {action} with <strong>{provider}</strong> - - 点击此处登录 - - - 已经注册? - - - 点击此处注册 - - - 还没有账号? - 输入密码 diff --git a/backend/src/Squidex.Web/Services/UrlGenerator.cs b/backend/src/Squidex.Web/Services/UrlGenerator.cs index 718d7061d1..6c9932fc56 100644 --- a/backend/src/Squidex.Web/Services/UrlGenerator.cs +++ b/backend/src/Squidex.Web/Services/UrlGenerator.cs @@ -20,11 +20,11 @@ public sealed class UrlGenerator( IGenericUrlGenerator urlGenerator, IAssetFileStore assetFileStore, IOptions assetOptions, - IOptions contentOptions) + IOptions contentOptions) : IUrlGenerator, IHttpImageEndpoint { private readonly AssetOptions assetOptions = assetOptions.Value; - private readonly ContentOptions contentOptions = contentOptions.Value; + private readonly ContentsOptions contentOptions = contentOptions.Value; public string? AssetThumbnail(NamedId appId, string idOrSlug, AssetType assetType) { diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemaIndexesController.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemaIndexesController.cs index 59913427fe..c1d53b0048 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemaIndexesController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemaIndexesController.cs @@ -79,7 +79,7 @@ public async Task PostIndex(string app, string schema, [FromBody] /// The name of the index. /// Schema index deletion added to job queue. /// Schema or app not found. - [HttpPost] + [HttpDelete] [Route("apps/{app}/schemas/{schema}/indexes/{name}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ApiPermissionOrAnonymous(PermissionIds.AppSchemasIndexes)] diff --git a/backend/src/Squidex/Areas/Frontend/Middlewares/IndexExtensions.cs b/backend/src/Squidex/Areas/Frontend/Middlewares/IndexExtensions.cs index cd7ec6fc6b..ec60bc590a 100644 --- a/backend/src/Squidex/Areas/Frontend/Middlewares/IndexExtensions.cs +++ b/backend/src/Squidex/Areas/Frontend/Middlewares/IndexExtensions.cs @@ -32,7 +32,6 @@ public static string AddOptions(this string html, HttpContext httpContext) }; var uiOptions = httpContext.RequestServices.GetService>()?.Value; - if (uiOptions != null) { var clonedOptions = uiOptions with diff --git a/backend/src/Squidex/Config/Domain/ContentsServices.cs b/backend/src/Squidex/Config/Domain/ContentsServices.cs index f47dceaac4..fe034aebf6 100644 --- a/backend/src/Squidex/Config/Domain/ContentsServices.cs +++ b/backend/src/Squidex/Config/Domain/ContentsServices.cs @@ -24,7 +24,7 @@ public static class ContentsServices { public static void AddSquidexContents(this IServiceCollection services, IConfiguration config) { - services.Configure(config, + services.Configure(config, "contents"); services.Configure(config, diff --git a/backend/src/Squidex/Config/Domain/FontendServices.cs b/backend/src/Squidex/Config/Domain/FontendServices.cs index c0a296d31b..85cb868e55 100644 --- a/backend/src/Squidex/Config/Domain/FontendServices.cs +++ b/backend/src/Squidex/Config/Domain/FontendServices.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Options; using Squidex.AI; using Squidex.Areas.Api.Controllers.UI; +using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.History; using Squidex.Hosting; using Squidex.Text.Translations; @@ -56,6 +57,13 @@ public static void AddSquidexFrontend(this IServiceCollection services) options.More["canUseChatBot"] = chatAgent.IsConfigured; }); + services.Configure((services, options) => + { + var contentsOptions = services.GetRequiredService>(); + + options.More["canCreateIndexes"] = contentsOptions.Value.OptimizeForSelfHosting; + }); + services.Configure((services, options) => { if (string.IsNullOrWhiteSpace(options.CollaborationService)) diff --git a/backend/src/Squidex/appsettings.json b/backend/src/Squidex/appsettings.json index 10f5f86e6b..3c07bb93d0 100644 --- a/backend/src/Squidex/appsettings.json +++ b/backend/src/Squidex/appsettings.json @@ -161,6 +161,9 @@ // Hide all onboarding tooltips and dialogs. "hideOnboarding": false, + // Hide the indexes UI. + "hideIndexes": false, + // Hide the today and now button. "hideDateButtons": false, diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs index 305e2b1895..efe82fab23 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs @@ -126,7 +126,7 @@ protected CachingGraphQLResolver CreateSut(params Schema[] schemas) { x.CanCache = true; }) - .Configure(x => + .Configure(x => { x.CanCache = true; }) diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryFixture.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryFixture.cs index 4ad3793960..21cfaf98c7 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryFixture.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryFixture.cs @@ -75,7 +75,7 @@ protected ContentsQueryFixture(bool selfHosting) var services = new ServiceCollection() - .AddSingleton(Options.Create(new ContentOptions { OptimizeForSelfHosting = selfHosting })) + .AddSingleton(Options.Create(new ContentsOptions { OptimizeForSelfHosting = selfHosting })) .AddSingleton(CreateAppProvider()) .AddSingleton(mongoClient) .AddSingleton(mongoDatabase) diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryParserTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryParserTests.cs index 184bd42470..7f855e9efa 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryParserTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryParserTests.cs @@ -26,7 +26,7 @@ public class ContentQueryParserTests : GivenContext public ContentQueryParserTests() { - var options = Options.Create(new ContentOptions { DefaultPageSize = 30 }); + var options = Options.Create(new ContentsOptions { DefaultPageSize = 30 }); Schema = Schema .AddString(1, "firstName", Partitioning.Invariant) diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryServiceTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryServiceTests.cs index 25ecc6e00e..5e619a2185 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryServiceTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ContentQueryServiceTests.cs @@ -45,7 +45,7 @@ public ContentQueryServiceTests() A.CallTo(() => queryParser.ParseAsync(A._, A._, A._, CancellationToken)) .ReturnsLazily(c => Task.FromResult(c.GetArgument(1)!)); - var options = Options.Create(new ContentOptions()); + var options = Options.Create(new ContentsOptions()); sut = new ContentQueryService( AppProvider, diff --git a/frontend/.prettierrc b/frontend/.prettierrc index adc03ac843..4647d2c956 100644 --- a/frontend/.prettierrc +++ b/frontend/.prettierrc @@ -1,5 +1,6 @@ { "plugins": ["squidex-prettier-plugin-organize-attributes"], + "endOfLine": "crlf", "overrides": [ { "files": "*.html", diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f6533e98c7..28d94c4d8d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -107,7 +107,7 @@ "karma-coverage": "~2.2.1", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.1.0", - "prettier": "^3.2.5", + "prettier": "3.3.3", "squidex-prettier-plugin-organize-attributes": "^1.0.1", "storybook": "^8.1.5", "stylelint": "16.6.1", @@ -22682,9 +22682,9 @@ } }, "node_modules/prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" diff --git a/frontend/package.json b/frontend/package.json index 831f431e84..01b76c3465 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -114,7 +114,7 @@ "karma-coverage": "~2.2.1", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.1.0", - "prettier": "^3.2.5", + "prettier": "3.3.3", "squidex-prettier-plugin-organize-attributes": "^1.0.1", "storybook": "^8.1.5", "stylelint": "16.6.1", diff --git a/frontend/src/app/_theme.html b/frontend/src/app/_theme.html index 9afd23c3f7..f96941c7b8 100644 --- a/frontend/src/app/_theme.html +++ b/frontend/src/app/_theme.html @@ -1,1561 +1,1524 @@ - + - - - - Squidex theme - - - - - - - - -
-
-
-

Squidex Theme

-
-
-
- -
-
+ + + Squidex theme + + + + + + + + +
-
-

Navbars

+
+

Squidex Theme

+
-
-
- +
+
+
+
+

Navbars

+
-
-
-
- +
+
+ +
-
-
-
-
-
-

Buttons

+
+
+
+
+

Buttons

+
-
- -
-
-

- - - - - - - - Link -

- -

- - - - - - - -

- -

- - - - - - -

- -

- - - - - - -

- -
-
- +
+
+

+ + + + + + + + Link +

+ +

+ + + + + + + +

+ +

+ + + + + + +

+ +

+ + + + + + +

+ +
- -
-
+ -
- +
+ +
+
- + + +
+ +
-
-
-
- + +
+ +
-
- -
-
- + + +
+ +
-
- -
- - +
+ - + - -
-
+ -
-

- -

- -
-
- - +
-
-
- - +
+

+ +

+ +
+
+ + +
-
-
-
- - +
+
+ + +
-
-
-
- - +
+
+ + +
-
-
-
- - +
+
+ + +
-
-
-
- - - +
+
+ + +
-
-
-
- - - +
+
+ + + +
-
-
-
- - - +
+
+ + + +
-
-
-
- - - +
+
+ + + +
-
-
-
-
-
-
-
-
-

Typography

-
-
- -
-
-
-

Heading 1

-

Heading 2

-

Heading 3

-

Heading 4

-
Heading 5
-
Heading 6
-

Heading with muted text

- -

Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.

+
+
+
+
+

Typography

-
-
-

Example body text

-

Nullam quis risus eget urna mollis ornare vel eu leo. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam id dolor id nibh ultricies vehicula.

-

The following is rendered as bold text.

-

The following is rendered as italicized text.

-

The following is an abbreviation attr.

+
+
+
+

Heading 1

+

Heading 2

+

Heading 3

+

Heading 4

+
Heading 5
+
Heading 6
+

Heading with muted text

+ +

Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.

+
- -
-
-
-

Emphasis classes

- -

Fusce dapibus, tellus ac cursus commodo, tortor mauris nibh.

-

Nullam id dolor id nibh ultricies vehicula ut id elit.

-

Etiam porta sem malesuada magna mollis euismod.

-

Donec ullamcorper nulla non metus auctor fringilla.

-

Duis mollis, est non commodo luctus, nisi erat porttitor ligula.

-

Maecenas sed diam eget risus varius blandit sit amet non magna.

+
+
+

Example body text

+ +

+ Nullam quis risus eget urna mollis ornare vel eu leo. Cum sociis natoque penatibus et magnis + dis parturient montes, nascetur ridiculus mus. Nullam id dolor id nibh ultricies vehicula. +

+

The following is rendered as bold text.

+

The following is rendered as italicized text.

+

The following is an abbreviation attr.

+
+
+
+
+

Emphasis classes

+ +

Fusce dapibus, tellus ac cursus commodo, tortor mauris nibh.

+

Nullam id dolor id nibh ultricies vehicula ut id elit.

+

Etiam porta sem malesuada magna mollis euismod.

+

Donec ullamcorper nulla non metus auctor fringilla.

+

Duis mollis, est non commodo luctus, nisi erat porttitor ligula.

+

Maecenas sed diam eget risus varius blandit sit amet non magna.

+
- -
-
- -
-
-

Blockquotes

-
-
-
-
-
-

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

-
Someone famous in Source Title
-
+
+
+

Blockquotes

-
-
-
-

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

+
+
+
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

+ +
Someone famous in Source Title
+
+
+
+
+
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

-
Someone famous in Source Title
-
+
Someone famous in Source Title
+
+
-
-
-
-
-
-

List Tables

+
+
+
+
+

List Tables

+
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
- - Column headingColumn headingColumn heading
- - Column contentColumn contentColumn content
- - Column contentColumn contentColumn content
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Column headingColumn headingColumn heading
+ + Column contentColumn contentColumn content
+ + Column contentColumn contentColumn content
+
-
-
-
-
-
-

List Tables (on white background)

+
+
+
+
+

List Tables (on white background)

+
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
- - Column headingColumn headingColumn heading
- - Column contentColumn contentColumn content
- - Column contentColumn contentColumn content
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Column headingColumn headingColumn heading
+ + Column contentColumn contentColumn content
+ + Column contentColumn contentColumn content
+
-
-
-
-
-
-

Forms

+
+
+
+
+

Forms

+
-
- -
-
- Legend -
-

Validation error.

+ +
+ Legend -
    -
  • An user with the same email aready exists.
  • -
-
- -

Validation error.

- +
  • An user with the same email aready exists.
-
-
-
-
-
- +
+
+

Validation error.

- - - We will never share your email with anyone else. -
- -
- +
    +
  • An user with the same email aready exists.
  • +
+
+
- -
+
+
+
+
+ -
- + - -
+ We will never share your email with anyone else. +
-
- +
+ - -
+ +
-
- +
+ - -
+ +
-
-

Custom Radio buttons

+
+ -
- - +
-
- - -
- -
- - -
-
- -
-

Custom Checkboxes

+
+ -
- - +
-
- - -
- -
- - -
-
+
+

Custom Radio buttons

- -
-
+
+ + +
-
- -
- +
+ + +
- -
+
+ + +
+
-
- +
+

Custom Checkboxes

- -
+
+ + +
-
- +
+ + +
- +
+ + +
+
-
Success! You've done it.
+
+
-
- - - +
+ +
+ -
Shucks, try again.
-
+ +
-
- +
+ -
-
You have entered an invalid value.
+
- -
- -
- +
+ - + -
- The app name cannot be changed later. +
Success! You've done it.
-
-
- +
+ - -
+ -
- +
Shucks, try again.
+
- -
+
+ -
- +
+
You have entered an invalid value.
+
- -
+ +
-
- +
+ -
- $ + - +
+ The app name cannot be changed later. +
+
+ +
+ - .00 +
-
-
- +
+ -
- + +
- +
+ - +
-
- -
-
-
- -
-
-
-
-
-
-

Navigation

-
-
+
+ -
-
-

Tabs

+
+ $ -
- -
+ -
- -
-
+ .00 +
+
-
-

Pills

+
+ -
- -
+
+ -
+ -
- -
-
+ +
+
+ +
+
+ +
+
-
-
-

Breadcrumbs

- -
- - - - +
+
+
+
+

Navigation

-
-

Pagination

+
+
+

Tabs

-
-
-
- -
-
-

Dropdown

-
- +
+
    +
  • + « +
  • +
  • + 1 +
  • +
  • + 2 +
  • +
  • + 3 +
  • +
  • + 4 +
  • +
  • + 5 +
  • +
  • + » +
  • +
+
+
+
-
-
-
-

Side Menu

- - +
+
+

Dropdown

+ +
+ +
+
-
-

Sidedbar

-
-
-
-
-
-
-

Indicators

+
+
+
+
+

Indicators

+
-
-
-
-

Alerts

+
+
+

Alerts

-
-
- +
+
+ -

Warning!

+

Warning!

-

Best check yo self, you're not looking too good. Nulla vitae elit libero, a pharetra augue. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

+

+ Best check yo self, you're not looking too good. Nulla vitae elit libero, a pharetra augue. Praesent + commodo cursus magna, vel scelerisque nisl consectetur et. +

+
-
-
-
-
-
- - - Oh snap! Change a few things up and try submitting again. +
+
+
+
+ + + Oh snap! Change a few things up and try submitting + again. +
-
-
-
-
- +
+
+
+ - Well done! You successfully read this important alert message. + Well done! You successfully read + this important alert message. +
-
-
-
-
- +
+
+
+ - Heads up! This alert needs your attention, but it's not super important. + Heads up! This + alert needs your attention, but it's not super important. +
-
-
-

Badges

- -
- Primary - Secondary - Success - Danger - Warning - Info - Light - Dark -
+
+

Badges

+ +
+ Primary + Secondary + Success + Danger + Warning + Info + Light + Dark +
-
- Primary - Secondary - Success - Danger - Warning - Info - Light - Dark +
+ Primary + Secondary + Success + Danger + Warning + Info + Light + Dark +
-
-
-
-
-
-

Progress

+
+
+
+
+

Progress

+
-
-
-
-

Basic

+
+
+

Basic

-
-
-
+
+
+
+
-
-

Contextual alternatives

+

Contextual alternatives

-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-

Multiple bars

+

Multiple bars

-
-
-
-
-
+
+
+
+
+
+
-
-

Striped

+

Striped

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-

Animated

+

Animated

-
-
-
+
+
+
+
-
-
-
-
-
-

Containers

+
+
+
+
+

Containers

+
-
-
-
-
-
-

Jumbotron

- -

This is a simple hero unit, a simple jumbotron-style component for calling extra attention to featured content or information.

-

Learn more

+
+
+
+
+

Jumbotron

+ +

+ This is a simple hero unit, a simple jumbotron-style component for calling extra attention to featured + content or information. +

+

Learn more

+
-
-
-
-

List groups

+
+
+

List groups

+
-
-
-
-
-
    -
  • - 14 Cras justo odio -
  • -
  • - 2 Dapibus ac facilisis in -
  • -
  • - 1 Morbi leo risus -
  • -
+
+
+
+
    +
  • + 14 Cras justo odio +
  • +
  • + 2 Dapibus ac facilisis in +
  • +
  • + 1 Morbi leo risus +
  • +
+
-
-
- -
-
-
-
-

Cards

+
+
+
+
+

Cards

+
-
-
-
-
-
-
-
-

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

+
+
+
+
+
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

-
Someone famous in Source Title
-
+
Someone famous in Source Title
+
+
-
-
-
-
-

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

+
+
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

-
Someone famous in Source Title
-
+
Someone famous in Source Title
+
+
-
-
-
-
-

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

+
+
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

-
Someone famous in Source Title
-
+
Someone famous in Source Title
+
+
-
-
-
-
-

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

+
+
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

-
Someone famous in Source Title
-
+
Someone famous in Source Title
+
+
-
-
-
-
-

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

+
+
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

-
Someone famous in Source Title
-
+
Someone famous in Source Title
+
+
-
-
-
-
-
-
-

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

- -
Someone famous in Source Title
-
+
+
+
+
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

+ +
Someone famous in Source Title
+
+
-
-
-
-
-

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

+
+
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

-
Someone famous in Source Title
-
+
Someone famous in Source Title
+
+
-
-
-
-
-

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

+
+
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

-
Someone famous in Source Title
-
+
Someone famous in Source Title
+
+
-
-
-
-
-

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

+
+
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

-
Someone famous in Source Title
-
+
Someone famous in Source Title
+
+
-
-
-
-
-

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

+
+
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

-
Someone famous in Source Title
-
+
Someone famous in Source Title
+
+
-
-
-
-
-

Card header

+
+
+
+

Card header

-
-
Special title treatment
+
+
Special title treatment
-
Support card subtitle
-
- - Card image +
Support card subtitle
+
-
-

Some quick example text to build on the card title and make up the bulk of the card's content.

- Card link - Another link -
+ Card image + +
+

+ Some quick example text to build on the card title and make up the bulk of the card's content. +

+ Card link + Another link +
-
-
-
-
-
-
-

Modals

+
+
+
+
+

Modals

+
-
- -
-
-
-