diff --git a/app/components/work_packages/activities_tab/index_component.html.erb b/app/components/work_packages/activities_tab/index_component.html.erb index 6bf30eafaf41..6ff2baeee91c 100644 --- a/app/components/work_packages/activities_tab/index_component.html.erb +++ b/app/components/work_packages/activities_tab/index_component.html.erb @@ -1,6 +1,6 @@ <%= content_tag("turbo-frame", id: "work-package-activities-tab-content") do - flex_layout(classes: "work-packages-activities-tab-index-component") do |activties_tab_wrapper_container| + flex_layout(classes: "work-packages-activities-tab-index-component", mb: [5, 5, 5, 5, 0]) do |activties_tab_wrapper_container| activties_tab_wrapper_container.with_row(classes: "work-packages-activities-tab-index-component--errors") do render( WorkPackages::ActivitiesTab::ErrorStreamComponent.new diff --git a/app/components/work_packages/activities_tab/index_component.sass b/app/components/work_packages/activities_tab/index_component.sass index d4c6750a2db6..34fe6c22fb33 100644 --- a/app/components/work_packages/activities_tab/index_component.sass +++ b/app/components/work_packages/activities_tab/index_component.sass @@ -1,4 +1,5 @@ .work-packages-activities-tab-index-component + overflow-y: hidden &--errors position: absolute width: calc(100% - 22px) diff --git a/app/components/work_packages/activities_tab/journals/item_component/add_reactions.html.erb b/app/components/work_packages/activities_tab/journals/item_component/add_reactions.html.erb index dbe48383d22f..468c87d5baf1 100644 --- a/app/components/work_packages/activities_tab/journals/item_component/add_reactions.html.erb +++ b/app/components/work_packages/activities_tab/journals/item_component/add_reactions.html.erb @@ -9,7 +9,7 @@ overlay.with_show_button( icon: "smiley", "aria-label": I18n.t("reactions.add_reaction"), - title: I18n.t("reactions.add_reaction"), + name: I18n.t("reactions.add_reaction"), mr: 2, test_selector: "add-reactions-button" ) diff --git a/app/models/queries/versions.rb b/app/models/queries/versions.rb index b6af7df75293..6e036b0bae4a 100644 --- a/app/models/queries/versions.rb +++ b/app/models/queries/versions.rb @@ -30,7 +30,6 @@ module Queries::Versions ::Queries::Register.register(VersionQuery) do filter Filters::SharingFilter - order Orders::NameOrder - order Orders::SemverNameOrder + order Orders::DefaultOrder end end diff --git a/app/models/queries/versions/orders/name_order.rb b/app/models/queries/versions/orders/default_order.rb similarity index 80% rename from app/models/queries/versions/orders/name_order.rb rename to app/models/queries/versions/orders/default_order.rb index 362b9340cfb9..b86f38ef6d52 100644 --- a/app/models/queries/versions/orders/name_order.rb +++ b/app/models/queries/versions/orders/default_order.rb @@ -26,22 +26,20 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class Queries::Versions::Orders::NameOrder < Queries::Orders::Base +class Queries::Versions::Orders::DefaultOrder < Queries::Orders::Base self.model = Version def self.key - :name + /\A(id|name|semver_name)\z/ end - private + def initialize(attribute) + if attribute == :semver_name + OpenProject::Deprecation.warn("Sorting by semver_name is deprecated, name should be used instead") - def order - ordered = Version.order(:name) - - if direction == :desc - ordered = ordered.reverse_order + super(:name) + else + super end - - ordered end end diff --git a/app/models/queries/versions/orders/semver_name_order.rb b/app/models/queries/versions/orders/semver_name_order.rb deleted file mode 100644 index 465429fb6bb0..000000000000 --- a/app/models/queries/versions/orders/semver_name_order.rb +++ /dev/null @@ -1,47 +0,0 @@ -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) the OpenProject GmbH -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License version 3. -# -# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -# Copyright (C) 2006-2013 Jean-Philippe Lang -# Copyright (C) 2010-2013 the ChiliProject Team -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# See COPYRIGHT and LICENSE files for more details. -#++ - -class Queries::Versions::Orders::SemverNameOrder < Queries::Orders::Base - self.model = Version - - def self.key - :semver_name - end - - private - - def order(scope) - ordered = scope.order_by_semver_name - - if direction == :desc - ordered = ordered.reverse_order - end - - ordered - end -end diff --git a/app/models/queries/work_packages/selects/property_select.rb b/app/models/queries/work_packages/selects/property_select.rb index 7963dfb81348..c73db63aaeff 100644 --- a/app/models/queries/work_packages/selects/property_select.rb +++ b/app/models/queries/work_packages/selects/property_select.rb @@ -53,7 +53,6 @@ def caption }, parent: { association: "ancestors_relations", - default_order: "asc", sortable: false }, status: { @@ -96,18 +95,14 @@ def caption version: { association: "version", sortable: "name", - default_order: "ASC", - null_handling: "NULLS LAST", groupable: "#{WorkPackage.table_name}.version_id" }, start_date: { - sortable: "#{WorkPackage.table_name}.start_date", - null_handling: "NULLS LAST" + sortable: "#{WorkPackage.table_name}.start_date" }, due_date: { highlightable: true, - sortable: "#{WorkPackage.table_name}.due_date", - null_handling: "NULLS LAST" + sortable: "#{WorkPackage.table_name}.due_date" }, estimated_hours: { sortable: "#{WorkPackage.table_name}.estimated_hours", diff --git a/docs/api/apiv3/paths/versions.yml b/docs/api/apiv3/paths/versions.yml index 07f1953b348d..e69e05a9acc6 100644 --- a/docs/api/apiv3/paths/versions.yml +++ b/docs/api/apiv3/paths/versions.yml @@ -2,18 +2,31 @@ --- get: parameters: - - description: |- - JSON specifying filter conditions. - Accepts the same format as returned by the [queries](https://www.openproject.org/docs/api/endpoints/queries/) endpoint. - Currently supported filters are: + - description: |- + JSON specifying filter conditions. + Accepts the same format as returned by the [queries](https://www.openproject.org/docs/api/endpoints/queries/) endpoint. + Currently supported filters are: - + sharing: filters versions by how they are shared within the server (*none*, *descendants*, *hierarchy*, *tree*, *system*). - example: '[{ "sharing": { "operator": "*", "values": ["system"] }" }]' - in: query - name: filters - required: false - schema: - type: string + + sharing: filters versions by how they are shared within the server (*none*, *descendants*, *hierarchy*, *tree*, *system*). + example: '[{ "sharing": { "operator": "*", "values": ["system"] }" }]' + in: query + name: filters + required: false + schema: + type: string + - description: |- + JSON specifying sort criteria. + Accepts the same format as returned by the [queries](https://www.openproject.org/docs/api/endpoints/queries/) endpoint. Currently supported attributes are: + + + id: Sort by the version id + + name: Sort by the version name using numeric collation, comparing sequences of decimal digits by their numeric value + + semver_name: Deprecated, use name instead + example: '[["name", "desc"]]' + in: query + name: sortBy + required: false + schema: + type: string responses: '200': content: @@ -23,57 +36,57 @@ get: value: _embedded: elements: - - _links: - availableInProjects: - href: "/api/v3/versions/11/projects" - definingProject: - href: "/api/v3/projects/12" - self: - href: "/api/v3/versions/11" - _type: Version - description: - format: plain - html: This version has a description - raw: This version has a description - endDate: - id: 11 - name: v3.0 Alpha - startDate: '2014-11-20' - status: Open - - _links: - availableInProjects: - href: "/api/v3/versions/12/projects" - definingProject: - href: "/api/v3/projects/11" - self: - href: "/api/v3/versions/12" - _type: Version - description: - format: plain - html: '' - raw: '' - endDate: - id: 12 - name: v2.0 - startDate: - status: Closed - - _links: - availableInProjects: - href: "/api/v3/versions/13/projects" - definingProject: - href: "/api/v3/projects/13" - self: - href: "/api/v3/versions/10" - _type: Version - description: - format: plain - html: '' - raw: '' - endDate: - id: 10 - name: v1.0 - startDate: - status: Open + - _links: + availableInProjects: + href: "/api/v3/versions/11/projects" + definingProject: + href: "/api/v3/projects/12" + self: + href: "/api/v3/versions/11" + _type: Version + description: + format: plain + html: This version has a description + raw: This version has a description + endDate: + id: 11 + name: v3.0 Alpha + startDate: '2014-11-20' + status: Open + - _links: + availableInProjects: + href: "/api/v3/versions/12/projects" + definingProject: + href: "/api/v3/projects/11" + self: + href: "/api/v3/versions/12" + _type: Version + description: + format: plain + html: '' + raw: '' + endDate: + id: 12 + name: v2.0 + startDate: + status: Closed + - _links: + availableInProjects: + href: "/api/v3/versions/13/projects" + definingProject: + href: "/api/v3/projects/13" + self: + href: "/api/v3/versions/10" + _type: Version + description: + format: plain + html: '' + raw: '' + endDate: + id: 10 + name: v1.0 + startDate: + status: Open _links: self: href: "/api/v3/versions" @@ -85,7 +98,7 @@ get: description: OK headers: {} tags: - - Versions + - Versions description: Returns a collection of versions. The client can choose to filter the versions similar to how work packages are filtered. In addition to the provided filters, the server will reduce the result set to only contain versions, for which @@ -130,7 +143,7 @@ post: * a constraint for a property was violated (`PropertyConstraintViolation`) headers: {} tags: - - Versions + - Versions description: |- Creates a new version applying the attributes provided in the body. Please note that while there is a fixed set of attributes, custom fields can extend a version's attributes and are accepted by the endpoint. diff --git a/docs/release-notes/15-0-0/OpenProject_SSO_SAML_OpenID-highlighted.png b/docs/release-notes/15-0-0/OpenProject_SSO_SAML_OpenID-highlighted.png new file mode 100644 index 000000000000..52bd618848ee Binary files /dev/null and b/docs/release-notes/15-0-0/OpenProject_SSO_SAML_OpenID-highlighted.png differ diff --git a/docs/release-notes/15-0-0/README.md b/docs/release-notes/15-0-0/README.md index b54bdecebfc6..3fae72b7668e 100644 --- a/docs/release-notes/15-0-0/README.md +++ b/docs/release-notes/15-0-0/README.md @@ -8,16 +8,103 @@ release_date: 2024-10-31 # OpenProject 15.0.0 -Release date: 2024-10-31 +Release date: 2024-11-13 -We released OpenProject [OpenProject 15.0.0](https://community.openproject.org/versions/2076). -The release contains several bug fixes and we recommend updating to the newest version. -In these Release Notes, we will give an overview of important feature changes. -At the end, you will find a complete list of all changes and bug fixes. +We released [OpenProject 15.0.0](https://community.openproject.org/versions/2076). The major release contains several bug fixes and we recommend updating to the newest version. In these Release Notes, we will give an overview of important feature changes. At the end, you will find a complete list of all changes and bug fixes. ## Important feature changes - +### Boost your communication with a better structured activity tab, real-time loading messages and notifications, emoji reactions, and more + +A major change in version 15.0 is the overall look-and-feel of the activity tab of work packages. Users will notice that the activity tab has a new design with additional options, and that emoji reactions are now enabled. Additionally, new comments will appear directly without having to reload. This also applies to the notification center, where new notifications will appear in real-time. + +This is a big bundle of new features that will greatly improve communication and interaction within OpenProject, making it more simple, more effective and more fun. + +Related features in 15.0: +- [Change the design of the Activity panel to Primer](https://community.openproject.org/wp/54733) +- [Emoji reactions to work package comments](https://community.openproject.org/wp/40437) +- [Continuously update the notification center. Don't ask for loading updates.](https://community.openproject.org/wp/58253) +- [Remove "Latest activity" section from work package "Overview" tab](https://community.openproject.org/wp/58017) +- [On "Newest at the bottom" have the line touch the input box](https://community.openproject.org/wp/57262) + +Let's take a closer look at the three biggest changes concerning this feature bundle: + +#### A new timeline design for the activity tab of work packages + +Apart from some obvious design changes that all fit GitHub's Primer design system, users will benefit from some great new features, such as: +- The comment box being a fixed element anchored to the bottom of the split screen area. +- Filtering the activity panel with options to either show everything, changes only or comments only. +- Ordering to either newest on top or newest at the bottom + +![Screenshot showing the new activity tab with highlighted changes](openproject-15-0-activity-tab-highlighted-all.png) + +#### Emoji reactions to work package comments + +Many users have wished to be able to react to comments in work packages using emoji and with OpenProject 15.0 this is now possible. In order to still keep it clear and concise, we have limited the emojis to eight helpful reactions: + +![Example screenshot showing emoji reactions in OpenProject's work package comments](openproject-emoji-overview.png) + +Please note that emoji reactions don't trigger notifications. If you need your colleague to be notified about your reaction, leave a regular comment. + +#### Continuous update of the notification center + +Starting with version 15.0, the notification center will continuously update and new notifications will appear directly. This means no more blue flash message mentioning that there are updates and asking if you want to reload the page. The number shown next to the bell icon will also update immediately. This feature adds to our goal to enable smooth communication and information. + +### Benefit from easy Single Sign-On authentication settings with SAML and OIDC in your administration + +With OpenProject 15.0, particularly SaaS customers will benefit from our new user interface for SAML and OIDC. This means they can now set up integrations between OpenProject and SAML or OpenID connect stacks independently and offer users options for Single Sign-On (SSO). Before 15.0, SaaS customers had to contact the OpenProject support if they wanted custom integrations with their SAML or OpenID connect stacks. Now, they cannot only set them up on their own, but also have tools for debugging them if needed. + +Related features in 15.0: + +- [User interface for OIDC (OpenID Connect) provider configuration](https://community.openproject.org/wp/57677) +- [User interface for SAML configuration](https://community.openproject.org/wp/40279) +- [Show danger zone when deleting SAML or OIDC provider](https://community.openproject.org/wp/58451) +- [Allow setting omniauth_direct_login_provider through UI](https://community.openproject.org/wp/58437) + +Under *Administration > Authentication*, admins can now set up [OpenID](../../system-admin-guide/authentication/openid-providers/) or [SAML providers](../../system-admin-guide/authentication/saml/). Here is an example screenshot for adding an OpenID provider: + +![Example screenshot of the administration screen if you want to add an OpenIDprovider in OpenProject](openproject-15-0-sso-administration-dropdown.png) + +Once set up, users can log in with their existing account, for example like shown in this screenshot: + +![Example screenshot of the log in screen with options to single sign-on](OpenProject_SSO_SAML_OpenID-highlighted.png) + +### Use the new 'Standard global role' and enable permissions to view email addresses + +With OpenProject 15.0, you get a new default 'Standard global role' that is automatically and permanently given to all users. If you are an administrator responsible for roles, please check this under *Administration > Users and permissions > Roles and permissions > Standard global role*. This role has several permissions to choose from, one being 'View users' mail addresses'. Enable this permission to allow any user to see everyone's email address in autocomplete situations, such as when they select a work package assignee from a drop-down list. + +Before version 15.0, users could choose whether their email address was displayed. Now this is an administrative decision that applies to either everyone or no one. + +![Example screenshot of permissions view for the new Standard global role, with checkmark at 'View users' mail addresses](openproject-15-0-standard-global-role.png) + +### Enjoy easier navigation in project lists with quick action table headers + +With OpenProject 15.0, we are pleased to release another great feature for our project lists: Clicking on the table headers in a project list now gives you a quick action menu that not only allows you to sort in descending or ascending order, but also to filter or change, remove or add a column. While these features are not new and you can still find them in the top-right menu button, these actions are now much quicker to access. + +![Example screenshot of a project list with dropdown menu on a table header](openproject-15-0-project-lists.png) + +![Gif showing how quickly you can now change rows in project lists by clicking on the headers](quick_action_table_headers.gif) + +### Experience simplified design settings with fewer sidebar design variables needed + +Before version 15.0, the design configuration in the administration was very complex as there were many variables to be defined. This is why with OpenProject 15.0, the following design colors cannot be customized anymore: +- Header font +- Header font on hover +- Header border +- Main menu font +- Main menu font when selected +- Main menu font on hover +- Main menu border + +All these colors will now be calculated depending on the brightness of the respective background to ensure a high-enough contrast. Font colors will either be black or white, and border colors will be set only if there is a bright background. + +### Reduce manual cleanup when adding a custom field to a type – no more auto-applying to all projects + +With OpenProject 14.6, we released a feature that allows you to enable or disable a custom field for multiple projects at once. This is why with OpenProject 15.0, we remove the automation to apply a new custom field to all projects where the respective type is activated. This reduces manual cleanup in case you did not want to activate the new custom field in all projects. If you do want that, you can use the feature we introduced last release and go to *Administration > Custom Fields* and click on the 'Add projects' button. + +### Improved navigation clarity – 'My account' is renamed to 'Account settings' + +In the personal menu that can be accessed by clicking on your avatar, we renamed 'My account' to 'Account settings', in order to give you a clearer understanding that this menu item contains settings. It now also differs more clearly from 'My Page' and 'My Activities‘, which provide personal data instead of settings. ## Important updates and breaking changes @@ -116,12 +203,13 @@ At the end, you will find a complete list of all changes and bug fixes. ## Contributions -A very special thank you goes to our sponsors for this release. -Also a big thanks to our Community members for reporting bugs and helping us identify and provide fixes. -Special thanks for reporting and finding bugs go to Bill Bai, Sam Yelman, Ivan Kuchin, Knight Chang, Gábor Alexovics, Gregor Buergisser, Andrey Dermeyko, Various Interactive, Clayton Belcher, Александр Татаринцев, Keno Krewer. -Last but not least, we are very grateful for our very engaged translation contributors on Crowdin, who translated quite a few OpenProject strings! -Would you like to help out with translations yourself? -Then take a look at our translation guide and find out exactly how you can contribute. -It is very much appreciated! +A very special thank you goes to the City of Cologne for sponsoring parts of this release. Also a big thanks to our Community members for reporting bugs and helping us identify and provide fixes. Special thanks for reporting and finding bugs go to Bill Bai, Sam Yelman, Ivan Kuchin, Knight Chang, Gábor Alexovics, Gregor Buergisser, Andrey Dermeyko, Various Interactive, Clayton Belcher, Александр Татаринцев, and Keno Krewer. + +Last but not least, we are very grateful for our very engaged translation contributors on Crowdin, who translated quite a few OpenProject strings! This release we would like to highlight +- [Alexander Aleschenko](https://crowdin.com/profile/top4ek), for a great number of translations into Russian. +- [hmmftg](https://crowdin.com/profile/hmmftg), for a great number of translations into Persian. +- [william](https://crowdin.com/profile/WilliamFromTW), for a great number of translations into Chinese Simplified and Chinese Traditional. +- [Alin Marcu](https://crowdin.com/profile/deconfcom), for a great number of translations into Romanian. +Would you like to help out with translations yourself? Then take a look at our [translation guide](../../development/translate-openproject/) and find out exactly how you can contribute. It is very much appreciated! \ No newline at end of file diff --git a/docs/release-notes/15-0-0/openproject-15-0-activity-tab-highlighted-all.png b/docs/release-notes/15-0-0/openproject-15-0-activity-tab-highlighted-all.png new file mode 100644 index 000000000000..5f81cc3950b7 Binary files /dev/null and b/docs/release-notes/15-0-0/openproject-15-0-activity-tab-highlighted-all.png differ diff --git a/docs/release-notes/15-0-0/openproject-15-0-project-lists.png b/docs/release-notes/15-0-0/openproject-15-0-project-lists.png new file mode 100644 index 000000000000..a1cfc9399341 Binary files /dev/null and b/docs/release-notes/15-0-0/openproject-15-0-project-lists.png differ diff --git a/docs/release-notes/15-0-0/openproject-15-0-sso-administration-dropdown.png b/docs/release-notes/15-0-0/openproject-15-0-sso-administration-dropdown.png new file mode 100644 index 000000000000..74e180e88609 Binary files /dev/null and b/docs/release-notes/15-0-0/openproject-15-0-sso-administration-dropdown.png differ diff --git a/docs/release-notes/15-0-0/openproject-15-0-sso-administration-highlighted.png b/docs/release-notes/15-0-0/openproject-15-0-sso-administration-highlighted.png new file mode 100644 index 000000000000..0e61e943283c Binary files /dev/null and b/docs/release-notes/15-0-0/openproject-15-0-sso-administration-highlighted.png differ diff --git a/docs/release-notes/15-0-0/openproject-15-0-standard-global-role.png b/docs/release-notes/15-0-0/openproject-15-0-standard-global-role.png new file mode 100644 index 000000000000..57d377c18652 Binary files /dev/null and b/docs/release-notes/15-0-0/openproject-15-0-standard-global-role.png differ diff --git a/docs/release-notes/15-0-0/openproject-emoji-overview.png b/docs/release-notes/15-0-0/openproject-emoji-overview.png new file mode 100644 index 000000000000..397312bfd2b5 Binary files /dev/null and b/docs/release-notes/15-0-0/openproject-emoji-overview.png differ diff --git a/docs/release-notes/15-0-0/quick_action_table_headers.gif b/docs/release-notes/15-0-0/quick_action_table_headers.gif new file mode 100644 index 000000000000..e7c2fe4140f9 Binary files /dev/null and b/docs/release-notes/15-0-0/quick_action_table_headers.gif differ diff --git a/docs/system-admin-guide/design/README.md b/docs/system-admin-guide/design/README.md index 1bc54b52e2e6..dea2a33f2c21 100644 --- a/docs/system-admin-guide/design/README.md +++ b/docs/system-admin-guide/design/README.md @@ -13,7 +13,7 @@ Navigate to *Administration* -> *Design* in order to customize your OpenProject The design page provides several options to customize your OpenProject Enterprise edition, grouped under three tabs, **Interface, Branding, PDF export styles**. You can [choose a color theme](#choose-a-color-theme) under any of these tabs. -Under **Interface** you can also choose [custom colors](#interface-colors) for elements of the interface such as the primary link colour, secondary accent colour, the background of the top navigation header and the main menu. +Under **Interface** you can also choose [custom colors](#interface-colors) for elements of the interface such as the primary button colour, accent colour, the background of the top navigation header and the main menu. ![Design interface settings in OpenProject adminstration](openproject_system_guide_design_interface.png) diff --git a/docs/system-admin-guide/design/openproject_system_guide_design_interface.png b/docs/system-admin-guide/design/openproject_system_guide_design_interface.png index 155ab93bd129..e45081bf1c95 100644 Binary files a/docs/system-admin-guide/design/openproject_system_guide_design_interface.png and b/docs/system-admin-guide/design/openproject_system_guide_design_interface.png differ diff --git a/frontend/src/app/features/in-app-notifications/bell/in-app-notification-bell.component.ts b/frontend/src/app/features/in-app-notifications/bell/in-app-notification-bell.component.ts index 417a2534b2b1..180d9489f873 100644 --- a/frontend/src/app/features/in-app-notifications/bell/in-app-notification-bell.component.ts +++ b/frontend/src/app/features/in-app-notifications/bell/in-app-notification-bell.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, ElementRef, Input, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, ElementRef, Input, OnInit, HostListener } from '@angular/core'; import { combineLatest, merge, Observable, timer } from 'rxjs'; import { filter, map, shareReplay, switchMap, throttleTime } from 'rxjs/operators'; import { ActiveWindowService } from 'core-app/core/active-window/active-window.service'; @@ -33,6 +33,15 @@ export class InAppNotificationBellComponent implements OnInit { populateInputsFromDataset(this); } + // enable other parts of the application to trigger an immediate update + // e.g. a stimulus controller + // currently used by the new activities tab which does it's own polling + // and receives updates from the backend earlier than the polling in the bell component + @HostListener('document:ian-update-immediate') + triggerImmediateUpdate() { + this.storeService.fetchUnread().subscribe(); + } + ngOnInit() { this.polling$ = merge( timer(10, this.interval).pipe(filter(() => this.activeWindow.isActive)), diff --git a/frontend/src/app/features/plugins/plugin-context.ts b/frontend/src/app/features/plugins/plugin-context.ts index 56550723912b..9153da3a8978 100644 --- a/frontend/src/app/features/plugins/plugin-context.ts +++ b/frontend/src/app/features/plugins/plugin-context.ts @@ -33,7 +33,6 @@ import { AttachmentsResourceService } from 'core-app/core/state/attachments/atta import { HttpClient } from '@angular/common/http'; import { TimezoneService } from 'core-app/core/datetime/timezone.service'; import { TurboRequestsService } from 'core-app/core/turbo/turbo-requests.service'; -import { IanCenterService } from 'core-app/features/in-app-notifications/center/state/ian-center.service'; /** * Plugin context bridge for plugins outside the CLI compiler context * in order to access services and parts of the core application @@ -69,7 +68,6 @@ export class OpenProjectPluginContext { attachmentsResourceService: this.injector.get(AttachmentsResourceService), http: this.injector.get(HttpClient), turboRequests: this.injector.get(TurboRequestsService), - ianCenter: this.injector.get(IanCenterService), }; public readonly helpers = { diff --git a/frontend/src/app/features/work-packages/routing/wp-view-base/work-package-single-view.base.ts b/frontend/src/app/features/work-packages/routing/wp-view-base/work-package-single-view.base.ts index a89498031d98..89851497d456 100644 --- a/frontend/src/app/features/work-packages/routing/wp-view-base/work-package-single-view.base.ts +++ b/frontend/src/app/features/work-packages/routing/wp-view-base/work-package-single-view.base.ts @@ -188,16 +188,6 @@ export class WorkPackageSingleViewBase extends UntilDestroyedMixin { this.attachmentsResourceService.fetchCollection(this.workPackage.$links.attachments.href as string).subscribe(); } - if (this.workPackage.$links.fileLinks) { - this.fileLinkResourceService - .updateCollectionsForWorkPackage(this.workPackage.$links.fileLinks.href as string) - .pipe(take(1)) - .subscribe( - () => { /* Do nothing */ }, - (error:HttpErrorResponse) => { this.toastService.addError(error); }, - ); - } - // Listen to tab changes to update the tab label this.keepTab.observable .pipe(this.untilDestroyed()) diff --git a/frontend/src/app/shared/components/datepicker/styles/datepicker.modal.sass b/frontend/src/app/shared/components/datepicker/styles/datepicker.modal.sass index 9cad619e11d6..35d68546881f 100644 --- a/frontend/src/app/shared/components/datepicker/styles/datepicker.modal.sass +++ b/frontend/src/app/shared/components/datepicker/styles/datepicker.modal.sass @@ -28,6 +28,8 @@ grid-template-columns: auto auto 1fr grid-column-gap: $spot-spacing-2 margin: 0 $spot-spacing-1 0 + .spot-form-field--input:has(spot-switch) + margin-bottom: 0px !important &--dates-container display: grid @@ -52,7 +54,8 @@ visibility: hidden &--flatpickr-instance.inline - margin: 0.5rem auto !important + margin: 0.5rem auto 0 auto !important + overflow: hidden &--stretch-content flex-grow: 1 diff --git a/frontend/src/app/shared/components/modal/modal-banner/modal-banner.component.sass b/frontend/src/app/shared/components/modal/modal-banner/modal-banner.component.sass index 2df95d7b0ac8..7b2a2d2c0dcb 100644 --- a/frontend/src/app/shared/components/modal/modal-banner/modal-banner.component.sass +++ b/frontend/src/app/shared/components/modal/modal-banner/modal-banner.component.sass @@ -3,16 +3,16 @@ .op-modal-banner display: grid grid-template-columns: 20px auto max-content - grid-gap: $spot-spacing-1 + grid-gap: 1rem align-items: center - padding: $spot-spacing-0-75 + padding: 0.75rem 1rem @media only screen and (max-width: $breakpoint-sm) grid-template-columns: auto max-content &:first-child - border-top-left-radius: $spot-spacing-0-25 - border-top-right-radius: $spot-spacing-0-25 + border-top-left-radius: 0.25rem + border-top-right-radius: 0.25rem &_warning background: var(--bgColor-attention-muted) diff --git a/frontend/src/app/shared/components/storages/storage/storage.component.ts b/frontend/src/app/shared/components/storages/storage/storage.component.ts index cc7ca74d77c2..dd967426192e 100644 --- a/frontend/src/app/shared/components/storages/storage/storage.component.ts +++ b/frontend/src/app/shared/components/storages/storage/storage.component.ts @@ -77,7 +77,7 @@ import { IHalResourceLink } from 'core-app/core/state/hal-resource'; import { LocationPickerModalComponent, } from 'core-app/shared/components/storages/location-picker-modal/location-picker-modal.component'; -import { ToastService } from 'core-app/shared/components/toaster/toast.service'; +import { IToast, ToastService } from 'core-app/shared/components/toaster/toast.service'; import { StorageFilesResourceService } from 'core-app/core/state/storage-files/storage-files.service'; import { IUploadFile, OpUploadService } from 'core-app/core/upload/upload.service'; import { IUploadLink } from 'core-app/core/state/storage-files/upload-link.model'; @@ -237,21 +237,27 @@ export class StorageComponent extends UntilDestroyedMixin implements OnInit, OnD ngOnInit():void { this.storage = this.storagesResourceService.requireEntity(this.projectStorage._links.storage.href); - this.fileLinks = this.collectionKey() - .pipe( - switchMap((key) => { - if (isNewResource(this.resource)) { - return this.fileLinkResourceService.collection(key); - } + this.fileLinks = this.storage.pipe( + take(1), + switchMap(() => + this.collectionKey().pipe( + switchMap((key) => { + if (isNewResource(this.resource)) { + return this.fileLinkResourceService.collection(key); + } + return this.fileLinkResourceService.requireCollection(key); + }), + tap((fileLinks) => { + if (isNewResource(this.resource)) { + this.resource.fileLinks = { elements: fileLinks.map((a) => a._links?.self) }; + } + }), + )), + ); - return this.fileLinkResourceService.requireCollection(key); - }), - tap((fileLinks) => { - if (isNewResource(this.resource)) { - this.resource.fileLinks = { elements: fileLinks.map((a) => a._links?.self) }; - } - }), - ); + this.fileLinks.subscribe({ error: (err:string | HttpErrorResponse | IToast) => + this.toastService.addError(err), + }); this.disabled = combineLatest([ this.storage, diff --git a/frontend/src/app/spot/styles/sass/components/action-bar.sass b/frontend/src/app/spot/styles/sass/components/action-bar.sass index 8cd8dae78d2d..6906c633f80f 100644 --- a/frontend/src/app/spot/styles/sass/components/action-bar.sass +++ b/frontend/src/app/spot/styles/sass/components/action-bar.sass @@ -1,7 +1,7 @@ .spot-action-bar display: grid grid-template-columns: 1fr - padding: 0 $spot-spacing-1 + padding: 0 1rem a.button text-decoration: none @@ -21,7 +21,7 @@ // We need to set the margins on the buttons itself because // an empty action bar would take unnecessary space if we put the padding there. // see #47994 and #49729 - margin: $spot-spacing-1 $spot-spacing-1 $spot-spacing-0-75 0 + margin: 1rem 1rem 1rem 0 &:last-child margin-right: 0 diff --git a/frontend/src/stimulus/controllers/dynamic/work-packages/activities-tab/index.controller.ts b/frontend/src/stimulus/controllers/dynamic/work-packages/activities-tab/index.controller.ts index 51da16177367..80c8305a3165 100644 --- a/frontend/src/stimulus/controllers/dynamic/work-packages/activities-tab/index.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/work-packages/activities-tab/index.controller.ts @@ -4,7 +4,6 @@ import { } from 'core-app/shared/components/editor/components/ckeditor/ckeditor.types'; import { TurboRequestsService } from 'core-app/core/turbo/turbo-requests.service'; import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; -import { IanCenterService } from 'core-app/features/in-app-notifications/center/state/ian-center.service'; interface CustomEventWithIdParam extends Event { params:{ @@ -56,13 +55,11 @@ export default class IndexController extends Controller { private turboRequests:TurboRequestsService; private apiV3Service:ApiV3Service; - private ianCenterService:IanCenterService; async connect() { const context = await window.OpenProject.getPluginContext(); this.turboRequests = context.services.turboRequests; this.apiV3Service = context.services.apiV3Service; - this.ianCenterService = context.services.ianCenter; this.setLocalStorageKey(); this.setLastUpdateTimestamp(); @@ -166,7 +163,7 @@ export default class IndexController extends Controller { // otherwise the browser will perform an auto scroll to the before focused button after the stream update was applied this.unfocusReactionButtons(); - const journalsContainerAtBottom = this.isJournalsContainerScrolledToBottom(this.journalsContainerTarget); + const journalsContainerAtBottom = this.isJournalsContainerScrolledToBottom(); void this.performUpdateStreamsRequest(this.prepareUpdateStreamsUrl()) .then((html) => { @@ -224,7 +221,7 @@ export default class IndexController extends Controller { } private updateNotificationCenter() { - this.ianCenterService.updateImmediate(); + document.dispatchEvent(new Event('ian-update-immediate')); } private performAutoScrolling(html:string, journalsContainerAtBottom:boolean) { @@ -239,7 +236,7 @@ export default class IndexController extends Controller { if (this.isMobile()) { this.scrollInputContainerIntoView(300); } else { - this.scrollJournalContainer(this.journalsContainerTarget, true, true); + this.scrollJournalContainer(true, true); } } }, 100); @@ -273,7 +270,7 @@ export default class IndexController extends Controller { } private scrollToActivity(activityId:string) { - const scrollableContainer = jQuery(this.element).scrollParent()[0]; + const scrollableContainer = this.getScrollableContainer(); const activityElement = document.getElementById(`activity-anchor-${activityId}`); if (activityElement && scrollableContainer) { @@ -282,7 +279,7 @@ export default class IndexController extends Controller { } private scrollToBottom() { - const scrollableContainer = jQuery(this.element).scrollParent()[0]; + const scrollableContainer = this.getScrollableContainer(); if (scrollableContainer) { scrollableContainer.scrollTop = scrollableContainer.scrollHeight; } @@ -314,6 +311,19 @@ export default class IndexController extends Controller { return this.element.querySelector('.work-packages-activities-tab-journals-new-component'); } + private getScrollableContainer():HTMLElement | null { + if (this.isWithinNotificationCenter()) { + // valid for both mobile and desktop + return document.querySelector('.work-package-details-tab') as HTMLElement; + } + if (this.isMobile()) { + return document.querySelector('#content-body') as HTMLElement; + } + + // valid for desktop + return document.querySelector('.tabcontent') as HTMLElement; + } + // Code Maintenance: Get rid of this JS based view port checks when activities are rendered in fully primierized activity tab in all contexts private isMobile():boolean { return window.innerWidth < 1279; @@ -354,31 +364,25 @@ export default class IndexController extends Controller { this.journalsContainerTarget.style.marginBottom = `${this.formRowTarget.clientHeight + 29}px`; } - private isJournalsContainerScrolledToBottom(journalsContainer:HTMLElement) { + private isJournalsContainerScrolledToBottom() { let atBottom = false; // we have to handle different scrollable containers for different viewports/pages in order to idenfity if the user is at the bottom of the journals // DOM structure different for notification center and workpackage detail view as well - // seems way to hacky for me, but I couldn't find a better solution - if (this.isMobile() && !this.isWithinNotificationCenter()) { - const scrollableContainer = document.querySelector('#content-body') as HTMLElement; - - atBottom = (scrollableContainer.scrollTop + scrollableContainer.clientHeight + 10) >= scrollableContainer.scrollHeight; - } else { - const scrollableContainer = jQuery(journalsContainer).scrollParent()[0]; - + const scrollableContainer = this.getScrollableContainer(); + if (scrollableContainer) { atBottom = (scrollableContainer.scrollTop + scrollableContainer.clientHeight + 10) >= scrollableContainer.scrollHeight; } return atBottom; } - private scrollJournalContainer(journalsContainer:HTMLElement, toBottom:boolean, smooth:boolean = false) { - const scrollableContainer = jQuery(journalsContainer).scrollParent()[0]; + private scrollJournalContainer(toBottom:boolean, smooth:boolean = false) { + const scrollableContainer = this.getScrollableContainer(); if (scrollableContainer) { if (smooth) { scrollableContainer.scrollTo({ top: toBottom ? scrollableContainer.scrollHeight : 0, - behavior: 'smooth', + behavior: 'smooth', }); } else { scrollableContainer.scrollTop = toBottom ? scrollableContainer.scrollHeight : 0; @@ -406,7 +410,7 @@ export default class IndexController extends Controller { } showForm() { - const journalsContainerAtBottom = this.isJournalsContainerScrolledToBottom(this.journalsContainerTarget); + const journalsContainerAtBottom = this.isJournalsContainerScrolledToBottom(); this.buttonRowTarget.classList.add('d-none'); this.formRowTarget.classList.remove('d-none'); @@ -415,22 +419,20 @@ export default class IndexController extends Controller { this.addEventListenersToCkEditorInstance(); if (this.isMobile()) { - this.scrollInputContainerIntoView(300); + // timeout amount tested on mobile devices for best possible user experience + this.scrollInputContainerIntoView(100); // first bring the input container fully into view (before focusing!) + this.focusEditor(400); // wait before focusing to avoid interference with the auto scroll } else if (this.sortingValue === 'asc' && journalsContainerAtBottom) { // scroll to (new) bottom if sorting is ascending and journals container was already at bottom before showing the form - this.scrollJournalContainer(this.journalsContainerTarget, true); - } - - const ckEditorInstance = this.getCkEditorInstance(); - if (ckEditorInstance) { - setTimeout(() => ckEditorInstance.editing.view.focus(), 10); + this.scrollJournalContainer(true); + this.focusEditor(); } } - focusEditor() { + focusEditor(timeout:number = 10) { const ckEditorInstance = this.getCkEditorInstance(); if (ckEditorInstance) { - setTimeout(() => ckEditorInstance.editing.view.focus(), 10); + setTimeout(() => ckEditorInstance.editing.view.focus(), timeout); } } @@ -483,7 +485,11 @@ export default class IndexController extends Controller { this.journalsContainerTarget.classList.remove('work-packages-activities-tab-index-component--journals-container_with-input-compensation'); } - if (this.isMobile()) { this.scrollInputContainerIntoView(300); } + if (this.isMobile()) { + // wait for the keyboard to be fully down before scrolling further + // timeout amount tested on mobile devices for best possible user experience + this.scrollInputContainerIntoView(500); + } } onBlurEditor() { @@ -552,13 +558,15 @@ export default class IndexController extends Controller { this.adjustJournalsContainer(); setTimeout(() => { - this.scrollJournalContainer( - this.journalsContainerTarget, - this.sortingValue === 'asc', - true, - ); - if (this.isMobile()) { - this.scrollInputContainerIntoView(300); + if (this.isMobile() && !this.isWithinNotificationCenter()) { + // wait for the keyboard to be fully down before scrolling further + // timeout amount tested on mobile devices for best possible user experience + this.scrollInputContainerIntoView(800); + } else { + this.scrollJournalContainer( + this.sortingValue === 'asc', + true, + ); } }, 10); diff --git a/modules/storages/config/locales/crowdin/af.yml b/modules/storages/config/locales/crowdin/af.yml index f1abfc92757b..24378828dc0d 100644 --- a/modules/storages/config/locales/crowdin/af.yml +++ b/modules/storages/config/locales/crowdin/af.yml @@ -114,7 +114,7 @@ af: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/ar.yml b/modules/storages/config/locales/crowdin/ar.yml index 9c5e59ebcbb4..928909ca070b 100644 --- a/modules/storages/config/locales/crowdin/ar.yml +++ b/modules/storages/config/locales/crowdin/ar.yml @@ -114,7 +114,7 @@ ar: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/az.yml b/modules/storages/config/locales/crowdin/az.yml index 02d3cb7ad4d4..04ec559a2802 100644 --- a/modules/storages/config/locales/crowdin/az.yml +++ b/modules/storages/config/locales/crowdin/az.yml @@ -114,7 +114,7 @@ az: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/be.yml b/modules/storages/config/locales/crowdin/be.yml index 1e081e819a35..ace876a794d0 100644 --- a/modules/storages/config/locales/crowdin/be.yml +++ b/modules/storages/config/locales/crowdin/be.yml @@ -114,7 +114,7 @@ be: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/bg.yml b/modules/storages/config/locales/crowdin/bg.yml index e55d1a027ce3..8e72e4191458 100644 --- a/modules/storages/config/locales/crowdin/bg.yml +++ b/modules/storages/config/locales/crowdin/bg.yml @@ -114,7 +114,7 @@ bg: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/ca.yml b/modules/storages/config/locales/crowdin/ca.yml index ced6e5a24eb0..cc25c5021b8a 100644 --- a/modules/storages/config/locales/crowdin/ca.yml +++ b/modules/storages/config/locales/crowdin/ca.yml @@ -114,7 +114,7 @@ ca: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/ckb-IR.yml b/modules/storages/config/locales/crowdin/ckb-IR.yml index f409575cea2d..2d85756fba15 100644 --- a/modules/storages/config/locales/crowdin/ckb-IR.yml +++ b/modules/storages/config/locales/crowdin/ckb-IR.yml @@ -114,7 +114,7 @@ ckb-IR: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/cs.yml b/modules/storages/config/locales/crowdin/cs.yml index 3b5d41662045..3dae94aaf154 100644 --- a/modules/storages/config/locales/crowdin/cs.yml +++ b/modules/storages/config/locales/crowdin/cs.yml @@ -114,7 +114,7 @@ cs: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/da.yml b/modules/storages/config/locales/crowdin/da.yml index f13cf777b697..d5af1ac54aaa 100644 --- a/modules/storages/config/locales/crowdin/da.yml +++ b/modules/storages/config/locales/crowdin/da.yml @@ -114,7 +114,7 @@ da: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/el.yml b/modules/storages/config/locales/crowdin/el.yml index 3940b366b6b3..bc5374be3c5a 100644 --- a/modules/storages/config/locales/crowdin/el.yml +++ b/modules/storages/config/locales/crowdin/el.yml @@ -114,7 +114,7 @@ el: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/eo.yml b/modules/storages/config/locales/crowdin/eo.yml index 6e6dfd3a0665..8537766e403e 100644 --- a/modules/storages/config/locales/crowdin/eo.yml +++ b/modules/storages/config/locales/crowdin/eo.yml @@ -114,7 +114,7 @@ eo: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/es.yml b/modules/storages/config/locales/crowdin/es.yml index 79b1a4a52407..68f2e38edd84 100644 --- a/modules/storages/config/locales/crowdin/es.yml +++ b/modules/storages/config/locales/crowdin/es.yml @@ -114,7 +114,7 @@ es: hide_inactive_folders: permission_not_set: no se han podido establecer permisos en %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'El usuario %{user} no se ha podido eliminar del grupo %{group} por la siguiente razón: %{reason}' diff --git a/modules/storages/config/locales/crowdin/et.yml b/modules/storages/config/locales/crowdin/et.yml index 7bbf47ae2386..af19ed8a8d0a 100644 --- a/modules/storages/config/locales/crowdin/et.yml +++ b/modules/storages/config/locales/crowdin/et.yml @@ -114,7 +114,7 @@ et: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/eu.yml b/modules/storages/config/locales/crowdin/eu.yml index d307c9212f7d..4943799e4c5c 100644 --- a/modules/storages/config/locales/crowdin/eu.yml +++ b/modules/storages/config/locales/crowdin/eu.yml @@ -114,7 +114,7 @@ eu: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/fa.yml b/modules/storages/config/locales/crowdin/fa.yml index 1dd6b7c9de48..40109097eeec 100644 --- a/modules/storages/config/locales/crowdin/fa.yml +++ b/modules/storages/config/locales/crowdin/fa.yml @@ -114,7 +114,7 @@ fa: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/fi.yml b/modules/storages/config/locales/crowdin/fi.yml index adb27293ab8b..5ecce54f89dd 100644 --- a/modules/storages/config/locales/crowdin/fi.yml +++ b/modules/storages/config/locales/crowdin/fi.yml @@ -114,7 +114,7 @@ fi: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/fil.yml b/modules/storages/config/locales/crowdin/fil.yml index f5026f1eae8e..fd1c5aa4c9af 100644 --- a/modules/storages/config/locales/crowdin/fil.yml +++ b/modules/storages/config/locales/crowdin/fil.yml @@ -114,7 +114,7 @@ fil: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/fr.yml b/modules/storages/config/locales/crowdin/fr.yml index c19df03639e9..69af9abd3b78 100644 --- a/modules/storages/config/locales/crowdin/fr.yml +++ b/modules/storages/config/locales/crowdin/fr.yml @@ -114,7 +114,7 @@ fr: hide_inactive_folders: permission_not_set: n'a pas pu définir les autorisations sur %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'L''utilisateur %{user} n''a pas pu être retiré du groupe %{group} pour la raison suivante : %{reason}' @@ -363,7 +363,7 @@ fr: project_storage_members: subtitle: Vérifiez l'état de la connexion pour l'espace de stockage %{storage_name_link} de tous les membres du projet. title: Statut de connexion des membres - permission_header_explanation: Les droits d'accès aux fichiers sur les supports de stockage externes ne sont appliqués qu'aux dossiers et aux fichiers contenus dans les dossiers de projet gérés automatiquement. Notez que toutes les droits sur les fichiers ne sont pas pris en charge par tous les fournisseurs de stockage. Veuillez consulter la documentation sur les autorisations de stockage de fichiers pour plus d'informations. + permission_header_explanation: 'Les droits d''accès aux fichiers sur les supports de stockage externes ne sont appliqués qu''aux dossiers et aux fichiers contenus dans les dossiers de projet gérés automatiquement. Notez que toutes les droits sur les fichiers ne sont pas pris en charge par tous les fournisseurs de stockage. Veuillez consulter la documentation sur les autorisations de stockage de fichiers pour plus d''informations.' provider_types: label: Type de fournisseur nextcloud: diff --git a/modules/storages/config/locales/crowdin/he.yml b/modules/storages/config/locales/crowdin/he.yml index e152ed296968..73a079d69e8b 100644 --- a/modules/storages/config/locales/crowdin/he.yml +++ b/modules/storages/config/locales/crowdin/he.yml @@ -114,7 +114,7 @@ he: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/hi.yml b/modules/storages/config/locales/crowdin/hi.yml index bab66a260a21..c470c774795a 100644 --- a/modules/storages/config/locales/crowdin/hi.yml +++ b/modules/storages/config/locales/crowdin/hi.yml @@ -114,7 +114,7 @@ hi: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/hr.yml b/modules/storages/config/locales/crowdin/hr.yml index ee0e4cf96315..68999d48526c 100644 --- a/modules/storages/config/locales/crowdin/hr.yml +++ b/modules/storages/config/locales/crowdin/hr.yml @@ -114,7 +114,7 @@ hr: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/hu.yml b/modules/storages/config/locales/crowdin/hu.yml index c5f25aac671a..dce2b92a2ca3 100644 --- a/modules/storages/config/locales/crowdin/hu.yml +++ b/modules/storages/config/locales/crowdin/hu.yml @@ -114,7 +114,7 @@ hu: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/id.yml b/modules/storages/config/locales/crowdin/id.yml index 41035588e69e..e5ab1988442b 100644 --- a/modules/storages/config/locales/crowdin/id.yml +++ b/modules/storages/config/locales/crowdin/id.yml @@ -114,7 +114,7 @@ id: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/it.yml b/modules/storages/config/locales/crowdin/it.yml index e5fd9b47c1fa..7c3e4057da8f 100644 --- a/modules/storages/config/locales/crowdin/it.yml +++ b/modules/storages/config/locales/crowdin/it.yml @@ -114,7 +114,7 @@ it: hide_inactive_folders: permission_not_set: impossibile impostare i permessi su %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'Impossibile rimuovere l''utente %{user} dal gruppo %{group} per il seguente motivo: %{reason}' diff --git a/modules/storages/config/locales/crowdin/ja.yml b/modules/storages/config/locales/crowdin/ja.yml index b15d49d59031..8d36a7616090 100644 --- a/modules/storages/config/locales/crowdin/ja.yml +++ b/modules/storages/config/locales/crowdin/ja.yml @@ -114,7 +114,7 @@ ja: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/ka.yml b/modules/storages/config/locales/crowdin/ka.yml index 68c94878e3f5..dacbda606356 100644 --- a/modules/storages/config/locales/crowdin/ka.yml +++ b/modules/storages/config/locales/crowdin/ka.yml @@ -114,7 +114,7 @@ ka: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/kk.yml b/modules/storages/config/locales/crowdin/kk.yml index 8b51bda47e91..9664b29bb04f 100644 --- a/modules/storages/config/locales/crowdin/kk.yml +++ b/modules/storages/config/locales/crowdin/kk.yml @@ -114,7 +114,7 @@ kk: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/ko.yml b/modules/storages/config/locales/crowdin/ko.yml index 65105b78ce50..2dda2c05063b 100644 --- a/modules/storages/config/locales/crowdin/ko.yml +++ b/modules/storages/config/locales/crowdin/ko.yml @@ -114,7 +114,7 @@ ko: hide_inactive_folders: permission_not_set: '- %{path}에 대한 권한을 설정할 수 없습니다.' remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: '다음과 같은 이유로 %{user} 사용자를 %{group} 그룹에서 제거할 수 없습니다: %{reason}' diff --git a/modules/storages/config/locales/crowdin/lt.yml b/modules/storages/config/locales/crowdin/lt.yml index 89b15abb6348..a0aac07c6952 100644 --- a/modules/storages/config/locales/crowdin/lt.yml +++ b/modules/storages/config/locales/crowdin/lt.yml @@ -114,7 +114,7 @@ lt: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/lv.yml b/modules/storages/config/locales/crowdin/lv.yml index 6a7b7819fcbe..9bdd1b5dc788 100644 --- a/modules/storages/config/locales/crowdin/lv.yml +++ b/modules/storages/config/locales/crowdin/lv.yml @@ -114,7 +114,7 @@ lv: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/mn.yml b/modules/storages/config/locales/crowdin/mn.yml index 399f6a147d7b..635c3ceda2e4 100644 --- a/modules/storages/config/locales/crowdin/mn.yml +++ b/modules/storages/config/locales/crowdin/mn.yml @@ -114,7 +114,7 @@ mn: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/ms.yml b/modules/storages/config/locales/crowdin/ms.yml index 6eb0cf7b70ba..9281fa26217f 100644 --- a/modules/storages/config/locales/crowdin/ms.yml +++ b/modules/storages/config/locales/crowdin/ms.yml @@ -114,7 +114,7 @@ ms: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/ne.yml b/modules/storages/config/locales/crowdin/ne.yml index 67c012957a88..a6ca390e59ee 100644 --- a/modules/storages/config/locales/crowdin/ne.yml +++ b/modules/storages/config/locales/crowdin/ne.yml @@ -114,7 +114,7 @@ ne: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/nl.yml b/modules/storages/config/locales/crowdin/nl.yml index 8792442ce019..0ce5af0deaf4 100644 --- a/modules/storages/config/locales/crowdin/nl.yml +++ b/modules/storages/config/locales/crowdin/nl.yml @@ -114,7 +114,7 @@ nl: hide_inactive_folders: permission_not_set: Kan machtigingen niet instellen op %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/no.yml b/modules/storages/config/locales/crowdin/no.yml index 3fca22a5ceeb..f0795af80ea8 100644 --- a/modules/storages/config/locales/crowdin/no.yml +++ b/modules/storages/config/locales/crowdin/no.yml @@ -114,7 +114,7 @@ hide_inactive_folders: permission_not_set: kunne ikke sette tillatelser på %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'Brukeren %{user} kunne ikke fjernes fra %{group} gruppen av følgende årsak: %{reason}' diff --git a/modules/storages/config/locales/crowdin/pl.yml b/modules/storages/config/locales/crowdin/pl.yml index 3c520ac5a29a..b0d8bfba832c 100644 --- a/modules/storages/config/locales/crowdin/pl.yml +++ b/modules/storages/config/locales/crowdin/pl.yml @@ -114,7 +114,7 @@ pl: hide_inactive_folders: permission_not_set: nie można ustawić uprawnień do lokalizacji %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'Użytkownika %{user} nie można było usunąć z grupy %{group} z następującego powodu: %{reason}' diff --git a/modules/storages/config/locales/crowdin/pt-BR.yml b/modules/storages/config/locales/crowdin/pt-BR.yml index d249e26d7bc2..be14e6f46cf0 100644 --- a/modules/storages/config/locales/crowdin/pt-BR.yml +++ b/modules/storages/config/locales/crowdin/pt-BR.yml @@ -114,7 +114,7 @@ pt-BR: hide_inactive_folders: permission_not_set: não foi possível definir permissões em %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'O usuário %{user} não pôde ser removido do grupo %{group} pelo seguinte motivo: %{reason}' diff --git a/modules/storages/config/locales/crowdin/pt-PT.yml b/modules/storages/config/locales/crowdin/pt-PT.yml index ba7d57370860..ee71ed7d8ce6 100644 --- a/modules/storages/config/locales/crowdin/pt-PT.yml +++ b/modules/storages/config/locales/crowdin/pt-PT.yml @@ -114,7 +114,7 @@ pt-PT: hide_inactive_folders: permission_not_set: não foi possível definir permissões em %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'Não foi possível remover o utilizador %{user} do grupo %{group} pelo seguinte motivo: %{reason}' diff --git a/modules/storages/config/locales/crowdin/ro.yml b/modules/storages/config/locales/crowdin/ro.yml index f40a74241926..c6519c8c3adf 100644 --- a/modules/storages/config/locales/crowdin/ro.yml +++ b/modules/storages/config/locales/crowdin/ro.yml @@ -114,7 +114,7 @@ ro: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/rw.yml b/modules/storages/config/locales/crowdin/rw.yml index 654a9358fe21..0936ba9ca64a 100644 --- a/modules/storages/config/locales/crowdin/rw.yml +++ b/modules/storages/config/locales/crowdin/rw.yml @@ -114,7 +114,7 @@ rw: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/si.yml b/modules/storages/config/locales/crowdin/si.yml index 69e2d4164bea..ca88a30287fd 100644 --- a/modules/storages/config/locales/crowdin/si.yml +++ b/modules/storages/config/locales/crowdin/si.yml @@ -114,7 +114,7 @@ si: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/sk.yml b/modules/storages/config/locales/crowdin/sk.yml index 5a304f36db3a..de1eac21f4bc 100644 --- a/modules/storages/config/locales/crowdin/sk.yml +++ b/modules/storages/config/locales/crowdin/sk.yml @@ -114,7 +114,7 @@ sk: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/sl.yml b/modules/storages/config/locales/crowdin/sl.yml index 1585d79eb9bd..42bedbb6201b 100644 --- a/modules/storages/config/locales/crowdin/sl.yml +++ b/modules/storages/config/locales/crowdin/sl.yml @@ -114,7 +114,7 @@ sl: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/sr.yml b/modules/storages/config/locales/crowdin/sr.yml index c20840b8683a..c9caad751fc4 100644 --- a/modules/storages/config/locales/crowdin/sr.yml +++ b/modules/storages/config/locales/crowdin/sr.yml @@ -114,7 +114,7 @@ sr: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/sv.yml b/modules/storages/config/locales/crowdin/sv.yml index 22789a86a9a4..6d029c19a40a 100644 --- a/modules/storages/config/locales/crowdin/sv.yml +++ b/modules/storages/config/locales/crowdin/sv.yml @@ -114,7 +114,7 @@ sv: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/th.yml b/modules/storages/config/locales/crowdin/th.yml index 2b8a5cc9fc8d..70d7d8dcce49 100644 --- a/modules/storages/config/locales/crowdin/th.yml +++ b/modules/storages/config/locales/crowdin/th.yml @@ -114,7 +114,7 @@ th: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/tr.yml b/modules/storages/config/locales/crowdin/tr.yml index c8cad4a8c5ca..410ff8f34b18 100644 --- a/modules/storages/config/locales/crowdin/tr.yml +++ b/modules/storages/config/locales/crowdin/tr.yml @@ -114,7 +114,7 @@ tr: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/uz.yml b/modules/storages/config/locales/crowdin/uz.yml index c7fa6ae3ddf7..689db830b2ce 100644 --- a/modules/storages/config/locales/crowdin/uz.yml +++ b/modules/storages/config/locales/crowdin/uz.yml @@ -114,7 +114,7 @@ uz: hide_inactive_folders: permission_not_set: could not set permissions on %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'The user %{user} could not be removed from the %{group} group for the following reason: %{reason}' diff --git a/modules/storages/config/locales/crowdin/vi.yml b/modules/storages/config/locales/crowdin/vi.yml index 98c43339c830..ec61349ecb61 100644 --- a/modules/storages/config/locales/crowdin/vi.yml +++ b/modules/storages/config/locales/crowdin/vi.yml @@ -114,7 +114,7 @@ vi: hide_inactive_folders: permission_not_set: không thể thiết lập quyền trên %{path}. remote_folders: - not_allowed: The %{username} doesn't have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud. + not_allowed: '%{username} doesn''t have access to the %{group_folder} folder. Please check the folder permissions on Nextcloud.' not_found: "%{group_folder} folder wasn't found. Please check your Nextcloud setup." remove_user_from_group: conflict: 'Không thể xóa người dùng %{user} khỏi nhóm %{group} vì lý do sau: %{reason}' diff --git a/spec/models/queries/versions/version_query_spec.rb b/spec/models/queries/versions/version_query_spec.rb new file mode 100644 index 000000000000..02b5751d29af --- /dev/null +++ b/spec/models/queries/versions/version_query_spec.rb @@ -0,0 +1,150 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +require "spec_helper" + +RSpec.describe Queries::Versions::VersionQuery do + def instance = subject + + describe "ordering" do + before do + allow(OpenProject::Deprecation).to receive(:warn) + + instance.order(attribute => direction) if attribute + end + + describe "without it" do + let(:attribute) { nil } + + it "orders by id descending" do + expected = Version.order(id: :desc) + + expect(instance.results.to_sql).to eql expected.to_sql + end + end + + describe "unknown order" do + let(:attribute) { :unknown } + let(:direction) { :asc } + + it "returns a query not returning anything" do + expected = Version.where(Arel::Nodes::Equality.new(1, 0)) + + expect(instance.results.to_sql).to eql expected.to_sql + end + + it { is_expected.not_to be_valid } + end + + describe "by id" do + let(:attribute) { :id } + + describe "ascending" do + let(:direction) { :asc } + + it "is the same as handwriting the query" do + expected = Version.order(id: :asc) + + expect(instance.results.to_sql).to eql expected.to_sql + end + end + + describe "descending" do + let(:direction) { :desc } + + it "is the same as handwriting the query" do + expected = Version.order(id: :desc) + + expect(instance.results.to_sql).to eql expected.to_sql + end + end + end + + describe "by name" do + let(:attribute) { :name } + + describe "ascending" do + let(:direction) { :asc } + + it "is the same as handwriting the query" do + expected = Version.order(name: :asc).order(id: :desc) + + expect(instance.results.to_sql).to eql expected.to_sql + end + end + + describe "descending" do + let(:direction) { :desc } + + it "is the same as handwriting the query" do + expected = Version.order(name: :desc).order(id: :desc) + + expect(instance.results.to_sql).to eql expected.to_sql + end + end + end + + describe "by semver_name" do + let(:attribute) { :semver_name } + + describe "ascending" do + let(:direction) { :asc } + + it "is the same as handwriting the query" do + expected = Version.order(name: :asc).order(id: :desc) + + expect(instance.results.to_sql).to eql expected.to_sql + end + + it "warns about being deprecated" do + instance.results + + expect(OpenProject::Deprecation) + .to have_received(:warn).with("Sorting by semver_name is deprecated, name should be used instead") + end + end + + describe "descending" do + let(:direction) { :desc } + + it "is the same as handwriting the query" do + expected = Version.order(name: :desc).order(id: :desc) + + expect(instance.results.to_sql).to eql expected.to_sql + end + + it "warns about being deprecated" do + instance.results + + expect(OpenProject::Deprecation) + .to have_received(:warn).with("Sorting by semver_name is deprecated, name should be used instead") + end + end + end + end +end diff --git a/spec/models/query/results_version_integration_spec.rb b/spec/models/query/results_version_integration_spec.rb index e06e3f5f4b3a..70a434630692 100644 --- a/spec/models/query/results_version_integration_spec.rb +++ b/spec/models/query/results_version_integration_spec.rb @@ -102,7 +102,7 @@ end end let(:work_packages_asc) { [old_version_wp, no_date_version_wp, new_version_wp, no_version_wp] } - let(:work_packages_desc) { [new_version_wp, no_date_version_wp, old_version_wp, no_version_wp] } + let(:work_packages_desc) { [no_version_wp, new_version_wp, no_date_version_wp, old_version_wp] } before do login_as(user) diff --git a/spec/models/query/sort_criteria_spec.rb b/spec/models/query/sort_criteria_spec.rb index 04bf3de16177..db61ccc019ad 100644 --- a/spec/models/query/sort_criteria_spec.rb +++ b/spec/models/query/sort_criteria_spec.rb @@ -87,7 +87,7 @@ it "adds the order handling (and the default order by id)" do expect(subject) - .to eq [["work_packages.start_date NULLS LAST"], ["work_packages.id DESC"]] + .to eq [["work_packages.start_date"], ["work_packages.id DESC"]] end end @@ -96,7 +96,7 @@ it "adds the order handling (and the default order by id)" do expect(subject) - .to eq [["work_packages.start_date NULLS LAST"], ["work_packages.id DESC"]] + .to eq [["work_packages.start_date"], ["work_packages.id DESC"]] end end @@ -105,7 +105,7 @@ it "adds the order handling (and the default order by id)" do expect(subject) - .to eq [["work_packages.start_date DESC NULLS LAST"], ["work_packages.id DESC"]] + .to eq [["work_packages.start_date DESC"], ["work_packages.id DESC"]] end end @@ -114,8 +114,8 @@ it "adds the order handling (and the default order by id)" do expect(subject) - .to eq [["name DESC NULLS LAST"], - ["work_packages.start_date NULLS LAST"], + .to eq [["name DESC"], + ["work_packages.start_date"], ["work_packages.id DESC"]] end end