summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/actions/setup-redmine/action.yml68
-rw-r--r--.github/workflows/linters.yml55
-rw-r--r--.github/workflows/rubyonrails.yml17
-rw-r--r--.github/workflows/tests.yml103
-rw-r--r--.stylelintrc5
-rw-r--r--Gemfile10
-rw-r--r--app/assets/images/chevron-down.svg1
-rw-r--r--app/assets/images/chevron-right-idnt.svg1
-rw-r--r--app/assets/images/file-rss.svg1
-rw-r--r--app/assets/images/hourglass-empty.svg1
-rw-r--r--app/assets/images/icons.svg39
-rw-r--r--app/assets/javascripts/application.js61
-rw-r--r--app/assets/javascripts/attachments.js14
-rw-r--r--app/assets/javascripts/gantt.js2
-rw-r--r--app/assets/stylesheets/application.css167
-rw-r--r--app/assets/stylesheets/context_menu.css4
-rw-r--r--app/assets/stylesheets/context_menu_rtl.css11
-rw-r--r--app/assets/stylesheets/responsive.css30
-rw-r--r--app/assets/stylesheets/rtl.css72
-rw-r--r--app/assets/stylesheets/scm.css42
-rw-r--r--app/assets/stylesheets/wiki_syntax.css38
-rw-r--r--app/assets/stylesheets/wiki_syntax_detailed.css38
-rw-r--r--app/controllers/admin_controller.rb4
-rw-r--r--app/controllers/application_controller.rb6
-rw-r--r--app/controllers/auto_completes_controller.rb2
-rw-r--r--app/controllers/queries_controller.rb34
-rw-r--r--app/controllers/repositories_controller.rb10
-rw-r--r--app/controllers/wiki_controller.rb1
-rw-r--r--app/helpers/application_helper.rb15
-rw-r--r--app/helpers/icons_helper.rb30
-rw-r--r--app/helpers/queries_helper.rb6
-rw-r--r--app/helpers/repositories_helper.rb4
-rw-r--r--app/helpers/settings_helper.rb6
-rw-r--r--app/models/email_address.rb6
-rw-r--r--app/models/issue_priority.rb8
-rw-r--r--app/models/member.rb2
-rw-r--r--app/models/project_admin_query.rb63
-rw-r--r--app/models/project_query.rb44
-rw-r--r--app/models/query.rb4
-rw-r--r--app/models/time_entry_query.rb19
-rw-r--r--app/models/user.rb4
-rw-r--r--app/models/user_query.rb18
-rw-r--r--app/views/activities/_activities.html.erb2
-rw-r--r--app/views/admin/projects.html.erb4
-rw-r--r--app/views/attachments/_form.html.erb9
-rw-r--r--app/views/attachments/other.html.erb1
-rw-r--r--app/views/calendars/show.html.erb2
-rw-r--r--app/views/common/_tabs.html.erb8
-rw-r--r--app/views/common/error.html.erb5
-rw-r--r--app/views/context_menus/issues.html.erb9
-rw-r--r--app/views/context_menus/time_entries.html.erb2
-rw-r--r--app/views/custom_field_enumerations/index.html.erb2
-rw-r--r--app/views/custom_fields/index.api.rsb1
-rw-r--r--app/views/gantts/show.html.erb4
-rw-r--r--app/views/help/wiki_syntax/common_mark/en/wiki_syntax_detailed_common_mark.html.erb4
-rw-r--r--app/views/imports/_issues_mapping.html.erb2
-rw-r--r--app/views/issue_relations/_form.html.erb1
-rw-r--r--app/views/issues/_conflict.html.erb1
-rw-r--r--app/views/issues/_list.html.erb4
-rw-r--r--app/views/issues/bulk_edit.html.erb2
-rw-r--r--app/views/layouts/base.html.erb4
-rw-r--r--app/views/projects/settings/_members.html.erb10
-rw-r--r--app/views/queries/_filters.html.erb1
-rw-r--r--app/views/queries/_form.html.erb7
-rw-r--r--app/views/queries/_query_form.html.erb9
-rw-r--r--app/views/repositories/_dir_list_content.html.erb2
-rw-r--r--app/views/repositories/annotate.html.erb5
-rw-r--r--app/views/repositories/revision.html.erb10
-rw-r--r--app/views/roles/permissions.html.erb2
-rw-r--r--app/views/search/index.html.erb5
-rw-r--r--app/views/timelog/_list.html.erb4
-rw-r--r--app/views/timelog/bulk_edit.html.erb1
-rw-r--r--app/views/versions/_sidebar.html.erb4
-rw-r--r--app/views/versions/index.html.erb2
-rw-r--r--app/views/versions/show.html.erb2
-rw-r--r--app/views/welcome/index.html.erb2
-rw-r--r--app/views/wiki/show.html.erb2
-rw-r--r--app/views/workflows/edit.html.erb12
-rw-r--r--app/views/workflows/permissions.html.erb4
-rw-r--r--config/boot.rb16
-rw-r--r--config/icon_source.yml17
-rw-r--r--config/locales/cs.yml10
-rw-r--r--config/locales/fa.yml72
-rw-r--r--config/locales/sv.yml676
-rw-r--r--config/locales/tr.yml2
-rw-r--r--config/routes.rb6
-rw-r--r--doc/CHANGELOG260
-rw-r--r--doc/INSTALL14
-rw-r--r--doc/UPGRADING4
-rw-r--r--lib/redmine/activity.rb15
-rw-r--r--lib/redmine/field_format.rb3
-rw-r--r--lib/redmine/helpers/gantt.rb10
-rw-r--r--lib/redmine/i18n.rb19
-rw-r--r--lib/redmine/sudo_mode.rb2
-rw-r--r--lib/redmine/version.rb2
-rw-r--r--lib/redmine/wiki_formatting/common_mark/sanitization_filter.rb2
-rw-r--r--lib/redmine/wiki_formatting/macros.rb4
-rw-r--r--public/404.html6
-rw-r--r--public/500.html6
-rw-r--r--test/application_system_test_case.rb9
-rw-r--r--test/fixtures/changesets.yml12
-rw-r--r--test/fixtures/queries.yml2
-rw-r--r--test/functional/account_controller_test.rb18
-rw-r--r--test/functional/attachments_controller_test.rb6
-rw-r--r--test/functional/documents_controller_test.rb4
-rw-r--r--test/functional/issues_controller_test.rb50
-rw-r--r--test/functional/queries_controller_test.rb58
-rw-r--r--test/functional/repositories_bazaar_controller_test.rb1
-rw-r--r--test/functional/repositories_controller_test.rb2
-rw-r--r--test/functional/repositories_cvs_controller_test.rb1
-rw-r--r--test/functional/repositories_git_controller_test.rb1
-rw-r--r--test/functional/repositories_mercurial_controller_test.rb2
-rw-r--r--test/functional/repositories_subversion_controller_test.rb22
-rw-r--r--test/functional/search_controller_test.rb10
-rw-r--r--test/functional/workflows_controller_test.rb39
-rw-r--r--test/helpers/application_helper_test.rb48
-rw-r--r--test/integration/api_test/attachments_test.rb2
-rw-r--r--test/integration/api_test/custom_fields_test.rb2
-rw-r--r--test/integration/api_test/news_test.rb2
-rw-r--r--test/integration/attachments_test.rb10
-rw-r--r--test/integration/repositories_git_test.rb1
-rw-r--r--test/integration/routing/attachments_test.rb2
-rw-r--r--test/integration/sudo_mode_test.rb2
-rw-r--r--test/system/inline_autocomplete_test.rb47
-rw-r--r--test/system/issues_test.rb16
-rw-r--r--test/system/sudo_mode_test.rb3
-rw-r--r--test/system/timelog_test.rb3
-rw-r--r--test/unit/changeset_test.rb2
-rw-r--r--test/unit/email_address_test.rb6
-rw-r--r--test/unit/issue_priority_test.rb16
-rw-r--r--test/unit/lib/redmine/field_format/numeric_format_test.rb18
-rw-r--r--test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb1
-rw-r--r--test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb1
-rw-r--r--test/unit/lib/redmine/scm/adapters/git_adapter_test.rb9
-rw-r--r--test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb8
-rw-r--r--test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb1
-rw-r--r--test/unit/lib/redmine/wiki_formatting/common_mark/sanitization_filter_test.rb12
-rw-r--r--test/unit/lib/redmine/wiki_formatting/macros_test.rb4
-rw-r--r--test/unit/member_test.rb33
-rw-r--r--test/unit/project_admin_query_test.rb138
-rw-r--r--test/unit/project_query_test.rb34
-rw-r--r--test/unit/repository_bazaar_test.rb26
-rw-r--r--test/unit/repository_cvs_test.rb1
-rw-r--r--test/unit/repository_git_test.rb1
-rw-r--r--test/unit/repository_mercurial_test.rb9
-rw-r--r--test/unit/repository_subversion_test.rb1
-rw-r--r--test/unit/repository_test.rb10
-rw-r--r--test/unit/user_query_test.rb24
148 files changed, 2225 insertions, 868 deletions
diff --git a/.github/actions/setup-redmine/action.yml b/.github/actions/setup-redmine/action.yml
new file mode 100644
index 000000000..d637914f0
--- /dev/null
+++ b/.github/actions/setup-redmine/action.yml
@@ -0,0 +1,68 @@
+name: Setup Redmine Test Environment
+description: Composite action for setting up Redmine test environment
+
+inputs:
+ db-type:
+ description: 'Database type: postgresql, mysql2, or sqlite3. Note: postgresql and mysql2 require service containers to be defined in the workflow.'
+ required: true
+ ruby-version:
+ description: 'Ruby version to use'
+ required: true
+
+runs:
+ using: composite
+ steps:
+ - name: Install dependencies and configure environment
+ shell: bash
+ run: |
+ sudo apt-get update
+ sudo apt-get install --yes --quiet ghostscript gsfonts locales bzr cvs
+ sudo locale-gen en_US # for bazaar non ascii test
+
+ - name: Allow imagemagick to read PDF files
+ shell: bash
+ run: |
+ echo '<policymap>' > policy.xml
+ echo '<policy domain="coder" rights="read | write" pattern="PDF" />' >> policy.xml
+ echo '</policymap>' >> policy.xml
+ sudo rm /etc/ImageMagick-6/policy.xml
+ sudo mv policy.xml /etc/ImageMagick-6/policy.xml
+
+ - if: ${{ inputs.db-type == 'sqlite3' }}
+ name: Prepare test database for sqlite3
+ shell: bash
+ run: |
+ cat > config/database.yml <<EOF
+ test:
+ adapter: sqlite3
+ database: db/test.sqlite3
+ EOF
+
+ - if: ${{ inputs.db-type == 'mysql2' || inputs.db-type == 'postgresql' }}
+ name: Prepare test database for mysql2 and postgresql
+ shell: bash
+ run: |
+ cat > config/database.yml <<EOF
+ test:
+ adapter: ${{ inputs.db-type }}
+ database: redmine_test
+ username: root
+ password: root
+ host: 127.0.0.1
+ EOF
+
+ - name: Install Ruby and gems
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: ${{ inputs.ruby-version }}
+ bundler-cache: true
+
+ - name: Run prepare test environment
+ shell: bash
+ env:
+ RAILS_ENV: test
+ SCMS: subversion,git,git_utf8,filesystem,bazaar,cvs
+ run: |
+ bundle exec rake ci:about
+ bundle exec rake ci:setup
+ bundle exec rake db:environment:set
diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml
new file mode 100644
index 000000000..54ffd08df
--- /dev/null
+++ b/.github/workflows/linters.yml
@@ -0,0 +1,55 @@
+name: Lint
+
+on:
+ push:
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up Ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: 3.2
+ bundler-cache: true
+
+ - name: Lint code for consistent style
+ run: bundle exec rubocop --parallel
+
+ stylelint:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v2
+ with:
+ node-version: '20'
+
+ - name: Install dependencies
+ run: yarn install
+
+ - name: Lint CSS and SCSS files
+ run: npx stylelint "app/assets/stylesheets/**/*.css"
+
+ bundle-audit:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up Ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: '3.2'
+ bundler-cache: true
+
+ - name: Run bundle-audit
+ run: bundle exec bundle audit check --update
diff --git a/.github/workflows/rubyonrails.yml b/.github/workflows/rubyonrails.yml
index 07882bdc8..e69de29bb 100644
--- a/.github/workflows/rubyonrails.yml
+++ b/.github/workflows/rubyonrails.yml
@@ -1,17 +0,0 @@
-name: "Ruby on Rails CI"
-on:
- push:
-
-jobs:
- lint:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
- - name: Install Ruby and gems
- uses: ruby/setup-ruby@55283cc23133118229fd3f97f9336ee23a179fcf # v1.146.0
- with:
- ruby-version: 3.2
- bundler-cache: true
- - name: Lint Ruby files
- run: bundle exec rubocop --parallel
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 000000000..081dbc6a1
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,103 @@
+name: Tests
+
+on:
+ push:
+
+jobs:
+ tests:
+ name: test ${{matrix.db}} ruby-${{ matrix.ruby }}
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ ruby: ['3.1', '3.2', '3.3']
+ db: ['postgresql', 'mysql2', 'sqlite3']
+ fail-fast: false
+
+ services:
+ postgres:
+ image: postgres:13
+ env:
+ POSTGRES_DB: redmine_test
+ POSTGRES_USER: root
+ POSTGRES_PASSWORD: root
+ ports:
+ - 5432:5432
+ options: >-
+ --health-cmd pg_isready
+ --health-interval 10s
+ --health-timeout 5s
+ --health-retries 5
+
+ mysql:
+ image: mysql:8.0
+ env:
+ MYSQL_DATABASE: redmine_test
+ MYSQL_ROOT_PASSWORD: 'root'
+ ports:
+ - 3306:3306
+ options: >-
+ --health-cmd="mysqladmin ping"
+ --health-interval=10s
+ --health-timeout=5s
+ --health-retries=3
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Redmine test environment
+ uses: ./.github/actions/setup-redmine
+ with:
+ db-type: ${{ matrix.db }}
+ ruby-version: ${{ matrix.ruby }}
+
+ - name: Run tests
+ run: |
+ bin/rails test
+
+ - name: Run bazaar non ascii test
+ env:
+ LANG: en_US.ISO8859-1
+ LC_ALL: en_US.ISO8859-1
+ run: |
+ bin/rails test test/unit/repository_bazaar_test.rb
+
+ - name: Run autoload test
+ run: |
+ bin/rails test:autoload
+
+ system-tests:
+ name: system test
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Redmine test environment
+ uses: ./.github/actions/setup-redmine
+ with:
+ db-type: sqlite3
+ ruby-version: '3.3'
+
+ # System tests use Chrome and ChromeDriver installed on the GitHub Actions Ubuntu image.
+ # They are generally updated to the latest stable versions.
+ - name: Display Chrome version
+ run: google-chrome --version
+
+ - name: Run system tests
+ run: bin/rails test:system
+ env:
+ GOOGLE_CHROME_OPTS_ARGS: headless,disable-gpu,no-sandbox,disable-dev-shm-usage
+ # System tests might still be a bit unstable, so for now, even if a system test fails,
+ # output the results and consider the overall test as successful.
+ continue-on-error: true
+
+ - name: Upload system test screenshots
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: system-test-screenshots
+ path: tmp/screenshots
+ if-no-files-found: ignore
diff --git a/.stylelintrc b/.stylelintrc
index ea5c7df0f..6ee7b505e 100644
--- a/.stylelintrc
+++ b/.stylelintrc
@@ -22,5 +22,8 @@
"no-duplicate-selectors": true,
"no-empty-source": true,
"no-invalid-double-slash-comments": true,
- }
+ },
+ "ignoreFiles": [
+ "app/assets/stylesheets/jquery/*.css"
+ ]
}
diff --git a/Gemfile b/Gemfile
index 732333daf..a3f816c99 100644
--- a/Gemfile
+++ b/Gemfile
@@ -9,9 +9,9 @@ gem "actionpack-xml_parser"
gem 'roadie-rails', '~> 3.2.0'
gem 'marcel'
gem 'mail', '~> 2.8.1'
-gem 'nokogiri', '~> 1.16.0'
+gem 'nokogiri', '~> 1.18.3'
gem 'i18n', '~> 1.14.1'
-gem 'rbpdf', '~> 1.21.3'
+gem 'rbpdf', '~> 1.21.4'
gem 'addressable'
gem 'rubyzip', '~> 2.3.0'
gem 'propshaft', '~> 1.1.0'
@@ -19,7 +19,7 @@ gem 'rack', '>= 3.1.3'
# Ruby Standard Gems
gem 'csv', '~> 3.2.8'
-gem 'net-imap', '~> 0.4.8'
+gem 'net-imap', '~> 0.4.20'
gem 'net-pop', '~> 0.1.2'
gem 'net-smtp', '~> 0.4.0'
@@ -101,7 +101,7 @@ group :development do
end
group :test do
- gem "rails-dom-testing"
+ gem "rails-dom-testing", '>= 2.3.0'
gem 'mocha', '>= 2.0.1'
gem 'simplecov', '~> 0.22.0', :require => false
gem "ffi", platforms: [:mingw, :x64_mingw, :mswin]
@@ -111,8 +111,10 @@ group :test do
gem 'selenium-webdriver', '>= 4.11.0'
# RuboCop
gem 'rubocop', '~> 1.68.0', require: false
+ gem 'rubocop-ast', '~> 1.40.0', require: false
gem 'rubocop-performance', '~> 1.22.0', require: false
gem 'rubocop-rails', '~> 2.27.0', require: false
+ gem 'bundle-audit', require: false
end
local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local")
diff --git a/app/assets/images/chevron-down.svg b/app/assets/images/chevron-down.svg
new file mode 100644
index 000000000..7dfc75f54
--- /dev/null
+++ b/app/assets/images/chevron-down.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#ccd" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-chevron-down"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M6 9l6 6l6 -6" /></svg> \ No newline at end of file
diff --git a/app/assets/images/chevron-right-idnt.svg b/app/assets/images/chevron-right-idnt.svg
new file mode 100644
index 000000000..c15529e90
--- /dev/null
+++ b/app/assets/images/chevron-right-idnt.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#ccd" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg> \ No newline at end of file
diff --git a/app/assets/images/file-rss.svg b/app/assets/images/file-rss.svg
new file mode 100644
index 000000000..72ab4e29b
--- /dev/null
+++ b/app/assets/images/file-rss.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="#169"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 2l.117 .007a1 1 0 0 1 .876 .876l.007 .117v4l.005 .15a2 2 0 0 0 1.838 1.844l.157 .006h4l.117 .007a1 1 0 0 1 .876 .876l.007 .117v9a3 3 0 0 1 -2.824 2.995l-.176 .005h-10a3 3 0 0 1 -2.995 -2.824l-.005 -.176v-14a3 3 0 0 1 2.824 -2.995l.176 -.005zm-3 11a1 1 0 0 0 0 2a2 2 0 0 1 1.995 1.85l.005 .15a1 1 0 0 0 2 0a4 4 0 0 0 -4 -4m0 -3a1 1 0 0 0 0 2a5 5 0 0 1 5 5a1 1 0 0 0 2 0a7 7 0 0 0 -7 -7m.01 6h-.01a1 1 0 0 0 -.117 1.993l.127 .007a1 1 0 0 0 0 -2m5.989 -13.001l4.001 4.001h-4z" /></svg> \ No newline at end of file
diff --git a/app/assets/images/hourglass-empty.svg b/app/assets/images/hourglass-empty.svg
new file mode 100644
index 000000000..789eb193c
--- /dev/null
+++ b/app/assets/images/hourglass-empty.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#169" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-hourglass-empty"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M6 20v-2a6 6 0 1 1 12 0v2a1 1 0 0 1 -1 1h-10a1 1 0 0 1 -1 -1z" /><path d="M6 4v2a6 6 0 1 0 12 0v-2a1 1 0 0 0 -1 -1h-10a1 1 0 0 0 -1 1z" /></svg> \ No newline at end of file
diff --git a/app/assets/images/icons.svg b/app/assets/images/icons.svg
index 3bd8ae53b..384053145 100644
--- a/app/assets/images/icons.svg
+++ b/app/assets/images/icons.svg
@@ -14,6 +14,9 @@
<symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--angle-down">
<path d="M6 9l6 6l6 -6"/>
</symbol>
+ <symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--angle-left">
+ <path d="M15 6l-6 6l6 6"/>
+ </symbol>
<symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--angle-right">
<path d="M9 6l6 6l-6 6"/>
</symbol>
@@ -104,6 +107,13 @@
<path d="M7 7l5 5l-5 5"/>
<path d="M13 7l5 5l-5 5"/>
</symbol>
+ <symbol viewBox="0 0 24 24" id="icon--circle-dot-filled">
+ <path d="M17 3.34a10 10 0 1 1 -14.995 8.984l-.005 -.324l.005 -.324a10 10 0 0 1 14.995 -8.336zm-5 6.66a2 2 0 0 0 -1.977 1.697l-.018 .154l-.005 .149l.005 .15a2 2 0 1 0 1.995 -2.15z"/>
+ </symbol>
+ <symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--circle-minus">
+ <path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0"/>
+ <path d="M9 12l6 0"/>
+ </symbol>
<symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--clear-query">
<path d="M3 5a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-14z"/>
<path d="M9 9l6 6m0 -6l-6 6"/>
@@ -275,6 +285,11 @@
<path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0"/>
<path d="M8 11v-4a4 4 0 1 1 8 0v4"/>
</symbol>
+ <symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--message">
+ <path d="M8 9h8"/>
+ <path d="M8 13h6"/>
+ <path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12z"/>
+ </symbol>
<symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--move">
<path d="M15 14l4 -4l-4 -4"/>
<path d="M19 10h-11a4 4 0 1 0 0 8h1"/>
@@ -295,6 +310,17 @@
<symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--plugins">
<path d="M4 7h3a1 1 0 0 0 1 -1v-1a2 2 0 0 1 4 0v1a1 1 0 0 0 1 1h3a1 1 0 0 1 1 1v3a1 1 0 0 0 1 1h1a2 2 0 0 1 0 4h-1a1 1 0 0 0 -1 1v3a1 1 0 0 1 -1 1h-3a1 1 0 0 1 -1 -1v-1a2 2 0 0 0 -4 0v1a1 1 0 0 1 -1 1h-3a1 1 0 0 1 -1 -1v-3a1 1 0 0 1 1 -1h1a2 2 0 0 0 0 -4h-1a1 1 0 0 1 -1 -1v-3a1 1 0 0 1 1 -1"/>
</symbol>
+ <symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--project">
+ <path d="M7 16.5l-5 -3l5 -3l5 3v5.5l-5 3z"/>
+ <path d="M2 13.5v5.5l5 3"/>
+ <path d="M7 16.545l5 -3.03"/>
+ <path d="M17 16.5l-5 -3l5 -3l5 3v5.5l-5 3z"/>
+ <path d="M12 19l5 3"/>
+ <path d="M17 16.5l5 -3"/>
+ <path d="M12 13.5v-5.5l-5 -3l5 -3l5 3v5.5"/>
+ <path d="M7 5.03v5.455"/>
+ <path d="M12 8l5 -3"/>
+ </symbol>
<symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--projects">
<path d="M7 16.5l-5 -3l5 -3l5 3v5.5l-5 3z"/>
<path d="M2 13.5v5.5l5 3"/>
@@ -316,6 +342,10 @@
<path d="M9 18l3 3l3 -3"/>
<path d="M9 6l3 -3l3 3"/>
</symbol>
+ <symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--reply">
+ <path d="M21 14l-3 -3h-7a1 1 0 0 1 -1 -1v-6a1 1 0 0 1 1 -1h9a1 1 0 0 1 1 1v10"/>
+ <path d="M14 15v2a1 1 0 0 1 -1 1h-7l-3 3v-10a1 1 0 0 1 1 -1h2"/>
+ </symbol>
<symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--roles">
<path d="M12 21a12 12 0 0 1 -8.5 -15a12 12 0 0 0 8.5 -3a12 12 0 0 0 8.5 3c.568 1.933 .635 3.957 .223 5.89"/>
<path d="M19.001 19m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"/>
@@ -435,6 +465,15 @@
<path d="M19 16v6"/>
<path d="M12 7v5l3 3"/>
</symbol>
+ <symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--toggle-minus">
+ <path d="M9 12h6"/>
+ <path d="M12 3c7.2 0 9 1.8 9 9s-1.8 9 -9 9s-9 -1.8 -9 -9s1.8 -9 9 -9z"/>
+ </symbol>
+ <symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--toggle-plus">
+ <path d="M12 3c7.2 0 9 1.8 9 9s-1.8 9 -9 9s-9 -1.8 -9 -9s1.8 -9 9 -9z"/>
+ <path d="M15 12h-6"/>
+ <path d="M12 9v6"/>
+ </symbol>
<symbol viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" id="icon--unlock">
<path d="M5 11m0 2a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2z"/>
<path d="M12 16m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"/>
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 5b2578b7a..57a95798a 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -43,10 +43,18 @@ function toggleRowGroup(el) {
}
function toggleExpendCollapseIcon(el) {
+ const svg = el.getElementsByTagName('svg').item(0)
+
+ if (svg === null) {
+ return false;
+ }
+
if (el.classList.contains('icon-expanded')) {
- updateSVGIcon(el, 'angle-down')
+ updateSVGIcon(svg, 'angle-down')
+ svg.classList.remove('icon-rtl')
} else {
- updateSVGIcon(el, 'angle-right')
+ updateSVGIcon(svg, 'angle-right')
+ svg.classList.add('icon-rtl')
}
}
@@ -66,7 +74,9 @@ function collapseAllRowGroups(el) {
tbody.children('tr').each(function(index) {
if ($(this).hasClass('group')) {
$(this).removeClass('open');
- $(this).find('.expander').switchClass('icon-expanded', 'icon-collapsed');
+ var expander = $(this).find('.expander');
+ expander.switchClass('icon-expanded', 'icon-collapsed');
+ toggleExpendCollapseIcon(expander[0]);
} else {
$(this).hide();
}
@@ -78,7 +88,9 @@ function expandAllRowGroups(el) {
tbody.children('tr').each(function(index) {
if ($(this).hasClass('group')) {
$(this).addClass('open');
- $(this).find('.expander').switchClass('icon-collapsed', 'icon-expanded');
+ var expander = $(this).find('.expander');
+ expander.switchClass('icon-collapsed', 'icon-expanded');
+ toggleExpendCollapseIcon(expander[0]);
} else {
$(this).show();
}
@@ -209,9 +221,20 @@ function buildFilterRow(field, operator, values) {
case "list_optional_with_history":
case "list_status":
case "list_subprojects":
+ const iconType = values.length > 1 ? 'toggle-minus' : 'toggle-plus';
+ const clonedIcon = document.querySelector('#icon-copy-source svg').cloneNode(true);
+ updateSVGIcon(clonedIcon, iconType);
+
tr.find('.values').append(
- '<span style="display:none;"><select class="value" id="values_'+fieldId+'_1" name="v['+field+'][]"></select>' +
- ' <span class="toggle-multiselect icon-only '+(values.length > 1 ? 'icon-toggle-minus' : 'icon-toggle-plus')+'">&nbsp;</span></span>'
+ $('<span>', { style: 'display:none;' }).append(
+ $('<select>', {
+ class: 'value',
+ id: `values_${fieldId}_1`,
+ name: `v[${field}][]`,
+ }),
+ '\n',
+ $('<span>', { class: `toggle-multiselect icon-only icon-${iconType}` }).append(clonedIcon)
+ )
);
select = tr.find('.values select');
if (values.length > 1) { select.attr('multiple', true); }
@@ -573,19 +596,23 @@ function expandScmEntry(id) {
function scmEntryClick(id, url) {
var el = $('#'+id);
+ var expander = el.find('.expander');
+ var folder = el.find('.icon-folder');
if (el.hasClass('open')) {
collapseScmEntry(id);
el.find('.expander').switchClass('icon-expanded', 'icon-collapsed');
el.addClass('collapsed');
- updateSVGIcon(el.find('.icon-folder')[0], 'folder')
+ updateSVGIcon(folder[0], 'folder')
+ toggleExpendCollapseIcon(expander[0]);
return false;
} else if (el.hasClass('loaded')) {
expandScmEntry(id);
el.find('.expander').switchClass('icon-collapsed', 'icon-expanded');
el.removeClass('collapsed');
- updateSVGIcon(el.find('.icon-folder-open')[0], 'folder-open')
+ updateSVGIcon(folder[0], 'folder-open')
+ toggleExpendCollapseIcon(expander[0]);
return false;
}
@@ -598,8 +625,9 @@ function scmEntryClick(id, url) {
success: function(data) {
el.after(data);
el.addClass('open').addClass('loaded').removeClass('loading');
- updateSVGIcon(el.find('.icon-folder')[0], 'folder-open')
el.find('.expander').switchClass('icon-collapsed', 'icon-expanded');
+ updateSVGIcon(folder[0], 'folder-open')
+ toggleExpendCollapseIcon(expander[0]);
}
});
return true;
@@ -762,7 +790,7 @@ $(document).ready(function(){
drdn.addClass("expanded");
if ($(this).parent('#project-jump').length) {
selected = $('.drdn-items a.selected'); // Store selected project
- selected.focus(); // Calling focus to scroll to selected project
+ selected.first().focus(); // Calling focus to scroll to selected project
}
if (!isMobile()) {
drdn.find(".autocomplete").focus();
@@ -1010,12 +1038,16 @@ function toggleDisabledInit() {
$('input[data-disables], input[data-enables], input[data-shows]').each(toggleDisabledOnChange);
}
function toggleMultiSelectIconInit() {
- $('.toggle-multiselect:not(.icon-toggle-minus), .toggle-multiselect:not(.icon-toggle-plus)').each(function(){
- if ($(this).siblings('select').find('option:selected').length > 1){
- $(this).addClass('icon-toggle-minus');
+ $('.toggle-multiselect:not(.icon-toggle-minus):not(.icon-toggle-plus)').each(function(){
+ let iconType;
+ if ($(this).siblings('select').find('option:selected').length > 1) {
+ iconType = 'toggle-minus';
} else {
- $(this).addClass('icon-toggle-plus');
+ iconType = 'toggle-plus';
}
+
+ $(this).addClass(`icon-${iconType}`);
+ updateSVGIcon($(this).find('svg')[0], iconType);
});
}
@@ -1061,6 +1093,7 @@ $(document).ready(function(){
$('#content').on('click', '.toggle-multiselect', function() {
toggleMultiSelect($(this).siblings('select'));
$(this).toggleClass('icon-toggle-plus icon-toggle-minus');
+ updateSVGIcon($(this).find('svg')[0], $(this).hasClass('icon-toggle-plus') ? 'toggle-plus' : 'toggle-minus');
});
toggleMultiSelectIconInit();
diff --git a/app/assets/javascripts/attachments.js b/app/assets/javascripts/attachments.js
index e8e9ac6e8..6ff7bc3c6 100644
--- a/app/assets/javascripts/attachments.js
+++ b/app/assets/javascripts/attachments.js
@@ -5,9 +5,13 @@
*/
function addFile(inputEl, file, eagerUpload) {
- var attachmentsFields = $(inputEl).closest('.attachments_form').find('.attachments_fields');
- var addAttachment = $(inputEl).closest('.attachments_form').find('.add_attachment');
+ var attachmentsForm = $(inputEl).closest('.attachments_form')
+ var attachmentsFields = attachmentsForm.find('.attachments_fields');
+ var attachmentsIcons = attachmentsForm.find('.attachments_icons');
+ var addAttachment = attachmentsForm.find('.add_attachment');
var maxFiles = ($(inputEl).attr('multiple') == 'multiple' ? 10 : 1);
+ var delIcon = attachmentsIcons.find('svg.svg-del').clone();
+ var attachmentIcon = attachmentsIcons.find('svg.svg-attachment').clone();
if (attachmentsFields.children().length < maxFiles) {
var attachmentId = addFile.nextAttachmentId++;
@@ -16,12 +20,14 @@ function addFile(inputEl, file, eagerUpload) {
if (!param) {param = 'attachments'};
fileSpan.append(
+ attachmentIcon,
$('<input>', { type: 'text', 'class': 'icon icon-attachment filename readonly', name: param +'[' + attachmentId + '][filename]', readonly: 'readonly'} ).val(file.name),
$('<input>', { type: 'text', 'class': 'description', name: param + '[' + attachmentId + '][description]', maxlength: 255, placeholder: $(inputEl).data('description-placeholder') } ).toggle(!eagerUpload),
$('<input>', { type: 'hidden', 'class': 'token', name: param + '[' + attachmentId + '][token]'} ),
- $('<a>&nbsp</a>').attr({ href: "#", 'class': 'icon-only icon-del remove-upload' }).click(removeFile).toggle(!eagerUpload)
+ $('<a>', { href: "#", 'class': 'icon-only icon-del remove-upload' }).append(delIcon).click(removeFile).toggle(!eagerUpload)
).appendTo(attachmentsFields);
+
if ($(inputEl).data('description') == 0) {
fileSpan.find('input.description').remove();
}
@@ -63,7 +69,7 @@ function ajaxUpload(file, attachmentId, fileSpan, inputEl) {
.done(function(result) {
addInlineAttachmentMarkup(file);
progressSpan.progressbar( 'value', 100 ).remove();
- fileSpan.find('input.description, a').css('display', 'inline-block');
+ fileSpan.find('input.description, a').css('display', 'inline-flex');
})
.fail(function(result) {
progressSpan.text(result.statusText);
diff --git a/app/assets/javascripts/gantt.js b/app/assets/javascripts/gantt.js
index 6a42e5be9..ceb6d9a51 100644
--- a/app/assets/javascripts/gantt.js
+++ b/app/assets/javascripts/gantt.js
@@ -178,6 +178,7 @@ function drawSelectedColumns(){
$(this).show();
var column_name = $(this).attr('id');
$(this).resizable({
+ zIndex: 30,
alsoResize: '.gantt_' + column_name + '_container, .gantt_' + column_name + '_container > .gantt_hdr',
minWidth: 20,
handles: "e",
@@ -220,6 +221,7 @@ function resizableSubjectColumn(){
alsoResize: '.gantt_subjects_container, .gantt_subjects_container>.gantt_hdr, .project-name, .issue-subject, .version-name',
minWidth: 100,
handles: 'e',
+ zIndex: 30,
create: function( event, ui ) {
$('.ui-resizable-e').css('cursor','ew-resize');
}
diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css
index 822442bf4..9d052cdec 100644
--- a/app/assets/stylesheets/application.css
+++ b/app/assets/stylesheets/application.css
@@ -236,11 +236,14 @@ a.user.user-mention {
#sidebar a.selected {line-height:1.7em; padding:1px 3px 2px 2px; margin-left:-2px; background-color:#9DB9D5; color:#fff; border-radius:2px;}
#sidebar a.selected:hover {text-decoration:none;}
+#sidebar a.selected svg.icon-svg { stroke: #fff !important; }
#sidebar .query.default {font-weight: bold;}
#admin-menu a {line-height:1.7em;}
#admin-menu a.selected:not(:has(svg)) {padding-left: 20px !important; background-position: 2px 40%;}
a#toggle-completed-versions {color:#999;}
+a#toggle-completed-versions span.icon-label {margin-left: 0}
+a#toggle-completed-versions svg, a#toggle-completed-versions:hover svg {stroke:#999}
/***** Dropdown *****/
.drdn {position:relative;}
@@ -314,13 +317,13 @@ div + .drdn-items {border-top:1px solid #ccc;}
width:100%;
height:24px;
display:inline-block;
- padding:3px 18px 3px 6px;
+ padding:1.5px 18px 3px 6px;
border-radius:3px;
border:1px solid #ccc;
margin:0 !important;
vertical-align:middle;
color:#555;
- background:#fff url(/arrow_down.png) no-repeat 97% 50%;
+ background:#fff url(/chevron-down.svg) no-repeat 98% 50%;
}
#project-jump .drdn.expanded .drdn-trigger {background-image:url(/arrow_up.png);}
#project-jump .drdn-content {width:280px;}
@@ -356,10 +359,13 @@ table.list td.buttons a, div.buttons a, table.list td.buttons span.icon-only { m
table.list td.buttons a:last-child, div.buttons a:last-child { margin-right: 0; }
table.list td.buttons img, div.buttons img {vertical-align:middle;}
table.list td.reorder {width:15%; white-space:nowrap; text-align:center; }
-table.list table.progress td {padding-right:0px;}
+table.list table.progress td {padding-right:0; border-top: none;}
table.list caption { text-align: left; padding: 0.5em 0.5em 0.5em 0; }
table.list tr.overdue td.due_date { color: #c22; }
#role-permissions-trackers table.list th {white-space:normal;}
+table.list div.wiki p {
+ margin: 0;
+}
.table-list-cell {display: table-cell; vertical-align: top; padding:2px; }
.table-list div.buttons {width: 15%;}
@@ -378,16 +384,16 @@ table.issues td.block_column {color:#777; font-size:90%; padding:4px 4px 4px 24p
table.issues td.block_column>span {font-weight: bold; display: block; margin-bottom: 4px;}
table.issues td.block_column>pre {white-space:normal;}
-tr.idnt td.subject, tr.idnt td.name {background: url(/arrow_right.png) no-repeat 2px 50%;}
-tr.idnt-1 td.subject, tr.idnt-1 td.name {padding-left: 24px; background-position: 8px 50%;}
-tr.idnt-2 td.subject, tr.idnt-2 td.name {padding-left: 40px; background-position: 24px 50%;}
-tr.idnt-3 td.subject, tr.idnt-3 td.name {padding-left: 56px; background-position: 40px 50%;}
-tr.idnt-4 td.subject, tr.idnt-4 td.name {padding-left: 72px; background-position: 56px 50%;}
-tr.idnt-5 td.subject, tr.idnt-5 td.name {padding-left: 88px; background-position: 72px 50%;}
-tr.idnt-6 td.subject, tr.idnt-6 td.name {padding-left: 104px; background-position: 88px 50%;}
-tr.idnt-7 td.subject, tr.idnt-7 td.name {padding-left: 120px; background-position: 104px 50%;}
-tr.idnt-8 td.subject, tr.idnt-8 td.name {padding-left: 136px; background-position: 120px 50%;}
-tr.idnt-9 td.subject, tr.idnt-9 td.name {padding-left: 152px; background-position: 136px 50%;}
+tr.idnt td.subject, tr.idnt td.name {background: url(/chevron-right-idnt.svg) no-repeat 2px 50%;}
+tr.idnt-1 td.subject, tr.idnt-1 td.name {padding-left: 24px; background-position: 4px 50%;}
+tr.idnt-2 td.subject, tr.idnt-2 td.name {padding-left: 40px; background-position: 20px 50%;}
+tr.idnt-3 td.subject, tr.idnt-3 td.name {padding-left: 56px; background-position: 36px 50%;}
+tr.idnt-4 td.subject, tr.idnt-4 td.name {padding-left: 72px; background-position: 52px 50%;}
+tr.idnt-5 td.subject, tr.idnt-5 td.name {padding-left: 88px; background-position: 68px 50%;}
+tr.idnt-6 td.subject, tr.idnt-6 td.name {padding-left: 104px; background-position: 84px 50%;}
+tr.idnt-7 td.subject, tr.idnt-7 td.name {padding-left: 120px; background-position: 100px 50%;}
+tr.idnt-8 td.subject, tr.idnt-8 td.name {padding-left: 136px; background-position: 116px 50%;}
+tr.idnt-9 td.subject, tr.idnt-9 td.name {padding-left: 152px; background-position: 132px 50%;}
table.issue-report {table-layout:fixed;}
table.issue-report tr.total, table.issue-report-detailed tr.total { font-weight: bold; border-top:2px solid #d0d7de;}
@@ -434,7 +440,7 @@ tr.version.closed, tr.version.closed a { color: #999; }
tr.version:not(.shared) td.name { padding-left: 20px; }
tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; white-space:nowrap; }
-tr.member td.icon-user, #principals_for_new_member .icon-user {background:transparent;}
+#principals_for_new_member .icon-user, #users_for_watcher .icon-user {background:transparent;}
#principals_for_new_member svg, #principals_for_new_member img {margin-right: 4px;}
tr.user td {width:13%;white-space: nowrap;}
@@ -583,7 +589,7 @@ div.square {
}
.contextual {float:right; white-space: nowrap; line-height:1.4em;margin:5px 0px; padding-left: 10px; font-size:0.9em;}
.contextual input, .contextual select {font-size:0.9em;}
-.message .contextual { margin-top: 0; }
+.message .contextual, #comments .contextual { margin-top: 0; }
.splitcontent {overflow: auto; display: flex; flex-wrap: wrap;}
.splitcontentleft {flex: 1; margin-right: 5px;}
@@ -599,9 +605,9 @@ select {
-o-appearance: none;
appearance: none;
background-color: #fff;
- background-image: url(/arrow_down.png);
+ background-image: url(/chevron-down.svg);
background-repeat: no-repeat;
- background-position: calc(100% - 7px) 50%;
+ background-position: calc(100% - 2px) 50%;
padding-right: 20px;
}
input[type="file"] {border: 0; padding-left: 0; padding-right: 0; height: initial; background-color: initial; }
@@ -641,8 +647,8 @@ blockquote { font-style: italic; border-left: 3px solid #e0e0e0; padding-left: 0
blockquote blockquote { margin-left: 0;}
abbr, span.field-description[title] { border-bottom: 1px dotted #aaa; cursor: help; }
textarea.wiki-edit {width:99%; resize:vertical; box-sizing: border-box;}
-body.textarea-monospace textarea.wiki-edit {font-family: Consolas, Menlo, "Liberation Mono", Courier, monospace; font-size: 0.75rem;}
-body.textarea-proportional textarea.wiki-edit {font-family: var(--fonts-main); font-size: 0.75rem;}
+body.textarea-monospace textarea.wiki-edit {font-family: Consolas, Menlo, "Liberation Mono", Courier, monospace;}
+body.textarea-proportional textarea.wiki-edit {font-family: var(--fonts-main);}
li p {margin-top: 0;}
div.issue {
background: #ffffdd;
@@ -652,8 +658,8 @@ div.issue {
border-radius: 3px;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
-p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;}
-p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
+p.breadcrumb { font-size: 0.8125rem; margin: 4px 0 4px 0;}
+p.subtitle { font-size: 0.8125rem; margin: -6px 0 12px 0; font-style: italic; }
p.footnote { font-size: 0.9em; margin-top: 0px; margin-bottom: 0px; }
.wiki-class-ltr {direction:ltr !important;}
.wiki-class-rtl {direction:rtl !important;}
@@ -760,12 +766,13 @@ div.journal span.update-info {color: #666; font-size: 0.9em;}
#history p.nodata {display: none;}
div#activity dl, #search-results { margin-left: 2em; }
-div#activity dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; }
+div#activity dd, #search-results dd { margin-bottom: 1em; padding-left: 22px; font-size: 0.8125rem;}
+div#activity dt svg.icon-svg {margin-right: 4px;}
div#activity dt.me .time { border-bottom: 1px solid #999; }
-div#activity dt .time { color: #777; font-size: 93%; margin-right: 4px; }
-div#activity dd .description, #search-results dd .description { font-style: italic; }
-div#activity span.project:after, #search-results span.project:after { content: " -"; }
-div#activity dd span.description, #search-results dd span.description { display:block; color: #808080; }
+div#activity dt .time { color: #555; font-size: 0.8125rem; margin-right: 4px; }
+div#activity dd .description, #search-results dd .description { font-style: italic; margin: 2px 0;}
+div#activity span.project:after, #search-results span.project:after { content: " - "; white-space: pre;}
+div#activity dd span.description, #search-results dd span.description { display:block; color: #666; }
div#activity dt.grouped {padding-left:5em;}
div#activity dd.grouped {margin-left:9em;}
div#activity h3 {
@@ -777,6 +784,8 @@ div#activity dt {
border-top: 1px solid #eeeeee;
width: 100%; /* Prevents border from disappearing due to inline-flex shrinking */
box-sizing: border-box;
+ display: flex;
+ align-items: flex-end;
}
div#activity dl dt:first-child {
border: 0px;
@@ -890,6 +899,9 @@ ul.projects div.description ul li {list-style-type:initial;}
#projects-index a.project ~ svg, table.projects tr.project td.name svg {
margin-left: 4px;
}
+#projects-index div.wiki p {
+ margin-top: 0px;
+}
#notified-projects>ul, #tracker_project_ids>ul, #custom_field_project_ids>ul {max-height:250px; overflow-y:auto;}
@@ -1051,17 +1063,14 @@ input#months { width: 46px; }
.jstBlock .jstTabs { padding-right: 6px; }
.jstBlock .wiki-preview { padding: 2px; }
-.jstBlock .wiki-preview p:first-child { padding-top: 0 !important; margin-top: 0 !important;}
-.jstBlock .wiki-preview p:last-child { padding-bottom: 0 !important; margin-bottom: 0 !important;}
+.jstBlock .wiki-preview > p:first-child { padding-top: 0 !important; margin-top: 0 !important;}
+.jstBlock .wiki-preview > p:last-child { padding-bottom: 0 !important; margin-bottom: 0 !important;}
.tabular .wiki-preview, .tabular .jstTabs {width: 95%;}
.tabular.settings .wiki-preview, .tabular.settings .jstTabs { width: 99%; }
.tabular.settings .wiki-preview p {padding-left: 0 !important}
.tabular .wiki-preview p {
min-height: initial;
- padding: 0;
- padding-top: 1em !important;
- padding-bottom: 1em !important;
overflow: initial;
}
@@ -1102,13 +1111,29 @@ span.required {color: #bb0000;}
.attachments_fields input.description, #existing-attachments input.description {margin-left:4px; width:340px;}
.attachments_fields>span, #existing-attachments>span {display:block; white-space:nowrap;}
/* ToDo: delete this line when legacy icons are deleted */
-.attachments_fields , #existing-attachments .icon-attachment {background-image: none; padding-left: 0}
+.attachments_fields .icon-attachment, #existing-attachments .icon-attachment {background-image: none; padding-left: 0}
.attachments_fields input.filename, #existing-attachments .filename {border:0; width:250px; color:#555; background-color:inherit; }
.tabular input.filename {max-width:75% !important;}
-.attachments_fields input.filename {height:1.8em;}
-.attachments_fields .ajax-waiting input.filename {background:url(/hourglass.png) no-repeat 0px 50%;}
-.attachments_fields .ajax-loading input.filename {background:url(/loading.gif) no-repeat 0px 50%;}
.attachments_fields div.ui-progressbar { width: 100px; height:14px; margin: 2px 0 -5px 8px; display: inline-block; }
+.attachments_fields input.filename {
+ height:1.8em;
+ padding-left: 3px;
+ padding-right: 0;
+}
+.attachments_fields .ajax-waiting {
+ padding-left: 16px;
+ background:url(/hourglass-empty.svg) no-repeat 0px 50%;
+}
+.attachments_fields .ajax-waiting .svg-attachment {
+ display: none;
+}
+.attachments_fields .ajax-loading {
+ padding-left: 16px;
+ background: url(/loading.gif) no-repeat 0px 50%;
+}
+.attachments_fields .ajax-loading .svg-attachment {
+ display: none;
+}
a.remove-upload:hover {text-decoration:none !important;}
.existing-attachment.deleted .filename {text-decoration:line-through; color:#999 !important;}
@@ -1127,7 +1152,7 @@ div.thumbnails img {margin: 3px; vertical-align: middle;}
p.other-formats { text-align: right; font-size:0.9em; color: #666; }
.other-formats span + span:before { content: "| "; }
-a.atom { background: url(/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
+a.atom { background: url(/file-rss.svg) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
em.info {font-style:normal;display:block;font-size:90%;color:#888;}
em.info.error {padding-left:20px; background:url(/exclamation.png) no-repeat 0 50%;}
@@ -1186,28 +1211,59 @@ input.autocomplete.ajax-loading {
}
div.flash {margin-top: 8px;}
+div.flash svg.icon-svg, #errorExplanation svg.icon-svg, .conflict svg.icon-svg {
+ margin-right: 4px;
+ margin-left: -26px;
+}
div.flash.error, #errorExplanation {
- background: url(/exclamation.png) 8px 50% no-repeat;
background-color: #ffe3e3;
border-color: #d88;
color: #880000;
}
+div.flash.error:not(:has(svg)), #errorExplanation:not(:has(svg)) {
+ background: url(/exclamation.png) 8px 50% no-repeat #ffe3e3;
+}
+div.flash.error svg.icon-svg, #errorExplanation svg.icon-svg {
+ stroke: #880000;
+}
+
+#errorExplanation:has(svg) {
+ position: relative;
+}
+
+#errorExplanation:has(svg) > svg.icon-svg {
+ position: absolute;
+ top: 50%;
+ bottom: 50%;
+ margin-left: -24px;
+ margin-top: -9px;
+}
div.flash.notice {
- background: url(/true.png) 8px 5px no-repeat;
background-color: #dfffdf;
border-color: #9fcf9f;
color: #005f00;
}
+div.flash.notice:not(:has(svg)) {
+ background: url(/true.png) 8px 50% no-repeat #dfffdf;
+}
+div.flash.notice svg.icon-svg {
+ stroke: #005f00;
+}
div.flash.warning, .conflict {
- background: url(/warning.png) 8px 5px no-repeat;
background-color: #F3EDD1;
border-color: #eadbbc;
color: #A6750C;
text-align: left;
}
+div.flash.warning:not(:has(svg)), .conflict:not(:has(svg)) {
+ background: url(/warning.png) 8px 5px no-repeat #F3EDD1;
+}
+div.flash.warning svg.icon-svg, .conflict svg.icon-svg {
+ stroke: #A6750C;
+}
.nodata, .warning {
text-align: center;
@@ -1425,18 +1481,26 @@ button.tab-left:hover, button.tab-right:hover {
button.tab-left:focus, button.tab-right:focus {
outline: 0;
}
+button.tab-left svg.icon-svg, button.tab-right svg.icon-svg {
+ stroke: #999;
+ stroke-width: 2;
+}
button.tab-left {
right: 28px;
- background: #eeeeee url(/arrow_left.png) no-repeat 50% 50%;
border-top-left-radius:3px;
}
+button.tab-left:not(:has(svg)) {
+ background: #eeeeee url(/arrow_left.png) no-repeat 50% 50%;
+}
button.tab-right {
right: 4px;
- background: #eeeeee url(/arrow_right.png) no-repeat 50% 50%;
border-top-right-radius:3px;
}
+button.tab-right:not(:has(svg)) {
+ background: #eeeeee url(/arrow_right.png) no-repeat 50% 50%;
+}
button.tab-left.disabled, button.tab-right.disabled {
background-color: #ccc;
@@ -1487,7 +1551,12 @@ div.wiki .external {
div.wiki a {word-wrap: break-word;}
div.wiki a.new {color: #b73535;}
-div.wiki p {line-height: 1.6;}
+div.wiki p {
+ line-height: 1.6;
+ margin-top: 1em;
+ margin-bottom: 1em;
+ padding: 0;
+}
div.wiki ul, div.wiki ol {margin-bottom:1em;}
div.wiki li {line-height: 1.6; margin-bottom: 0.125rem;}
div.wiki li>ul, div.wiki li>ol {margin-bottom: 0;}
@@ -1750,7 +1819,6 @@ td.gantt_selected_column .gantt_hdr,.gantt_selected_column_container {
.icon svg, .icon-only svg {
flex-shrink: 0;
}
-}
a.icon:hover svg, a.icon-only:hover svg {
stroke: #c61a1a;
@@ -1794,6 +1862,11 @@ svg.s18 {
height: 1.125rem;
}
+svg.s16 {
+ width: 1rem;
+ height: 1rem;
+}
+
svg.s14 {
width: 0.875rem;
height: 0.875rem;
@@ -1845,7 +1918,7 @@ span.icon-label {
.open .icon-folder:not(:has(svg)) { background-image: url(/folder_open.png); }
.icon-package:not(:has(svg)) { background-image: url(/package.png); }
.icon-user:not(:has(svg)) { background-image: url(/user.png); }
-.icon-project, .icon-projects:not(:has(svg)) { background-image: url(/projects.png); }
+.icon-project:not(:has(svg)), .icon-projects:not(:has(svg)) { background-image: url(/projects.png); }
.icon-help:not(:has(svg)) { background-image: url(/help.png); }
.icon-attachment:not(:has(svg)) { background-image: url(/attachment.png); }
.icon-history:not(:has(svg)) { background-image: url(/history.png); }
@@ -1905,8 +1978,8 @@ span.icon-label {
.icon-bookmarked-project:not(:has(svg)) { background-image: url(/tag_blue.png); }
.icon-sorted-asc:not(:has(svg)) { background-image: url(/arrow_down.png); }
.icon-sorted-desc:not(:has(svg)) { background-image: url(/arrow_up.png); }
-.icon-toggle-plus { background-image: url(/bullet_toggle_plus.png) }
-.icon-toggle-minus { background-image: url(/bullet_toggle_minus.png) }
+.icon-toggle-plus:not(:has(svg)) { background-image: url(/bullet_toggle_plus.png) }
+.icon-toggle-minus:not(:has(svg)) { background-image: url(/bullet_toggle_minus.png) }
.icon-clear-query:not(:has(svg)) { background-image: url(/close_hl.png); }
.icon-import:not(:has(svg)) { background-image: url(/database_go.png); }
@@ -1960,7 +2033,7 @@ div.gravatar-with-child > img.gravatar:nth-child(2) {
h2 img.gravatar, h3 img.gravatar {margin-right: 4px;}
h4 img.gravatar {margin: -2px 4px -4px 0;}
td.username img.gravatar {margin: 0 0.5em 0 0; vertical-align: top;}
-#activity dt img.gravatar {float: left; margin: 0 1em 1em 0;}
+#activity dt img.gravatar {margin: 0 1em 0 0;}
/* Used on 12px Gravatar img tags without the icon background */
.icon-gravatar {float: left; margin-right: 4px;}
diff --git a/app/assets/stylesheets/context_menu.css b/app/assets/stylesheets/context_menu.css
index 875564c8f..637b89a32 100644
--- a/app/assets/stylesheets/context_menu.css
+++ b/app/assets/stylesheets/context_menu.css
@@ -45,7 +45,8 @@
}
#context-menu li>a { flex-grow: 1; }
#context-menu a.disabled, #context-menu a.disabled:hover {color: #aaa;}
-#context-menu li a.submenu { padding-right:16px; background:url("/arrow_right.png") right no-repeat; padding-left: 28px;}
+#context-menu li a.submenu:not(:has(+ span)) { padding-right:16px; background:url("/arrow_right.png") right no-repeat;}
+#context-menu li a.submenu { padding-left: 28px;}
#context-menu li:hover { border:1px solid #628db6; background-color:#eef5fd; border-radius:3px; }
#context-menu a:hover {color:#2A5685;}
#context-menu li.folder ul li a:not(.icon) {
@@ -59,6 +60,7 @@
.context-menu-selection { background-color:#507AAA !important; color:#f8f8f8 !important; }
.context-menu-selection a, .context-menu-selection a:hover { color:#f8f8f8 !important; }
.context-menu-selection:hover { background-color:#507AAA !important; color:#f8f8f8 !important; }
+.context-menu-selection svg.icon-svg { stroke: #fff !important; }
div#gantt_area .context-menu-selection { background-color: rgba(80, 122, 170, 0.48) !important; }
div#gantt_area .context-menu-selection:hover { background-color: rgba(80, 122, 170, 0.48) !important; }
div#gantt_area .context-menu-selection a { color: #169 !important; }
diff --git a/app/assets/stylesheets/context_menu_rtl.css b/app/assets/stylesheets/context_menu_rtl.css
index cab05ad64..b373ad146 100644
--- a/app/assets/stylesheets/context_menu_rtl.css
+++ b/app/assets/stylesheets/context_menu_rtl.css
@@ -6,10 +6,9 @@
#context-menu li.folder ul { left:auto; right:168px; }
#context-menu li.folder>ul { left:auto; right:148px; }
-#context-menu li a.submenu { background:url("/arrow_left.png") left no-repeat; }
-
-#context-menu a {
- background-position: 100% 40%;
- padding-right: 20px;
- padding-left: 0px;
+#context-menu li a.submenu:not(:has(+ span)) { background:url("/arrow_left.png") left no-repeat; }
+#context-menu li.folder ul li a:not(.icon) {padding-right: 28px;}
+#context-menu li a.submenu {
+ padding-right: 28px;
+ padding-left: 0;
}
diff --git a/app/assets/stylesheets/responsive.css b/app/assets/stylesheets/responsive.css
index 4eff84187..e3dde5ba8 100644
--- a/app/assets/stylesheets/responsive.css
+++ b/app/assets/stylesheets/responsive.css
@@ -385,7 +385,7 @@
list-style: none;
}
- .flyout-menu #watchers {
+ .flyout-menu #watchers, .flyout-menu .queries {
display: -webkit-flex;
display: -webkit-box;
display: flex;
@@ -402,11 +402,11 @@
order: 3;
}
- .flyout-menu #watchers h3 {
+ #sidebar-wrapper {
margin-left: -8px;
}
- .flyout-menu #watchers ul li {
+ .flyout-menu #watchers ul li, .flyout-menu ul.queries li {
display: -webkit-flex;
display: -webkit-box;
display: flex;
@@ -418,6 +418,16 @@
-webkit-align-items: center;
-webkit-box-align: center;
align-items: center;
+ border-top: 1px solid rgba(255,255,255,.1);
+ }
+
+ .flyout-menu #watchers ul li a, .flyout-menu ul.queries li a {
+ border-top: none;
+ }
+
+ .flyout-menu ul.queries li a.icon-clear-query {
+ flex-shrink: 0;
+ padding-right: 8px;
}
.flyout-menu ul li a {
@@ -440,6 +450,10 @@
color: white;
}
+ .flyout-menu .icon svg, .flyout-menu .icon-only svg {
+ stroke: white;
+ }
+
.flyout-menu ul li a:hover {
text-decoration: none;
}
@@ -560,8 +574,10 @@
#admin-menu a.selected {
line-height: 40px;
padding: 0;
- padding-left: 32px !important;
- background-position: 8px 50%;
+ }
+
+ #admin-menu a.icon:not(:has(svg)) {
+ padding-left: 20px !important;
}
/*----------------------------------------*\
@@ -765,6 +781,10 @@
width: 100%; /* let subject have one full width column */
}
+ #issue_tree .issue:has(.buttons a) > td.subject, #relations .issue:has(.buttons a) > td.subject {
+ padding-right: 40px;
+ }
+
#issue_tree .issue > td:not(.subject), #relations .issue > td:not(.subject) {
width: 20%; /* three columns for all cells that are not subject */
}
diff --git a/app/assets/stylesheets/rtl.css b/app/assets/stylesheets/rtl.css
index 65988f509..20a2a73dc 100644
--- a/app/assets/stylesheets/rtl.css
+++ b/app/assets/stylesheets/rtl.css
@@ -21,12 +21,29 @@ h1, h2, h3, h4 {padding:2px 00px 1px 10px;}
#main-menu {left:auto;right:6px;margin-right:0;margin-left:-500px;}
#main-menu li {float:right;margin:0px 0px 0px 2px;}
-#admin-menu a {padding-left:0;padding-right:20px;}
+#admin-menu a:not(:has(svg)) {padding-left:0;padding-right:20px;}
+
+#sidebar {float:left; padding-right: 20px; padding-left: 8px; border-left: 0; border-right: 1px solid #d0d7de;}
+* html #sidebar hr {left: auto; right: -6px;}
+
+#main.collapsedsidebar #sidebar {
+ padding-left: 0;
+ padding-right: 20px;
+}
-#sidebar {float:left;}
-* html #sidebar hr{ left: auto; right: -6px; }
#sidebar .contextual { margin-right: 0; margin-left: 1em;}
-#sidebar ul li {margin: 0px 0px 0px 2px;}
+#sidebar ul li {margin: 0 0 0 2px;}
+#sidebar #sidebar-switch-panel {
+ margin-left: 0;
+ margin-right: -20px;
+ padding-left: 28px;
+ padding-right: 0;
+}
+
+#sidebar #sidebar-switch-panel #sidebar-switch-button {
+ padding-right: 0;
+ padding-left: 28px;
+}
#content {border-right:0 solid #ddd; border-left:1px solid #ddd;}
* html #content{padding-right:0;}
@@ -38,10 +55,10 @@ div.modal p.buttons {text-align:left;}
/***** Links *****/
#sidebar a.selected {padding:1px 2px 2px 3px; margin-left:0px; margin-right:-2px;}
-#admin-menu a.selected {padding-left:0!important; padding-right:20px!important; background-position:right 2px 40%;}
+#admin-menu a.selected:not(:has(svg)) {padding-left:0!important; padding-right:20px!important; background-position:right 2px 40%;}
-a.collapsible {padding-left:0px; padding-right:12px; background: url(/arrow_down.png) no-repeat right 0px top 50%;}
-a.collapsible.collapsed {background-image: url(/arrow_left.png);}
+a.collapsible:not(:has(svg)) {padding-left:0px; padding-right:12px; background: url(/arrow_down.png) no-repeat right 0px top 50%;}
+a.collapsible.collapsed:not(:has(svg)) {background-image: url(/arrow_left.png);}
/***** Tables *****/
table.list td {padding-left:0px; padding-right:10px;}
@@ -122,7 +139,7 @@ div.projects h3 {padding-left:0px; padding-right:20px;}
#watchers li {margin: 0px 0px 0px 2px; padding: 0px 0px 0px 0px;}
#watchers img.gravatar {margin: 0 0 2px 4px;}
-span.search_for_watchers a, span.add_attachment a {padding-left:px; padding-right:16px; background: url(/bullet_add.png) no-repeat right 50%; }
+span.search_for_watchers a:not(:has(svg)), span.add_attachment a:not(:has(svg)) {padding-left:0px; padding-right:16px; background: url(/bullet_add.png) no-repeat right 50%; }
div.square {float:right;}
.contextual {float:left; padding-left:0px; padding-right:10px;}
@@ -137,13 +154,11 @@ div.issue div.subject div div {padding-left:0px; padding-right:16px;}
div.issue span.private, div.journal span.private {margin-right:0px; margin-left:2px;}
-fieldset.collapsible>legend {padding-left:0px; padding-right:18px; background: url(/arrow_down.png) no-repeat right 50%;}
-fieldset.collapsible.collapsed>legend { background-image: url(/arrow_left.png); }
+fieldset.collapsible>legend:not(:has(svg)) {padding-left:0px; padding-right:18px; background: url(/arrow_down.png) no-repeat right 50%;}
+fieldset.collapsible.collapsed>legend:not(:has(svg)) { background-image: url(/arrow_left.png); }
fieldset#filters td.add-filter {text-align:left; }
-.toggle-multiselect {background: url(/bullet_toggle_plus.png) no-repeat right 40%; padding-left:0px; padding-right:8px; margin-right:0;}
-
div#issue-changesets {float:left; margin-left:0em; margin-right:1em; padding-left:0em; padding-right:1em;}
.journal ul.details img {margin:0 4px -3px 0;}
@@ -215,7 +230,7 @@ fieldset#notified_events .parent {padding-left:0px; padding-right:20px; }
.attachments_fields input.description {margin-left:0px; margin-right:4px;}
.attachments_fields input.filename {background:url(/attachment.png) no-repeat right 1px top 50%; padding-left:0px; padding-right:18px;}
-.attachments_fields .ajax-waiting input.filename {background:url(/hourglass.png) no-repeat right top 50%;}
+.attachments_fields .ajax-waiting input.filename {background:url(/hourglass-empty.svg) no-repeat right top 50%;}
.attachments_fields .ajax-loading input.filename {background:url(/loading.gif) no-repeat right top 50%;}
.attachments_fields div.ui-progressbar {margin: 2px 8px -5px 0;}
@@ -223,7 +238,7 @@ a.remove-upload {background: url(/delete.png) no-repeat right 1px top 50%; paddi
div.thumbnails div {margin-right:0px; margin-left:2px;}
-p.other-formats { text-align:left; }
+p.other-formats, p.query-totals { text-align:left; }
a.atom { background: url(/feed.png) no-repeat right 1px top 50%; padding: 2px 16px 3px 0; }
@@ -233,7 +248,7 @@ table.members td.name {padding-right: 20px; padding-left:0; }
table.members td.group, table.members td.groupnonmember, table.members td.groupanonymous {background: url(/group.png) no-repeat right 50%;}
input.autocomplete {
- background: #fff url(/magnifier.png) no-repeat right 2px top 50%; padding-left:0px !important; padding-right:20px !important;
+ background: #fff url(/search.svg) no-repeat right 2px top 50%; padding-left:0px !important; padding-right:20px !important;
}
.role-visibility {padding-right:2em; padding-left:0;}
@@ -243,16 +258,21 @@ input.autocomplete {
padding: 4px 30px 4px 4px;
}
-div.flash.error, #errorExplanation {
- background: url(/exclamation.png) right 8px top 50% no-repeat;
+div.flash svg.icon-svg, #errorExplanation svg.icon-svg {
+ margin-right: -26px;
+ margin-left: 4px;
}
-div.flash.notice {
- background: url(/true.png) right 8px top 5px no-repeat;
+div.flash.error:not(:has(svg)), #errorExplanation:not(:has(svg)) {
+ background: url(/exclamation.png) right 8px top 50% no-repeat #ffe3e3;
}
-div.flash.warning, .conflict {
- background: url(/warning.png) right 8px top 5px no-repeat;
+div.flash.notice:not(:has(svg)) {
+ background: url(/true.png) right 8px top 5px no-repeat #dfffdf;
+}
+
+div.flash.warning:not(:has(svg)), .conflict {
+ background: url(/warning.png) right 8px top 5px no-repeat #F3EDD1;
text-align:right;
}
@@ -347,11 +367,15 @@ a.wiki-anchor {margin-left:0px; margin-right:6px;}
.project.marker {margin-left:0; margin-right:-4px;}
/***** Icons *****/
-.icon {
+.icon:not(:has(svg)) {
background-position: right 50%;
padding-left:0; padding-right:20px;
}
+svg.icon-svg.icon-rtl {
+ transform: scaleX(-1);
+}
+
div.issue img.gravatar {
float: right;
margin: 0 0 0 6px;
@@ -362,6 +386,10 @@ div.issue table img.gravatar {
margin: 0 0em 0 0.5em;
}
+span.icon-label {
+ margin-right: 4px;
+}
+
h2 img.gravatar {margin: -2px 0 -4px 4px;}
h3 img.gravatar {margin: -4px 0 -4px 4px;}
h4 img.gravatar {margin: -6px 0 -4px 4px;}
diff --git a/app/assets/stylesheets/scm.css b/app/assets/stylesheets/scm.css
index e3eca24df..0df148f8e 100644
--- a/app/assets/stylesheets/scm.css
+++ b/app/assets/stylesheets/scm.css
@@ -19,24 +19,48 @@ div.revision-graph { position: absolute; min-width: 1px; }
div.changeset-changes ul { margin: 0; padding: 0; }
div.changeset-changes ul > ul { margin-left: 18px; padding: 0; }
+div.changeset-changes ul:first-child > li {padding-left: 0}
li.change {
list-style-type:none;
- background-image: url(/bullet_black.png);
- background-position: 1px 2px;
- background-repeat: no-repeat;
padding-top: 1px;
padding-bottom: 1px;
padding-left: 20px;
margin: 0;
}
-li.change.folder { background-image: url(/folder_open.png); }
+li.change:not(:has(svg)) {
+ background-image: url(/bullet_black.png);
+ background-position: 1px 2px;
+ background-repeat: no-repeat;
+}
+li.change.folder:not(:has(svg)) { background-image: url(/folder_open.png); }
li.change.folder.change-A { background-image: url(/folder_open_add.png); }
li.change.folder.change-M { background-image: url(/folder_open_orange.png); }
-li.change.change-A { background-image: url(/bullet_add.png); }
-li.change.change-M { background-image: url(/bullet_orange.png); }
-li.change.change-C { background-image: url(/bullet_blue.png); }
-li.change.change-R { background-image: url(/bullet_purple.png); }
-li.change.change-D { background-image: url(/bullet_delete.png); }
+
+li.change.change-A:not(:has(svg)) { background-image: url(/bullet_add.png); }
+li.change.change-A svg.icon-svg {
+ fill: #5db651;
+ stroke: #ffffff;
+}
+li.change.change-M:not(:has(svg)) { background-image: url(/bullet_orange.png); }
+li.change.change-M svg.icon-svg {
+ fill: #f0a810;
+ stroke: #ffffff;
+}
+li.change.change-C:not(:has(svg)) { background-image: url(/bullet_blue.png); }
+li.change.change-C svg.icon-svg {
+ fill: #049cec;
+ stroke: #ffffff;
+}
+li.change.change-R:not(:has(svg)) { background-image: url(/bullet_purple.png); }
+li.change.change-R svg.icon-svg {
+ fill: #8404ee;
+ stroke: #ffffff;
+}
+li.change.change-D:not(:has(svg)) { background-image: url(/bullet_delete.png); }
+li.change.change-D svg.icon-svg {
+ fill: #c61a1a;
+ stroke: #ffffff;
+}
li.change .copied-from { font-style: italic; color: #999; font-size: 0.9em; }
li.change .copied-from:before { content: " - "}
diff --git a/app/assets/stylesheets/wiki_syntax.css b/app/assets/stylesheets/wiki_syntax.css
index d326a3293..41e780c75 100644
--- a/app/assets/stylesheets/wiki_syntax.css
+++ b/app/assets/stylesheets/wiki_syntax.css
@@ -1,19 +1,33 @@
@font-face {
- font-family: "Noto Sans";
- src: url("/NotoSans-VariableFont_wdth,wght.woff2") format("woff2");
- font-weight: 100 900;
- font-stretch: 75% 125%;
- font-style: normal;
- font-display: swap;
+ font-family: "Noto Sans";
+ src: url("/NotoSans-Regular.woff2") format("woff2");
+ font-weight: 400;
+ font-style: normal;
+ font-display: swap;
}
@font-face {
- font-family: "Noto Sans";
- src: url("/NotoSans-Italic-VariableFont_wdth,wght.woff2") format("woff2");
- font-weight: 100 900;
- font-stretch: 75% 125%;
- font-style: italic;
- font-display: swap;
+ font-family: "Noto Sans";
+ src: url("/NotoSans-Bold.woff2") format("woff2");
+ font-weight: 700;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Noto Sans";
+ src: url("/NotoSans-Italic.woff2") format("woff2");
+ font-weight: 400;
+ font-style: italic;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Noto Sans";
+ src: url("/NotoSans-BoldItalic.woff2") format("woff2");
+ font-weight: 700;
+ font-style: italic;
+ font-display: swap;
}
:root {
diff --git a/app/assets/stylesheets/wiki_syntax_detailed.css b/app/assets/stylesheets/wiki_syntax_detailed.css
index 7d7c30f53..e90279641 100644
--- a/app/assets/stylesheets/wiki_syntax_detailed.css
+++ b/app/assets/stylesheets/wiki_syntax_detailed.css
@@ -1,19 +1,33 @@
@font-face {
- font-family: "Noto Sans";
- src: url("/NotoSans-VariableFont_wdth,wght.woff2") format("woff2");
- font-weight: 100 900;
- font-stretch: 75% 125%;
- font-style: normal;
- font-display: swap;
+ font-family: "Noto Sans";
+ src: url("/NotoSans-Regular.woff2") format("woff2");
+ font-weight: 400;
+ font-style: normal;
+ font-display: swap;
}
@font-face {
- font-family: "Noto Sans";
- src: url("/NotoSans-Italic-VariableFont_wdth,wght.woff2") format("woff2");
- font-weight: 100 900;
- font-stretch: 75% 125%;
- font-style: italic;
- font-display: swap;
+ font-family: "Noto Sans";
+ src: url("/NotoSans-Bold.woff2") format("woff2");
+ font-weight: 700;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Noto Sans";
+ src: url("/NotoSans-Italic.woff2") format("woff2");
+ font-weight: 400;
+ font-style: italic;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Noto Sans";
+ src: url("/NotoSans-BoldItalic.woff2") format("woff2");
+ font-weight: 700;
+ font-style: italic;
+ font-display: swap;
}
:root {
diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb
index 892629af1..9b45f9553 100644
--- a/app/controllers/admin_controller.rb
+++ b/app/controllers/admin_controller.rb
@@ -36,9 +36,7 @@ class AdminController < ApplicationController
end
def projects
- retrieve_query(ProjectQuery, false, :defaults => @default_columns_names)
- @query.admin_projects = 1
-
+ retrieve_query(ProjectAdminQuery, false, :defaults => @default_columns_names)
@entry_count = @query.result_count
@entry_pages = Paginator.new @entry_count, per_page_option, params['page']
@projects = @query.results_scope(:limit => @entry_pages.per_page, :offset => @entry_pages.offset).to_a
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 111c85bc5..819516039 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -509,11 +509,9 @@ class ApplicationController < ActionController::Base
if uri.send(component).present? && uri.send(component) != request.send(component)
return false
end
-
- uri.send(:"#{component}=", nil)
end
- # Always ignore basic user:password in the URL
- uri.userinfo = nil
+ # Remove unnecessary components to convert the URL into a relative URL
+ uri.omit!(:scheme, :authority)
rescue Addressable::URI::InvalidURIError
return false
end
diff --git a/app/controllers/auto_completes_controller.rb b/app/controllers/auto_completes_controller.rb
index 2982447e9..77105c8e8 100644
--- a/app/controllers/auto_completes_controller.rb
+++ b/app/controllers/auto_completes_controller.rb
@@ -26,7 +26,7 @@ class AutoCompletesController < ApplicationController
status = params[:status].to_s
issue_id = params[:issue_id].to_s
- scope = Issue.cross_project_scope(@project, params[:scope]).visible
+ scope = Issue.cross_project_scope(@project, params[:scope]).includes(:tracker).visible
scope = scope.open(status == 'o') if status.present?
scope = scope.where.not(:id => issue_id.to_i) if issue_id.present?
if q.present?
diff --git a/app/controllers/queries_controller.rb b/app/controllers/queries_controller.rb
index 53ce029b9..24f37eda2 100644
--- a/app/controllers/queries_controller.rb
+++ b/app/controllers/queries_controller.rb
@@ -19,6 +19,8 @@
class QueriesController < ApplicationController
menu_item :issues
+ layout :query_layout
+
before_action :find_query, :only => [:edit, :update, :destroy]
before_action :find_optional_project, :only => [:new, :create]
@@ -52,7 +54,6 @@ class QueriesController < ApplicationController
@query.user = User.current
@query.project = @project
@query.build_from_params(params)
- render :layout => 'admin' if params[:admin_projects]
end
def create
@@ -63,14 +64,13 @@ class QueriesController < ApplicationController
if @query.save
flash[:notice] = l(:notice_successful_create)
- redirect_to_items(:query_id => @query, :admin_projects => params[:admin_projects])
+ redirect_to_items(:query_id => @query)
else
render :action => 'new', :layout => !request.xhr?
end
end
def edit
- render :layout => 'admin' if params[:admin_projects]
end
def update
@@ -78,7 +78,7 @@ class QueriesController < ApplicationController
if @query.save
flash[:notice] = l(:notice_successful_update)
- redirect_to_items(:query_id => @query, :admin_projects => params[:admin_projects])
+ redirect_to_items(:query_id => @query)
else
render :action => 'edit'
end
@@ -109,18 +109,20 @@ class QueriesController < ApplicationController
end
def current_menu_item
- @query ? @query.queried_class.to_s.underscore.pluralize.to_sym : nil
+ return unless @query
+ return if query_layout == 'admin'
+
+ @query.queried_class.to_s.underscore.pluralize.to_sym
end
def current_menu(project)
- super if params[:admin_projects].nil?
+ super unless query_layout == 'admin'
end
private
def find_query
@query = Query.find(params[:id])
- @query.admin_projects = params[:admin_projects] if @query.is_a?(ProjectQuery)
@project = @query.project
render_403 unless @query.editable_by?(User.current)
rescue ActiveRecord::RecordNotFound
@@ -171,17 +173,25 @@ class QueriesController < ApplicationController
end
def redirect_to_project_query(options)
- if params[:admin_projects]
- redirect_to admin_projects_path(options)
- else
- redirect_to projects_path(options)
- end
+ redirect_to projects_path(options)
+ end
+
+ def redirect_to_project_admin_query(options)
+ redirect_to admin_projects_path(options)
end
def redirect_to_user_query(options)
redirect_to users_path(options)
end
+ def query_layout
+ @query&.layout || 'base'
+ end
+
+ def menu_items
+ {self.controller_name.to_sym => {:actions => {}, :default => current_menu_item}}
+ end
+
# Returns the Query subclass, IssueQuery by default
# for compatibility with previous behaviour
def query_class
diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb
index 9be7878ce..d6a13daf2 100644
--- a/app/controllers/repositories_controller.rb
+++ b/app/controllers/repositories_controller.rb
@@ -160,7 +160,15 @@ class RepositoriesController < ApplicationController
# Force the download
send_opt = {:filename => filename_for_content_disposition(@path.split('/').last)}
send_type = Redmine::MimeType.of(@path)
- send_opt[:type] = send_type.to_s if send_type
+ case send_type
+ when nil
+ # No MIME type detected. Let Rails use the default type.
+ when 'application/javascript'
+ # Avoid ActionController::InvalidCrossOriginRequest exception by setting non-JS content type
+ send_opt[:type] = 'text/plain'
+ else
+ send_opt[:type] = send_type
+ end
send_opt[:disposition] = disposition(@path)
send_data @repository.cat(@path, @rev), send_opt
else
diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb
index 36b90da77..bcb3b0891 100644
--- a/app/controllers/wiki_controller.rb
+++ b/app/controllers/wiki_controller.rb
@@ -240,6 +240,7 @@ class WikiController < ApplicationController
# don't load text
@versions = @page.content.versions.
select("id, author_id, comments, updated_on, version").
+ preload(:author).
reorder('version DESC').
limit(@version_pages.per_page + 1).
offset(@version_pages.offset).
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 2db6ce7ee..be346a109 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -128,7 +128,7 @@ module ApplicationHelper
# * :download - Force download (default: false)
def link_to_attachment(attachment, options={})
text = options.delete(:text) || attachment.filename
- icon = options.fetch(:icon, false)
+ icon = options.delete(:icon)
if options.delete(:download)
route_method = :download_named_attachment_url
@@ -352,6 +352,7 @@ module ApplicationHelper
:srcset => "#{thumbnail_path} 2x",
:style => "max-width: #{thumbnail_size}px; max-height: #{thumbnail_size}px;",
:title => attachment.filename,
+ :alt => attachment.filename,
:loading => "lazy"
),
attachment_path(
@@ -428,7 +429,7 @@ module ApplicationHelper
def format_changeset_comments(changeset, options={})
method = options[:short] ? :short_comments : :comments
- textilizable changeset, method, :formatting => Setting.commit_logs_formatting?
+ textilizable changeset, method, project: changeset.project, formatting: Setting.commit_logs_formatting?
end
def due_date_distance_in_words(date)
@@ -510,7 +511,7 @@ module ApplicationHelper
def render_flash_messages
s = +''
flash.each do |k, v|
- s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
+ s << content_tag('div', notice_icon(k) + v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
end
s.html_safe
end
@@ -781,7 +782,7 @@ module ApplicationHelper
end
def other_formats_links(&)
- concat('<p class="other-formats">'.html_safe + l(:label_export_to))
+ concat('<p class="other-formats hide-when-print">'.html_safe + l(:label_export_to))
yield Redmine::Views::OtherFormatsBuilder.new(self)
concat('</p>'.html_safe)
end
@@ -1378,7 +1379,7 @@ module ApplicationHelper
<|
$)
}x
- HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
+ HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/im unless const_defined?(:HEADING_RE)
def parse_sections(text, project, obj, attr, only_path, options)
return unless options[:edit_section_links]
@@ -1568,7 +1569,9 @@ module ApplicationHelper
def render_error_messages(errors)
html = +""
if errors.present?
- html << "<div id='errorExplanation'><ul>\n"
+ html << "<div id='errorExplanation'>\n"
+ html << notice_icon('error')
+ html << "<ul>\n"
errors.each do |error|
html << "<li>#{h error}</li>\n"
end
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index 99006308e..077bae158 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -21,10 +21,10 @@ module IconsHelper
DEFAULT_ICON_SIZE = "18"
DEFAULT_SPRITE = "icons"
- def sprite_icon(icon_name, label = nil, icon_only: false, size: DEFAULT_ICON_SIZE, css_class: nil, sprite: DEFAULT_SPRITE, plugin: nil)
+ def sprite_icon(icon_name, label = nil, icon_only: false, size: DEFAULT_ICON_SIZE, css_class: nil, sprite: DEFAULT_SPRITE, plugin: nil, rtl: false)
sprite = plugin ? "plugin_assets/#{plugin}/#{sprite}.svg" : "#{sprite}.svg"
- svg_icon = svg_sprite_icon(icon_name, size: size, css_class: css_class, sprite: sprite)
+ svg_icon = svg_sprite_icon(icon_name, size: size, css_class: css_class, sprite: sprite, rtl: rtl)
if label
label_classes = ["icon-label"]
@@ -67,11 +67,35 @@ module IconsHelper
sprite_icon(icon_name, **options)
end
+ def scm_change_icon(action, name, **options)
+ icon_name = case action
+ when 'A'
+ "add"
+ when 'D'
+ "circle-minus"
+ else
+ "circle-dot-filled"
+ end
+ sprite_icon(icon_name, name, size: 14)
+ end
+
+ def notice_icon(type, **options)
+ icon_name = case type
+ when 'notice'
+ 'checked'
+ when 'warning', 'error'
+ 'warning'
+ end
+
+ sprite_icon(icon_name, **options)
+ end
+
private
- def svg_sprite_icon(icon_name, size: DEFAULT_ICON_SIZE, sprite: DEFAULT_SPRITE, css_class: nil)
+ def svg_sprite_icon(icon_name, size: DEFAULT_ICON_SIZE, sprite: DEFAULT_SPRITE, css_class: nil, rtl: false)
css_classes = "s#{size} icon-svg"
css_classes += " #{css_class}" unless css_class.nil?
+ css_classes += " icon-rtl" if rtl
content_tag(
:svg,
diff --git a/app/helpers/queries_helper.rb b/app/helpers/queries_helper.rb
index 708a8acfb..ca7168f27 100644
--- a/app/helpers/queries_helper.rb
+++ b/app/helpers/queries_helper.rb
@@ -192,6 +192,8 @@ module QueriesHelper
value =
if [:hours, :spent_hours, :total_spent_hours, :estimated_hours, :total_estimated_hours, :estimated_remaining_hours].include? column.name
format_hours(value)
+ elsif column.is_a?(QueryCustomFieldColumn)
+ format_object(value, thousands_delimiter: column.custom_field.thousands_delimiter?)
else
format_object(value)
end
@@ -480,8 +482,6 @@ module QueriesHelper
url_params =
if controller_name == 'issues'
{:controller => 'issues', :action => 'index', :project_id => @project}
- elsif controller_name == 'admin' && action_name == 'projects'
- {:admin_projects => '1'}
else
{}
end
@@ -510,7 +510,7 @@ module QueriesHelper
url_params.merge(:query_id => query),
:class => css,
:title => query.description,
- :data => { :disable_with => query.name }) +
+ :data => { :disable_with => CGI.escapeHTML(query.name) }) +
clear_link.html_safe)
end.join("\n").html_safe,
:class => 'queries'
diff --git a/app/helpers/repositories_helper.rb b/app/helpers/repositories_helper.rb
index 7a6978979..c72816367 100644
--- a/app/helpers/repositories_helper.rb
+++ b/app/helpers/repositories_helper.rb
@@ -96,7 +96,7 @@ module RepositoriesHelper
if s = tree[file][:s]
style << ' folder'
path_param = to_path_param(@repository.relative_path(file))
- text = link_to(h(text), :controller => 'repositories',
+ text = link_to(sprite_icon("folder-open", h(text)), :controller => 'repositories',
:action => 'show',
:id => @project,
:repository_id => @repository.identifier_param,
@@ -108,7 +108,7 @@ module RepositoriesHelper
elsif c = tree[file][:c]
style << " change-#{c.action}"
path_param = to_path_param(@repository.relative_path(c.path))
- text = link_to(h(text), :controller => 'repositories',
+ text = link_to(scm_change_icon(c.action, h(text)), :controller => 'repositories',
:action => 'entry',
:id => @project,
:repository_id => @repository.identifier_param,
diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb
index 1fb57b2d7..39a836a03 100644
--- a/app/helpers/settings_helper.rb
+++ b/app/helpers/settings_helper.rb
@@ -48,7 +48,11 @@ module SettingsHelper
errors.each do |name, message|
s << content_tag('li', content_tag('b', l("setting_#{name}")) + " " + message)
end
- content_tag('div', content_tag('ul', s), :id => 'errorExplanation')
+
+ h = ''.html_safe
+ h << notice_icon('error')
+ h << content_tag('ul', s)
+ content_tag('div', h, :id => 'errorExplanation')
end
def setting_value(setting)
diff --git a/app/models/email_address.rb b/app/models/email_address.rb
index 69ae8a066..de8c86531 100644
--- a/app/models/email_address.rb
+++ b/app/models/email_address.rb
@@ -74,7 +74,7 @@ class EmailAddress < ApplicationRecord
# Returns true if domain belongs to domains list.
def self.domain_in?(domain, domains)
- domain = domain.downcase
+ domain = domain.to_s.downcase
domains = domains.to_s.split(/[\s,]+/) unless domains.is_a?(Array)
domains.reject(&:blank?).map(&:downcase).any? do |s|
s.start_with?('.') ? domain.end_with?(s) : domain == s
@@ -150,6 +150,10 @@ class EmailAddress < ApplicationRecord
def validate_email_domain
domain = address.partition('@').last
+ # Skip domain validation if the email does not contain a domain part,
+ # to avoid an incomplete error message like "domain not allowed ()"
+ return if domain.empty?
+
return if self.class.valid_domain?(domain)
if User.current.logged?
diff --git a/app/models/issue_priority.rb b/app/models/issue_priority.rb
index d60098c06..1bad524dc 100644
--- a/app/models/issue_priority.rb
+++ b/app/models/issue_priority.rb
@@ -60,11 +60,15 @@ class IssuePriority < Enumeration
end
def high?
- position > self.class.default_or_middle.position
+ return false unless (baseline_position = self.class.default_or_middle&.position)
+
+ position > baseline_position
end
def low?
- position < self.class.default_or_middle.position
+ return false unless (baseline_position = self.class.default_or_middle&.position)
+
+ position < baseline_position
end
# Updates position_name for active priorities
diff --git a/app/models/member.rb b/app/models/member.rb
index ffad20754..f4fedc0db 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -64,7 +64,7 @@ class Member < ApplicationRecord
def role_ids=(arg)
ids = (arg || []).collect(&:to_i) - [0]
# Keep inherited roles
- ids += member_roles.select {|mr| !mr.inherited_from.nil?}.collect(&:role_id)
+ ids |= member_roles.select {|mr| !mr.inherited_from.nil?}.collect(&:role_id)
new_role_ids = ids - role_ids
# Add new roles
diff --git a/app/models/project_admin_query.rb b/app/models/project_admin_query.rb
new file mode 100644
index 000000000..8321df488
--- /dev/null
+++ b/app/models/project_admin_query.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+# Redmine - project management software
+# Copyright (C) Jean-Philippe Lang
+#
+# 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.
+
+class ProjectAdminQuery < ProjectQuery
+ self.layout = 'admin'
+
+ def self.default(project: nil, user: User.current)
+ nil
+ end
+
+ def self.visible(*args)
+ user = args.shift || User.current
+ if user.admin?
+ where('1=1')
+ else
+ where('1=0')
+ end
+ end
+
+ def visible?(user=User.current)
+ user&.admin?
+ end
+
+ def editable_by?(user)
+ user&.admin?
+ end
+
+ def available_display_types
+ ['list']
+ end
+
+ def display_type
+ 'list'
+ end
+
+ def project_statuses_values
+ values = super
+
+ values << [l(:project_status_archived), Project::STATUS_ARCHIVED.to_s]
+ values << [l(:project_status_scheduled_for_deletion), Project::STATUS_SCHEDULED_FOR_DELETION.to_s]
+ values
+ end
+
+ def base_scope
+ Project.where(statement)
+ end
+end
diff --git a/app/models/project_query.rb b/app/models/project_query.rb
index 726721bd8..4464eb155 100644
--- a/app/models/project_query.rb
+++ b/app/models/project_query.rb
@@ -18,8 +18,6 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class ProjectQuery < Query
- attr_accessor :admin_projects
-
self.queried_class = Project
self.view_permission = :search_project
@@ -28,6 +26,13 @@ class ProjectQuery < Query
errors.add(:project_id, :exclusion) if query.project_id.present?
end
+ # Inheriting ProjectAdminQuery from ProjectQuery introduces the problem that
+ # ProjectQuery.visible also yields ProjectAdminQueries, as
+ # well. We fix that by adding a condition on the actual class name.
+ def self.visible(*)
+ super.where type: name
+ end
+
self.available_columns = [
QueryColumn.new(:name, :sortable => "#{Project.table_name}.name"),
QueryColumn.new(:status, :sortable => "#{Project.table_name}.status"),
@@ -83,12 +88,6 @@ class ProjectQuery < Query
add_custom_fields_filters(project_custom_fields)
end
- def build_from_params(params, defaults={})
- query = super
- query.admin_projects = params[:admin_projects]
- query
- end
-
def available_columns
return @available_columns if @available_columns
@@ -99,28 +98,7 @@ class ProjectQuery < Query
end
def available_display_types
- if self.admin_projects
- ['list']
- else
- ['board', 'list']
- end
- end
-
- def display_type
- if self.admin_projects
- 'list'
- else
- super
- end
- end
-
- def project_statuses_values
- values = super
- if self.admin_projects
- values << [l(:project_status_archived), Project::STATUS_ARCHIVED.to_s]
- values << [l(:project_status_scheduled_for_deletion), Project::STATUS_SCHEDULED_FOR_DELETION.to_s]
- end
- values
+ ['board', 'list']
end
def default_columns_names
@@ -136,11 +114,7 @@ class ProjectQuery < Query
end
def base_scope
- if self.admin_projects
- Project.where(statement)
- else
- Project.visible.where(statement)
- end
+ Project.visible.where(statement)
end
# Returns the project count
diff --git a/app/models/query.rb b/app/models/query.rb
index 445261893..ebf254102 100644
--- a/app/models/query.rb
+++ b/app/models/query.rb
@@ -355,6 +355,8 @@ class Query < ApplicationRecord
# Permission required to view the queries, set on subclasses.
class_attribute :view_permission
+ class_attribute :layout, default: 'base'
+
# Scope of queries that are global or on the given project
scope :global_or_on_project, (lambda do |project|
where(:project_id => (project.nil? ? nil : [nil, project.id]))
@@ -1004,7 +1006,7 @@ class Query < ApplicationRecord
end
end
- if field == 'project_id' || (self.type == 'ProjectQuery' && %w[id parent_id].include?(field))
+ if field == 'project_id' || (is_a?(ProjectQuery) && %w[id parent_id].include?(field))
if v.delete('mine')
v += User.current.memberships.pluck(:project_id).map(&:to_s)
end
diff --git a/app/models/time_entry_query.rb b/app/models/time_entry_query.rb
index 41997180b..82b895671 100644
--- a/app/models/time_entry_query.rb
+++ b/app/models/time_entry_query.rb
@@ -164,12 +164,19 @@ class TimeEntryQuery < Query
end
def base_scope
- TimeEntry.visible.
- joins(:project, :user).
- includes(:activity).
- references(:activity).
- left_join_issue.
- where(statement)
+ scope = TimeEntry.visible
+ .joins(:project, :user)
+ .includes(:activity)
+ .references(:activity)
+ .left_join_issue
+ .where(statement)
+
+ if Redmine::Database.mysql? && ActiveRecord::Base.connection.supports_optimizer_hints?
+ # Provides MySQL with a hint to use a better join order and avoid slow response times
+ scope.optimizer_hints('JOIN_ORDER(time_entries, projects, users)')
+ else
+ scope
+ end
end
def results_scope(options={})
diff --git a/app/models/user.rb b/app/models/user.rb
index 4ce63f809..c544ca64c 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -89,8 +89,8 @@ class User < Principal
:after_remove => Proc.new {|user, group| group.user_removed(user)}
has_many :changesets, :dependent => :nullify
has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
- has_one :atom_token, lambda {where "action='feeds'"}, :class_name => 'Token'
- has_one :api_token, lambda {where "action='api'"}, :class_name => 'Token'
+ has_one :atom_token, lambda {where "#{table.name}.action='feeds'"}, :class_name => 'Token'
+ has_one :api_token, lambda {where "#{table.name}.action='api'"}, :class_name => 'Token'
has_one :email_address, lambda {where :is_default => true}, :autosave => true
has_many :email_addresses, :dependent => :delete_all
belongs_to :auth_source
diff --git a/app/models/user_query.rb b/app/models/user_query.rb
index e805af2b5..fc8ba6463 100644
--- a/app/models/user_query.rb
+++ b/app/models/user_query.rb
@@ -17,6 +17,7 @@
# along with this program; if not, write to the Free Software
class UserQuery < Query
+ self.layout = 'admin'
self.queried_class = Principal # must be Principal (not User) for custom field filters to work
self.available_columns = [
@@ -33,6 +34,15 @@ class UserQuery < Query
QueryAssociationColumn.new(:auth_source, :name, caption: :field_auth_source, sortable: "#{AuthSource.table_name}.name")
]
+ def self.visible(*args)
+ user = args.shift || User.current
+ if user.admin?
+ where('1=1')
+ else
+ where('1=0')
+ end
+ end
+
def initialize(attributes=nil, *args)
super(attributes)
self.filters ||= { 'status' => {operator: "=", values: [User::STATUS_ACTIVE]} }
@@ -64,6 +74,14 @@ class UserQuery < Query
add_custom_fields_filters(user_custom_fields)
end
+ def visible?(user=User.current)
+ user&.admin?
+ end
+
+ def editable_by?(user)
+ user&.admin?
+ end
+
def auth_sources_values
AuthSource.order(name: :asc).pluck(:name, :id)
end
diff --git a/app/views/activities/_activities.html.erb b/app/views/activities/_activities.html.erb
index 21ec1fb28..f2d8e22bd 100644
--- a/app/views/activities/_activities.html.erb
+++ b/app/views/activities/_activities.html.erb
@@ -4,7 +4,7 @@
<dl>
<% sort_activity_events(events_by_day[day]).each do |e, in_group| -%>
<dt class="<%= e.event_type %> icon icon-<%= e.event_type %> <%= "grouped" if in_group %> <%= User.current.logged? && e.respond_to?(:event_author) && User.current == e.event_author ? 'me' : nil %>">
- <%= activity_event_type_icon e.event_type, plugin: Redmine::Activity.plugin_name(e.activity_provider_options.keys[0]) %>
+ <%= activity_event_type_icon e.event_type, plugin: Redmine::Activity.plugin_name(e.class) %>
<%= avatar(e.event_author) if e.respond_to?(:event_author) %>
<span class="time"><%= format_time(e.event_datetime, false) %></span>
<%= content_tag('span', e.project, :class => 'project') if @project.nil? || @project != e.project %>
diff --git a/app/views/admin/projects.html.erb b/app/views/admin/projects.html.erb
index baaf4e7d3..bb2f05677 100644
--- a/app/views/admin/projects.html.erb
+++ b/app/views/admin/projects.html.erb
@@ -6,7 +6,6 @@
<%= @query.persisted? && @query.description.present? ? content_tag('p', @query.description, class: 'subtitle') : '' %>
<%= form_tag(admin_projects_path(@project, nil), :method => :get, :id => 'query_form') do %>
-<%= hidden_field_tag 'admin_projects', '1' %>
<%= render :partial => 'queries/query_form' %>
<% end %>
@@ -19,5 +18,6 @@
<% end %>
<% content_for :sidebar do %>
- <%= render :partial => 'projects/sidebar' %>
+ <%= render_sidebar_queries(ProjectAdminQuery, @project) %>
+ <%= call_hook(:view_admin_projects_sidebar_queries_bottom) %>
<% end %>
diff --git a/app/views/attachments/_form.html.erb b/app/views/attachments/_form.html.erb
index 7bda59c95..e5b10fb55 100644
--- a/app/views/attachments/_form.html.erb
+++ b/app/views/attachments/_form.html.erb
@@ -7,17 +7,22 @@
<% css_class = (defined?(filedrop) && filedrop == false ? '' : (attachment_format_custom_field ? 'custom-field-filedrop' : 'filedrop')) %>
<span class="attachments_form">
+ <span class="attachments_icons hidden">
+ <%= sprite_icon('del', icon_only: true, css_class: 'svg-del') %>
+ <%= sprite_icon('attachment', icon_only: true, size: 16, css_class: 'svg-attachment') %>
+ </span>
<span class="attachments_fields">
<% if saved_attachments.present? %>
<% saved_attachments.each_with_index do |attachment, i| %>
<span id="attachments_p<%= i %>">
+ <%= sprite_icon('attachment', icon_only: true, size: 16, css_class: 'svg-attachment') %>
<%= text_field_tag("#{attachment_param}[p#{i}][filename]", attachment.filename, :class => 'filename') %>
<% if attachment.container_id.present? %>
- <%= link_to l(:label_delete), "#", :onclick => "$(this).closest('.attachments_form').find('.add_attachment').show(); $(this).parent().remove(); return false;", :class => 'icon-only icon-del' %>
+ <%= link_to sprite_icon('del', l(:button_delete), icon_only: true), "#", :onclick => "$(this).closest('.attachments_form').find('.add_attachment').show(); $(this).parent().remove(); return false;", :class => 'icon-only icon-del' %>
<%= hidden_field_tag "#{attachment_param}[p#{i}][id]", attachment.id %>
<% else %>
<%= text_field_tag("#{attachment_param}[p#{i}][description]", attachment.description, :maxlength => 255, :placeholder => l(:label_optional_description), :class => 'description') if description %>
- <%= link_to('&nbsp;'.html_safe, attachment_path(attachment, :attachment_id => "p#{i}", :format => 'js'), :method => 'delete', :remote => true, :class => 'icon-only icon-del remove-upload') %>
+ <%= link_to(sprite_icon('del', l(:button_delete), icon_only: true), attachment_path(attachment, :attachment_id => "p#{i}", :format => 'js'), :method => 'delete', :remote => true, :class => 'icon-only icon-del remove-upload') %>
<%= hidden_field_tag "#{attachment_param}[p#{i}][token]", attachment.token %>
<% end %>
</span>
diff --git a/app/views/attachments/other.html.erb b/app/views/attachments/other.html.erb
index f0f732f6f..613c470df 100644
--- a/app/views/attachments/other.html.erb
+++ b/app/views/attachments/other.html.erb
@@ -14,6 +14,7 @@
:download_link => link_to_attachment(
@attachment,
:text => l(:label_no_preview_download),
+ :icon => 'download',
:download => true,
:class => 'icon icon-download'
)
diff --git a/app/views/calendars/show.html.erb b/app/views/calendars/show.html.erb
index c1d412a0b..d5cb6a6a1 100644
--- a/app/views/calendars/show.html.erb
+++ b/app/views/calendars/show.html.erb
@@ -10,7 +10,7 @@
<div id="query_form_content">
<fieldset id="filters" class="collapsible <%= @query.new_record? ? "" : "collapsed" %>">
<legend onclick="toggleFieldset(this);" class="icon icon-<%= @query.new_record? ? "expanded" : "collapsed" %>">
- <%= sprite_icon(@query.new_record? ? "angle-down" : "angle-right") %>
+ <%= sprite_icon(@query.new_record? ? "angle-down" : "angle-right", rtl: !@query.new_record?) %>
<%= l(:label_filter_plural) %>
</legend>
<div style="<%= @query.new_record? ? "" : "display: none;" %>">
diff --git a/app/views/common/_tabs.html.erb b/app/views/common/_tabs.html.erb
index def21ff45..0190e5e6b 100644
--- a/app/views/common/_tabs.html.erb
+++ b/app/views/common/_tabs.html.erb
@@ -12,8 +12,12 @@
<% end -%>
</ul>
<div class="tabs-buttons" style="display:none;">
- <button class="tab-left" type="button" onclick="moveTabLeft(this);"></button>
- <button class="tab-right" type="button" onclick="moveTabRight(this);"></button>
+ <button class="tab-left icon-only" type="button" onclick="moveTabLeft(this);">
+ <%= sprite_icon "angle-left" %>
+ </button>
+ <button class="tab-right icon-only" type="button" onclick="moveTabRight(this);">
+ <%= sprite_icon "angle-right" %>
+ </button>
</div>
</div>
diff --git a/app/views/common/error.html.erb b/app/views/common/error.html.erb
index 007063f9c..e1c6ef82f 100644
--- a/app/views/common/error.html.erb
+++ b/app/views/common/error.html.erb
@@ -1,7 +1,10 @@
<h2><%= @status %></h2>
<% if @message.present? %>
- <p id="errorExplanation"><%= @message %></p>
+ <p id="errorExplanation">
+ <%= notice_icon('error') %>
+ <%= @message %>
+ </p>
<% end %>
<% if @archived_project && User.current.admin? %>
diff --git a/app/views/context_menus/issues.html.erb b/app/views/context_menus/issues.html.erb
index 64603dc61..ee7eaa18c 100644
--- a/app/views/context_menus/issues.html.erb
+++ b/app/views/context_menus/issues.html.erb
@@ -12,6 +12,7 @@
<% if @allowed_statuses.present? %>
<li class="folder">
<a href="#" class="submenu"><%= l(:field_status) %></a>
+ <span class="icon-only"><%= sprite_icon('angle-right', rtl: true) %></span>
<ul>
<% @allowed_statuses.each do |s| -%>
<li>
@@ -33,6 +34,7 @@
<% if @trackers.present? %>
<li class="folder">
<a href="#" class="submenu"><%= l(:field_tracker) %></a>
+ <span class="icon-only"><%= sprite_icon('angle-right', rtl: true) %></span>
<ul>
<% @trackers.each do |t| -%>
<li><%= context_menu_link t.name, _bulk_update_issues_path(@issue, :ids => @issue_ids, :issue => {'tracker_id' => t}, :back_url => @back), :method => :patch,
@@ -45,6 +47,7 @@
<% if @safe_attributes.include?('priority_id') && @priorities.present? -%>
<li class="folder">
<a href="#" class="submenu"><%= l(:field_priority) %></a>
+ <span class="icon-only"><%= sprite_icon('angle-right', rtl: true) %></span>
<ul>
<% @priorities.each do |p| -%>
<li><%= context_menu_link p.name, _bulk_update_issues_path(@issue, :ids => @issue_ids, :issue => {'priority_id' => p}, :back_url => @back), :method => :patch,
@@ -57,6 +60,7 @@
<% if @safe_attributes.include?('fixed_version_id') && @versions.present? -%>
<li class="folder">
<a href="#" class="submenu"><%= l(:field_fixed_version) %></a>
+ <span class="icon-only"><%= sprite_icon('angle-right', rtl: true) %></span>
<ul>
<% @versions.sort.each do |v| -%>
<li><%= context_menu_link format_version_name(v), _bulk_update_issues_path(@issue, :ids => @issue_ids, :issue => {'fixed_version_id' => v}, :back_url => @back), :method => :patch,
@@ -71,6 +75,7 @@
<% if @safe_attributes.include?('assigned_to_id') && @assignables.present? -%>
<li class="folder">
<a href="#" class="submenu"><%= l(:field_assigned_to) %></a>
+ <span class="icon-only"><%= sprite_icon('angle-right', rtl: true) %></span>
<ul>
<% if @assignables.include?(User.current) %>
<li><%= context_menu_link "<< #{l(:label_me)} >>", _bulk_update_issues_path(@issue, :ids => @issue_ids, :issue => {'assigned_to_id' => User.current}, :back_url => @back), :method => :patch,
@@ -89,6 +94,7 @@
<% if @safe_attributes.include?('category_id') && @project && @project.issue_categories.any? -%>
<li class="folder">
<a href="#" class="submenu"><%= l(:field_category) %></a>
+ <span class="icon-only"><%= sprite_icon('angle-right', rtl: true) %></span>
<ul>
<% @project.issue_categories.each do |u| -%>
<li><%= context_menu_link u.name, _bulk_update_issues_path(@issue, :ids => @issue_ids, :issue => {'category_id' => u}, :back_url => @back), :method => :patch,
@@ -103,6 +109,7 @@
<% if @safe_attributes.include?('done_ratio') && Issue.use_field_for_done_ratio? %>
<li class="folder">
<a href="#" class="submenu"><%= l(:field_done_ratio) %></a>
+ <span class="icon-only"><%= sprite_icon('angle-right', rtl: true) %></span>
<ul>
<% (0..10).map{|x|x*10}.each do |p| -%>
<li><%= context_menu_link "#{p}%", _bulk_update_issues_path(@issue, :ids => @issue_ids, :issue => {'done_ratio' => p}, :back_url => @back), :method => :patch,
@@ -115,6 +122,7 @@
<% @options_by_custom_field.each do |field, options| %>
<li class="folder <%= field.css_classes %>">
<a href="#" class="submenu"><%= field.name %></a>
+ <span class="icon-only"><%= sprite_icon('angle-right', rtl: true) %></span>
<ul>
<% options.each do |text, value| %>
<li><%= bulk_update_custom_field_context_menu_link(field, text, value || text) %></li>
@@ -129,6 +137,7 @@
<% if @can[:add_watchers] %>
<li class="folder">
<a href="#" class="submenu"><%= l(:label_issue_watchers) %></a>
+ <span class="icon-only"><%= sprite_icon('angle-right', rtl: true) %></span>
<ul>
<li><%= context_menu_link sprite_icon('add', l(:button_add)),
new_watchers_path(:object_type => 'issue', :object_id => @issue_ids),
diff --git a/app/views/context_menus/time_entries.html.erb b/app/views/context_menus/time_entries.html.erb
index c350aa337..d43021f59 100644
--- a/app/views/context_menus/time_entries.html.erb
+++ b/app/views/context_menus/time_entries.html.erb
@@ -12,6 +12,7 @@
<% if @activities.present? -%>
<li class="folder">
<a href="#" class="submenu"><%= l(:field_activity) %></a>
+ <span class="icon-only"><%= sprite_icon('angle-right', rtl: true) %></span>
<ul>
<% @activities.each do |u| -%>
<li><%= context_menu_link u.name, {:controller => 'timelog', :action => 'bulk_update', :ids => @time_entries.collect(&:id), :time_entry => {'activity_id' => u}, :back_url => @back}, :method => :post,
@@ -24,6 +25,7 @@
<% @options_by_custom_field.each do |field, options| %>
<li class="folder <%= field.css_classes %>">
<a href="#" class="submenu"><%= field.name %></a>
+ <span class="icon-only"><%= sprite_icon('angle-right', rtl: true) %></span>
<ul>
<% options.each do |text, value| %>
<li><%= bulk_update_time_entry_custom_field_context_menu_link(field, text, value || text) %></li>
diff --git a/app/views/custom_field_enumerations/index.html.erb b/app/views/custom_field_enumerations/index.html.erb
index 83e9d59c9..bd6651218 100644
--- a/app/views/custom_field_enumerations/index.html.erb
+++ b/app/views/custom_field_enumerations/index.html.erb
@@ -6,7 +6,7 @@
<ul id="custom_field_enumerations" class="flat">
<% @custom_field.enumerations.each_with_index do |value, position| %>
<li>
- <span class="icon-only icon-sort-handle sort-handle"></span>
+ <%= tag.span(sprite_icon('reorder', ''), :class => 'icon-only icon-sort-handle sort-handle', :title => l(:button_move)) %>
<%= hidden_field_tag "custom_field_enumerations[#{value.id}][position]", position, :class => 'position' %>
<%= text_field_tag "custom_field_enumerations[#{value.id}][name]", value.name, :size => 40 %>
<%= hidden_field_tag "custom_field_enumerations[#{value.id}][active]", 0 %>
diff --git a/app/views/custom_fields/index.api.rsb b/app/views/custom_fields/index.api.rsb
index 9f46f89f2..d4b19d62b 100644
--- a/app/views/custom_fields/index.api.rsb
+++ b/app/views/custom_fields/index.api.rsb
@@ -15,6 +15,7 @@ api.array :custom_fields do
api.multiple field.multiple?
api.default_value field.default_value
api.visible field.visible?
+ api.editable field.editable?
values = field.possible_values_options
if values.present?
diff --git a/app/views/gantts/show.html.erb b/app/views/gantts/show.html.erb
index c43f10fdd..27da5d29a 100644
--- a/app/views/gantts/show.html.erb
+++ b/app/views/gantts/show.html.erb
@@ -16,7 +16,7 @@
<div id="query_form_content">
<fieldset id="filters" class="collapsible <%= @query.new_record? ? "" : "collapsed" %>">
<legend onclick="toggleFieldset(this);" class="icon icon-<%= @query.new_record? ? "expanded" : "collapsed" %>">
- <%= sprite_icon(@query.new_record? ? "angle-down" : "angle-right") %>
+ <%= sprite_icon(@query.new_record? ? "angle-down" : "angle-right", rtl: !@query.new_record?) %>
<%= l(:label_filter_plural) %>
</legend>
<div style="<%= @query.new_record? ? "" : "display: none;" %>">
@@ -26,7 +26,7 @@
<fieldset id="options" class="collapsible collapsed">
<legend onclick="toggleFieldset(this);" class="icon icon-collapsed">
- <%= sprite_icon("angle-right") %>
+ <%= sprite_icon("angle-right", rtl: true) %>
<%= l(:label_options) %>
</legend>
<div style="display: none;">
diff --git a/app/views/help/wiki_syntax/common_mark/en/wiki_syntax_detailed_common_mark.html.erb b/app/views/help/wiki_syntax/common_mark/en/wiki_syntax_detailed_common_mark.html.erb
index a47a570f1..193606ab2 100644
--- a/app/views/help/wiki_syntax/common_mark/en/wiki_syntax_detailed_common_mark.html.erb
+++ b/app/views/help/wiki_syntax/common_mark/en/wiki_syntax_detailed_common_mark.html.erb
@@ -359,8 +359,8 @@ It can be expanded by clicking a link.
<p>The <strong>style</strong> attribute can be used in raw HTML to apply custom formatting. The following CSS properties are allowed:</p>
<pre><code>
color background-color
- width
- height
+ width min-width max-width
+ height min-height max-height
padding padding-left padding-right padding-top padding-bottom
margin margin-left margin-right margin-top margin-bottom
border border-left border-right border-top border-bottom border-radius border-style border-collapse border-spacing
diff --git a/app/views/imports/_issues_mapping.html.erb b/app/views/imports/_issues_mapping.html.erb
index 86e2dd89a..539bad9fe 100644
--- a/app/views/imports/_issues_mapping.html.erb
+++ b/app/views/imports/_issues_mapping.html.erb
@@ -7,7 +7,7 @@
<fieldset class="box tabular collapsible collapsed">
<legend onclick="toggleFieldset(this);" class="icon icon-collapsed">
- <%= sprite_icon("angle-right") %>
+ <%= sprite_icon("angle-right", rtl: true) %>
<%= l(:label_relations_mapping) %>
</legend>
<div id="relations-mapping" style="display: none;">
diff --git a/app/views/issue_relations/_form.html.erb b/app/views/issue_relations/_form.html.erb
index 97fe78cc6..b55e93eac 100644
--- a/app/views/issue_relations/_form.html.erb
+++ b/app/views/issue_relations/_form.html.erb
@@ -2,6 +2,7 @@
<% if @unsaved_relations && @unsaved_relations.any? %>
<% unsaved_relations_ids = @unsaved_relations.map(&:issue_to_id).compact.join(", ") %>
<div id="errorExplanation">
+ <%= notice_icon('error') %>
<ul>
<% relation_error_messages(@unsaved_relations).each do |message| %>
<li><%= message %></li>
diff --git a/app/views/issues/_conflict.html.erb b/app/views/issues/_conflict.html.erb
index ea4c35d7d..596584d14 100644
--- a/app/views/issues/_conflict.html.erb
+++ b/app/views/issues/_conflict.html.erb
@@ -1,4 +1,5 @@
<div class="conflict">
+ <%= notice_icon('warning') %>
<%= l(:notice_issue_update_conflict) %>
<% if @conflict_journals.present? %>
<div class="conflict-details">
diff --git a/app/views/issues/_list.html.erb b/app/views/issues/_list.html.erb
index df562c6f2..e8b151ef6 100644
--- a/app/views/issues/_list.html.erb
+++ b/app/views/issues/_list.html.erb
@@ -15,7 +15,7 @@
<% query.inline_columns.each do |column| %>
<%= column_header(query, column, query_options) %>
<% end %>
- <th class="buttons"></th>
+ <th class="buttons hide-when-print"></th>
</tr>
</thead>
<tbody>
@@ -36,7 +36,7 @@
<% query.inline_columns.each do |column| %>
<%= content_tag('td', column_content(column, issue), :class => column.css_classes) %>
<% end %>
- <td class="buttons"><%= link_to_context_menu %></td>
+ <td class="buttons hide-when-print"><%= link_to_context_menu %></td>
</tr>
<% query.block_columns.each do |column|
if (text = column_content(column, issue)) && text.present? -%>
diff --git a/app/views/issues/bulk_edit.html.erb b/app/views/issues/bulk_edit.html.erb
index a379305be..b8bf87beb 100644
--- a/app/views/issues/bulk_edit.html.erb
+++ b/app/views/issues/bulk_edit.html.erb
@@ -2,6 +2,7 @@
<% if @saved_issues && @unsaved_issues.present? %>
<div id="errorExplanation">
+ <%= notice_icon('error') %>
<span>
<%= l(:notice_failed_to_save_issues,
:count => @unsaved_issues.size,
@@ -241,6 +242,7 @@
<% if @values_by_custom_field.present? %>
<div class="flash warning">
+ <%= notice_icon('warning') %>
<%= l(:warning_fields_cleared_on_bulk_edit) %>:<br />
<%= safe_join(@values_by_custom_field.map {|field, ids| content_tag "span", "#{field.name} (#{ids.size})"}, ', ') %>
</div>
diff --git a/app/views/layouts/base.html.erb b/app/views/layouts/base.html.erb
index fdd560481..9293e3dd1 100644
--- a/app/views/layouts/base.html.erb
+++ b/app/views/layouts/base.html.erb
@@ -106,7 +106,7 @@
<% if sidebar_content? %>
<div id="sidebar-switch-panel" style="visibility: hidden;">
<a id="sidebar-switch-button" class="" href="#">
- <%= sprite_icon("chevrons-right", size: 20) %></a>
+ <%= sprite_icon("chevrons-right", size: 20, rtl: true) %></a>
</div>
<%= javascript_tag "$('#sidebar-switch-panel').css('visibility', 'visible');" %>
<% end %>
@@ -124,7 +124,7 @@
</div>
</div>
<div id="footer">
- Powered by <%= link_to Redmine::Info.app_name, Redmine::Info.url, :target => '_blank', :rel => 'noopener' %> &copy; 2006-2024 Jean-Philippe Lang
+ Powered by <%= link_to Redmine::Info.app_name, Redmine::Info.url, :target => '_blank', :rel => 'noopener' %> &copy; 2006-2025 Jean-Philippe Lang
</div>
<div id="ajax-indicator" style="display:none;"><span><%= l(:label_loading) %></span></div>
diff --git a/app/views/projects/settings/_members.html.erb b/app/views/projects/settings/_members.html.erb
index 6e13808b6..0514baca0 100644
--- a/app/views/projects/settings/_members.html.erb
+++ b/app/views/projects/settings/_members.html.erb
@@ -19,9 +19,13 @@
<% members.each do |member| %>
<% next if member.new_record? %>
<tr id="member-<%= member.id %>" class="member">
- <td class="name icon icon-<%= member.principal.class.name.downcase %>">
- <%= principal_icon(member.principal) %>
- <%= link_to_user member.principal %>
+ <td class="name">
+ <span class="icon">
+ <% if member.principal %>
+ <%= principal_icon(member.principal) %>
+ <%= link_to_user member.principal %>
+ <% end %>
+ </span>
</td>
<td class="roles">
<span id="member-<%= member.id %>-roles"><%= member.roles.sort.collect(&:to_s).join(', ') %></span>
diff --git a/app/views/queries/_filters.html.erb b/app/views/queries/_filters.html.erb
index 42756775a..a1118f6ab 100644
--- a/app/views/queries/_filters.html.erb
+++ b/app/views/queries/_filters.html.erb
@@ -22,5 +22,6 @@ $(document).ready(function(){
<%= select_tag 'add_filter_select', filters_options_for_select(query), :name => nil %>
</div>
+<div id="icon-copy-source" style="display: none;"><%= sprite_icon('') %></div>
<%= hidden_field_tag 'f[]', '' %>
<% include_calendar_headers_tags %>
diff --git a/app/views/queries/_form.html.erb b/app/views/queries/_form.html.erb
index 213fb4890..8b52383de 100644
--- a/app/views/queries/_form.html.erb
+++ b/app/views/queries/_form.html.erb
@@ -4,7 +4,6 @@
<div class="tabular">
<%= hidden_field_tag 'gantt', '1' if params[:gantt] %>
<%= hidden_field_tag 'calendar', '1' if params[:calendar] %>
-<%= hidden_field_tag 'admin_projects', '1' if params[:admin_projects] %>
<p>
<label for="query_name">
@@ -21,7 +20,7 @@
<p><label><%=l(:field_visible)%></label>
<label class="block"><%= radio_button 'query', 'visibility', Query::VISIBILITY_PRIVATE %> <%= l(:label_visibility_private) %></label>
<label class="block"><%= radio_button 'query', 'visibility', Query::VISIBILITY_PUBLIC %> <%= l(:label_visibility_public) %></label>
- <% unless @query.type == 'ProjectQuery' %>
+ <% unless @query.is_a?(ProjectQuery) %>
<label class="block"><%= radio_button 'query', 'visibility', Query::VISIBILITY_ROLES %> <%= l(:label_visibility_roles) %>:</label>
<% Role.givable.sorted.each do |role| %>
<label class="block role-visibility"><%= check_box_tag 'query[role_ids][]', role.id, @query.roles.include?(role), :id => nil %> <%= role.name %></label>
@@ -31,9 +30,9 @@
</p>
<% end %>
-<% unless @query.type == 'ProjectQuery' %>
+<% unless @query.is_a?(ProjectQuery) %>
<p><label for="query_is_for_all"><%=l(:field_is_for_all)%></label>
- <%= check_box_tag 'query_is_for_all', 1, @query.project.nil?, :class => (User.current.admin? ? '' : 'disable-unless-private') %></p>
+ <%= check_box_tag 'query_is_for_all', 1, @query.project.nil?, :disabled => (!@query.new_record? && @query.project.nil?), :class => (User.current.admin? ? '' : 'disable-unless-private') %></p>
<% end %>
<% unless params[:calendar] %>
diff --git a/app/views/queries/_query_form.html.erb b/app/views/queries/_query_form.html.erb
index 68bc483a4..77094e16e 100644
--- a/app/views/queries/_query_form.html.erb
+++ b/app/views/queries/_query_form.html.erb
@@ -6,7 +6,7 @@
<div id="query_form_content">
<fieldset id="filters" class="collapsible <%= @query.new_record? ? "" : "collapsed" %>">
<legend onclick="toggleFieldset(this);" class="icon icon-<%= @query.new_record? ? "expanded" : "collapsed" %>">
- <%= sprite_icon(@query.new_record? ? "angle-down" : "angle-right") %>
+ <%= sprite_icon(@query.new_record? ? "angle-down" : "angle-right", rtl: !@query.new_record?) %>
<%= l(:label_filter_plural) %>
</legend>
<div style="<%= @query.new_record? ? "" : "display: none;" %>">
@@ -17,7 +17,7 @@
<% if @query.available_columns.any? %>
<fieldset id="options" class="collapsible collapsed">
<legend onclick="toggleFieldset(this);" class="icon icon-collapsed">
- <%= sprite_icon("angle-right") %>
+ <%= sprite_icon("angle-right", rtl: true) %>
<%= l(:label_options) %>
</legend>
<div class="hidden">
@@ -69,9 +69,8 @@
<% end %>
<% else %>
<% if @query.editable_by?(User.current) %>
- <% redirect_params = (controller_name == 'admin' && action_name == 'projects') ? {:admin_projects => 1} : {} %>
- <%= link_to sprite_icon('edit', l(:button_edit_object, object_name: l(:label_query)).capitalize), edit_query_path(@query, redirect_params), :class => 'icon icon-edit' %>
- <%= delete_link query_path(@query, redirect_params), {}, l(:button_delete_object, object_name: l(:label_query)).capitalize %>
+ <%= link_to sprite_icon('edit', l(:button_edit_object, object_name: l(:label_query)).capitalize), edit_query_path(@query), :class => 'icon icon-edit' %>
+ <%= delete_link query_path(@query), {}, l(:button_delete_object, object_name: l(:label_query)).capitalize %>
<% end %>
<% end %>
</p>
diff --git a/app/views/repositories/_dir_list_content.html.erb b/app/views/repositories/_dir_list_content.html.erb
index 991400d7a..aed3dcc0c 100644
--- a/app/views/repositories/_dir_list_content.html.erb
+++ b/app/views/repositories/_dir_list_content.html.erb
@@ -14,7 +14,7 @@
:path => to_path_param(ent_path),
:rev => @rev,
:depth => (depth + 1),
- :parent_id => tr_id)) %>');"><%= sprite_icon('angle-right') %></span>
+ :parent_id => tr_id)) %>');"><%= sprite_icon('angle-right', rtl: true) %></span>
<% end %>
<%= link_to file_icon(entry, ent_name),
{:action => (entry.is_dir? ? 'show' : 'entry'), :id => @project, :repository_id => @repository.identifier_param, :path => to_path_param(ent_path), :rev => @rev},
diff --git a/app/views/repositories/annotate.html.erb b/app/views/repositories/annotate.html.erb
index db91ef913..e532714e4 100644
--- a/app/views/repositories/annotate.html.erb
+++ b/app/views/repositories/annotate.html.erb
@@ -51,7 +51,10 @@
</table>
</div>
<% else %>
-<p id="errorExplanation"><%= @error_message %></p>
+<p id="errorExplanation">
+ <%= notice_icon('error') %>
+ <%= @error_message %>
+</p>
<% end %>
<% html_title(l(:button_annotate)) -%>
diff --git a/app/views/repositories/revision.html.erb b/app/views/repositories/revision.html.erb
index ba716dd41..9423e5438 100644
--- a/app/views/repositories/revision.html.erb
+++ b/app/views/repositories/revision.html.erb
@@ -30,11 +30,11 @@
<% if User.current.allowed_to?(:browse_repository, @project) %>
<ul id="changes-legend">
-<li class="change change-A"><%= l(:label_added) %></li>
-<li class="change change-M"><%= l(:label_modified) %></li>
-<li class="change change-C"><%= l(:label_copied) %></li>
-<li class="change change-R"><%= l(:label_renamed) %></li>
-<li class="change change-D"><%= l(:label_deleted) %></li>
+<li class="change change-A"><%= scm_change_icon("A", (:label_added)) %></li>
+<li class="change change-M"><%= scm_change_icon("M", l(:label_modified)) %></li>
+<li class="change change-C"><%= scm_change_icon("C", l(:label_copied)) %></li>
+<li class="change change-R"><%= scm_change_icon("R", l(:label_renamed)) %></li>
+<li class="change change-D"><%= scm_change_icon("D", l(:label_deleted)) %></li>
</ul>
<div class="changeset-changes">
diff --git a/app/views/roles/permissions.html.erb b/app/views/roles/permissions.html.erb
index 63a1267fc..573fbc9fa 100644
--- a/app/views/roles/permissions.html.erb
+++ b/app/views/roles/permissions.html.erb
@@ -3,7 +3,7 @@
<div class="hide-when-print">
<fieldset id="filters" class="collapsible collapsed">
<legend onclick="toggleFieldset(this);" class="icon icon-collapsed">
- <%= sprite_icon("angle-right") %>
+ <%= sprite_icon("angle-right", rtl: true) %>
<%= l(:label_filter_plural) %>
</legend>
<div style="display: none;">
diff --git a/app/views/search/index.html.erb b/app/views/search/index.html.erb
index ce7887a9d..c17bbd8ea 100644
--- a/app/views/search/index.html.erb
+++ b/app/views/search/index.html.erb
@@ -25,7 +25,7 @@
<fieldset class="collapsible collapsed">
<legend onclick="toggleFieldset(this);" class="icon icon-collapsed">
- <%= sprite_icon("angle-right") %>
+ <%= sprite_icon("angle-right", rtl: true) %>
<%= l(:label_options) %>
</legend>
<div id="options-content" style="display:none;">
@@ -51,12 +51,13 @@
<h3><%= l(:label_result_plural) %> (<%= @result_count %>)</h3>
<% if @result_count_by_type['issues'].to_i > 0 && @search_attachments == '0' %>
<p class="buttons">
- <%= link_to l(:button_apply_issues_filter), issues_filter_path(@question, projects_scope: params[:scope], all_words: @all_words, titles_only: @titles_only, open_issues: @open_issues), :class => 'icon icon-list' %>
+ <%= link_to sprite_icon('list', l(:button_apply_issues_filter)), issues_filter_path(@question, projects_scope: params[:scope], all_words: @all_words, titles_only: @titles_only, open_issues: @open_issues), :class => 'icon icon-list' %>
</p>
<% end %>
<dl id="search-results">
<% @results.each do |e| %>
<dt class="<%= e.event_type %> icon icon-<%= e.event_type %>">
+ <%= sprite_icon(e.event_type) %>
<%= content_tag('span', e.project, :class => 'project') unless @project == e.project %>
<%= link_to(highlight_tokens(e.event_title.truncate(255), @tokens), e.event_url) %>
</dt>
diff --git a/app/views/timelog/_list.html.erb b/app/views/timelog/_list.html.erb
index 1a82b5f51..aa1c1c293 100644
--- a/app/views/timelog/_list.html.erb
+++ b/app/views/timelog/_list.html.erb
@@ -11,7 +11,7 @@
<% @query.inline_columns.each do |column| %>
<%= column_header(@query, column) %>
<% end %>
- <th></th>
+ <th class="buttons hide-when-print"></th>
</tr>
</thead>
<tbody>
@@ -36,7 +36,7 @@
<% @query.inline_columns.each do |column| %>
<%= content_tag('td', column_content(column, entry), :class => column.css_classes) %>
<% end %>
- <td class="buttons">
+ <td class="buttons hide-when-print">
<% if entry.editable_by?(User.current) -%>
<%= link_to sprite_icon('edit', l(:button_edit)), edit_time_entry_path(entry),
:title => l(:button_edit),
diff --git a/app/views/timelog/bulk_edit.html.erb b/app/views/timelog/bulk_edit.html.erb
index a4cb0c8af..4f496470a 100644
--- a/app/views/timelog/bulk_edit.html.erb
+++ b/app/views/timelog/bulk_edit.html.erb
@@ -2,6 +2,7 @@
<% if @unsaved_time_entries.present? %>
<div id="errorExplanation">
+ <%= notice_icon('error') %>
<span>
<%= l(:notice_failed_to_save_time_entries,
:count => @unsaved_time_entries.size,
diff --git a/app/views/versions/_sidebar.html.erb b/app/views/versions/_sidebar.html.erb
index 2b197388b..a1b9452d8 100644
--- a/app/views/versions/_sidebar.html.erb
+++ b/app/views/versions/_sidebar.html.erb
@@ -42,8 +42,8 @@
</ul>
<% if @completed_versions.present? %>
<p>
- <%= link_to_function sprite_icon('angle-right', l(:label_completed_versions)),
- '$("#toggle-completed-versions").toggleClass("icon-collapsed icon-expanded"); $("#completed-versions").toggle()',
+ <%= link_to_function sprite_icon('angle-right', l(:label_completed_versions), rtl: true),
+ '$("#toggle-completed-versions").toggleClass("icon-collapsed icon-expanded"); $("#completed-versions").toggle(); toggleExpendCollapseIcon(this);',
:id => 'toggle-completed-versions', :class => 'icon icon-collapsed collapsible' %>
<ul id = "completed-versions" style = "display:none;">
<% @completed_versions.each do |version| %>
diff --git a/app/views/versions/index.html.erb b/app/views/versions/index.html.erb
index 6c3d518bc..45b254a7d 100644
--- a/app/views/versions/index.html.erb
+++ b/app/views/versions/index.html.erb
@@ -40,7 +40,7 @@
<td class="checkbox"><%= check_box_tag 'ids[]', issue.id, false, :id => nil %></td>
<td class="assigned_to"><%= assignee_avatar(issue.assigned_to, :size => 16) %></td>
<td class="subject"><%= link_to_issue(issue, :project => (@project != issue.project)) %></td>
- <td class="buttons"><%= link_to_context_menu %></td>
+ <td class="buttons hide-when-print"><%= link_to_context_menu %></td>
</tr>
<% end -%>
</table>
diff --git a/app/views/versions/show.html.erb b/app/views/versions/show.html.erb
index f83aff80d..cdd2b3029 100644
--- a/app/views/versions/show.html.erb
+++ b/app/views/versions/show.html.erb
@@ -54,7 +54,7 @@
<td class="checkbox"><%= check_box_tag 'ids[]', issue.id, false, :id => nil %></td>
<td class="assigned_to"><%= assignee_avatar(issue.assigned_to, :size => 16) %></td>
<td class="subject"><%= link_to_issue(issue, :project => (@project != issue.project)) %></td>
- <td class="buttons"><%= link_to_context_menu %></td>
+ <td class="buttons hide-when-print"><%= link_to_context_menu %></td>
</tr>
<% end %>
</table>
diff --git a/app/views/welcome/index.html.erb b/app/views/welcome/index.html.erb
index 990fc03fb..7211818f3 100644
--- a/app/views/welcome/index.html.erb
+++ b/app/views/welcome/index.html.erb
@@ -11,7 +11,7 @@
<div class="splitcontentright">
<% if @news.any? %>
<div class="news box">
- <h3><%=l(:label_news_latest)%></h3>
+ <h3 class="icon icon-news"><%= sprite_icon('news', l(:label_news_latest))%></h3>
<%= render :partial => 'news/news', :collection => @news %>
<%= link_to l(:label_news_view_all), :controller => 'news' %>
</div>
diff --git a/app/views/wiki/show.html.erb b/app/views/wiki/show.html.erb
index 7c35463a0..4b222ef4b 100644
--- a/app/views/wiki/show.html.erb
+++ b/app/views/wiki/show.html.erb
@@ -63,7 +63,7 @@
<fieldset class="collapsible collapsed hide-when-print">
<legend onclick="toggleFieldset(this);" class="icon icon-collapsed">
- <%= sprite_icon("angle-right") %>
+ <%= sprite_icon("angle-right", rtl: true) %>
<%= l(:label_attachment_plural) %> (<%= @page.attachments.length %>)
</legend>
<div style="display: none;">
diff --git a/app/views/workflows/edit.html.erb b/app/views/workflows/edit.html.erb
index defb521a3..3265f68de 100644
--- a/app/views/workflows/edit.html.erb
+++ b/app/views/workflows/edit.html.erb
@@ -15,12 +15,12 @@
<p>
<label><%=l(:label_role)%>:
<%= options_for_workflow_select 'role_id[]', Role.sorted.select(&:consider_workflow?), @roles, :id => 'role_id', :class => 'expandable' %>
- <span class="toggle-multiselect icon-only"></span>
+ <span class="toggle-multiselect icon-only"><%= sprite_icon('') %></span>
</label>
<label><%=l(:label_tracker)%>:
<%= options_for_workflow_select 'tracker_id[]', Tracker.sorted, @trackers, :id => 'tracker_id', :class => 'expandable' %>
- <span class="toggle-multiselect icon-only"></span>
+ <span class="toggle-multiselect icon-only"><%= sprite_icon('') %></span>
</label>
<%= submit_tag l(:button_edit), :name => nil %>
@@ -40,8 +40,8 @@
<%= render :partial => 'form', :locals => {:name => 'always', :workflows => @workflows['always']} %>
<fieldset class="collapsible" style="padding: 0; margin-top: 0.5em;">
- <legend onclick="toggleFieldset(this);" class="icon icon-collapsed">
- <%= sprite_icon("angle-right") %>
+ <legend onclick="toggleFieldset(this);" class="icon icon-<%= @workflows['author'].present? ? "expanded" : "collapsed" %>">
+ <%= sprite_icon(@workflows['author'].present? ? "angle-down" : "angle-right", rtl: !@workflows['author'].present?) %>
<%= l(:label_additional_workflow_transitions_for_author) %>
</legend>
<div id="author_workflows" style="margin: 0.5em 0 0.5em 0;">
@@ -51,8 +51,8 @@
<%= javascript_tag "hideFieldset($('#author_workflows'))" unless @workflows['author'].present? %>
<fieldset class="collapsible" style="padding: 0;">
- <legend onclick="toggleFieldset(this);" class="icon icon-collapsed">
- <%= sprite_icon("angle-right") %>
+ <legend onclick="toggleFieldset(this);" class="icon icon-<%= @workflows['assignee'].present? ? "expanded" : "collapsed" %>">
+ <%= sprite_icon(@workflows['assignee'].present? ? "angle-down" : "angle-right", rtl: !@workflows['assignee'].present?) %>
<%= l(:label_additional_workflow_transitions_for_assignee) %>
</legend>
<div id="assignee_workflows" style="margin: 0.5em 0 0.5em 0;">
diff --git a/app/views/workflows/permissions.html.erb b/app/views/workflows/permissions.html.erb
index 9b326dea2..3de523254 100644
--- a/app/views/workflows/permissions.html.erb
+++ b/app/views/workflows/permissions.html.erb
@@ -15,12 +15,12 @@
<p>
<label><%=l(:label_role)%>:
<%= options_for_workflow_select 'role_id[]', Role.sorted.select(&:consider_workflow?), @roles, :id => 'role_id', :class => 'expandable' %>
- <span class="toggle-multiselect icon-only"></span>
+ <span class="toggle-multiselect icon-only"><%= sprite_icon('') %></span>
</label>
<label><%=l(:label_tracker)%>:
<%= options_for_workflow_select 'tracker_id[]', Tracker.sorted, @trackers, :id => 'tracker_id', :class => 'expandable' %>
- <span class="toggle-multiselect icon-only"></span>
+ <span class="toggle-multiselect icon-only"><%= sprite_icon('') %></span>
</label>
<%= submit_tag l(:button_edit), :name => nil %>
diff --git a/config/boot.rb b/config/boot.rb
index 7479b5aff..e7d68c4ad 100644
--- a/config/boot.rb
+++ b/config/boot.rb
@@ -1,5 +1,21 @@
# frozen_string_literal: true
+# Rack 3.1.14 or later sets default limits of 4MB for query string bytesize
+# and 4096 for the number of query parameters. These limits are too low
+# for Redmine and can cause the following issues:
+#
+# - The low bytesize limit prevents the mail handler from processing incoming
+# emails larger than 4MB (https://www.redmine.org/issues/42962)
+# - The low parameter limit prevents saving workflows with many statuses
+# (https://www.redmine.org/issues/42875)
+#
+# See also:
+# - https://github.com/rack/rack/blob/v3.1.16/README.md#configuration
+# - https://github.com/rack/rack/blob/v3.1.16/lib/rack/query_parser.rb#L54
+# - https://github.com/rack/rack/blob/v3.1.16/lib/rack/query_parser.rb#L57
+ENV['RACK_QUERY_PARSER_BYTESIZE_LIMIT'] ||= '33554432'
+ENV['RACK_QUERY_PARSER_PARAMS_LIMIT'] ||= '65536'
+
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
diff --git a/config/icon_source.yml b/config/icon_source.yml
index 92ea716b1..99c4b07dd 100644
--- a/config/icon_source.yml
+++ b/config/icon_source.yml
@@ -85,6 +85,8 @@
svg: chevron-right
- name: angle-up
svg: chevron-up
+- name: angle-left
+ svg: chevron-left
- name: email
svg: mail
- name: email-disabled
@@ -115,6 +117,8 @@
svg: refresh
- name: projects
svg: packages
+- name: project
+ svg: packages
- name: package
svg: package
- name: custom-fields
@@ -131,6 +135,10 @@
svg: message
- name: comment
svg: message
+- name: message
+ svg: message
+- name: reply
+ svg: messages
- name: arrow-right
svg: arrow-big-right
- name: wiki-page
@@ -199,3 +207,12 @@
svg: key
- name: search
svg: search
+- name: toggle-plus
+ svg: square-rounded-plus
+- name: toggle-minus
+ svg: square-rounded-minus
+- name: circle-minus
+ svg: circle-minus
+- name: circle-dot-filled
+ svg: circle-dot
+ style: filled
diff --git a/config/locales/cs.yml b/config/locales/cs.yml
index ed9150ddf..9597e3a9b 100644
--- a/config/locales/cs.yml
+++ b/config/locales/cs.yml
@@ -1473,10 +1473,10 @@ cs:
field_estimated_remaining_hours: Odhadovaný zbývající čas
field_last_activity_date: Poslední aktivita
setting_issue_done_ratio_interval: Krok koeficientu dokončení
- setting_copy_attachments_on_issue_copy: Copy attachments on copy
- field_thousands_delimiter: Thousands delimiter
- label_involved_principals: Author / Previous assignee
+ setting_copy_attachments_on_issue_copy: Kopírovat přílohy při kopírování
+ field_thousands_delimiter: Oddělovač tisíců
+ label_involved_principals: Autor / Předchozí přiřazený uživatel
label_attachment_summary:
zero: "%{filename}"
- one: "%{filename} and 1 file"
- other: "%{filename} and %{count} files"
+ one: "%{filename} a 1 soubor"
+ other: "%{filename} a %{count} soubory(ů)"
diff --git a/config/locales/fa.yml b/config/locales/fa.yml
index 55c61308f..e42aa8b97 100644
--- a/config/locales/fa.yml
+++ b/config/locales/fa.yml
@@ -140,7 +140,7 @@ fa:
must_contain_digits: "باید شامل عدد باشد (0-9)"
must_contain_special_chars: "باید شامل نویسه‌های خاص باشد (!, $, %, ...)"
domain_not_allowed: "شامل دامنه‌ی غیرمجازست (%{domain})"
- too_simple: "is too simple"
+ too_simple: "خیلی ساده است"
actionview_instancetag_blank_option: انتخاب کنید
@@ -274,10 +274,9 @@ fa:
mail_body_security_notification_notify_disabled: "نشانی رایانامه %{value} دیگر آگاه‌سازی دریافت نخواهد کرد."
mail_body_settings_updated: "تنظیمات زیر تغییر کردند:"
mail_body_password_updated: "گذرواژه شما تغییر کرد."
- mail_destroy_project_failed: Project %{value} could not be deleted.
- mail_destroy_project_successful: Project %{value} was deleted successfully.
- mail_destroy_project_with_subprojects_successful: Project %{value} and its subprojects
- were deleted successfully.
+ mail_destroy_project_failed: پروژه %{value} نمی‌تواند حذف شود.
+ mail_destroy_project_successful: پروژه %{value} با موفقیت حذف شد.
+ mail_destroy_project_with_subprojects_successful: پروژه %{value} و زیرپروژه‌های آن با موفقیت حذف شدند.
field_name: نام
field_description: توضیح
@@ -421,6 +420,10 @@ fa:
field_default_issue_query: جستار پیش‌فرض مسئله‌ها
field_default_project_query: جستار پیش‌فرض پروژه
field_default_time_entry_activity: دسته‌بندی پیش‌فرض زمان صرف شده
+ field_any_searchable: هر متن قابل جستجویی
+ field_estimated_remaining_hours: باقی‌مانده زمان برآورد شده
+ field_last_activity_date: آخرین فعالیت
+ field_thousands_delimiter: جداکننده هزارگان
setting_app_title: عنوان برنامه
setting_welcome_text: متن خوش‌آمد گویی
@@ -478,6 +481,7 @@ fa:
setting_default_projects_modules: ابزارهای پیش‌فرض فعال برای پروژه‌های جدید
setting_issue_done_ratio: برآورد اندازه انجام شده مسئله با
setting_issue_done_ratio_issue_field: محاسبه بر اساس درصد پیش‌رفت مسئله
+ setting_issue_done_ratio_interval: فاصله گزینه‌های درصد انجام
setting_issue_done_ratio_issue_status: محاسبه بر اساس وضعیت مسئله
setting_start_of_week: آغاز تقویم از
setting_rest_api_enabled: فعال‌سازی وب سرویس‌های REST
@@ -502,6 +506,7 @@ fa:
setting_force_default_language_for_anonymous: الزام زبان پیش فرض برای کاربران ناشناس
setting_force_default_language_for_loggedin: الزام زبان پیش‌فرض برای کاربران واردشده
setting_link_copied_issue: ارتباط مسائل در هنگام رونوشت
+ setting_copy_attachments_on_issue_copy: رونوشت پیوست‌ها در زمان رونوشت
setting_max_additional_emails: بیشینه تعداد نشانی‌های رایانامه
setting_email_domains_allowed: دامنه‌های مجاز برای نشانی رایانامه
setting_email_domains_denied: دامنه‌های غیرمجاز برای نشانی رایانامه
@@ -525,6 +530,7 @@ fa:
permission_edit_project: ویرایش پروژه
permission_close_project: بستن / بازگشایی پروژه
permission_delete_project: حذف پروژه
+ permission_select_project_publicity: تنظیم پروژه به عمومی یا محرمانه
permission_select_project_modules: انتخاب ابزارهای پروژه
permission_manage_members: مدیریت اعضا
permission_manage_project_activities: مدیریت دسته‌بندی زمان‌های پروژه
@@ -631,6 +637,7 @@ fa:
label_issue_assigned_to_updated: مسئول به‌روز شد
label_issue_priority_updated: اولویت به‌روز شد
label_issue_fixed_version_updated: نسخه هدف به‌روز شد
+ label_issue_attachment_added: پیوست اضافه شد
label_document: سند
label_document_new: سند جدید
label_document_plural: اسناد
@@ -712,6 +719,10 @@ fa:
label_attachment_plural: پیوست‌ها
label_file_added: پرونده افزوده شد
label_attachment_description: توضیحات پیوست
+ label_attachment_summary:
+ zero: "%{filename}"
+ one: "%{filename} و 1 پرونده"
+ other: "%{filename} و %{count} پرونده"
label_report: گزارش
label_report_plural: گزارش‌ها
label_news: مطلب
@@ -811,6 +822,7 @@ fa:
label_more_than_ago: بعد از چند روز پیش
label_ago: روز قبل
label_contains: شامل
+ label_contains_any_of: شامل یکی از
label_not_contains: فاقد
label_starts_with: شروع با
label_ends_with: پایان با
@@ -819,6 +831,9 @@ fa:
label_no_issues_in_project: مسئله‌ای در پروژه وجود ندارد
label_any_open_issues: هر مسئله‌ی باز
label_no_open_issues: بدون هیچ مسئله باز
+ label_has_been: بوده است
+ label_has_never_been: هرگز نبوده است
+ label_changed_from: تغییر کرده از
label_day_plural: روز
label_repository: مخزن
label_repository_new: مخزن جدید
@@ -838,6 +853,7 @@ fa:
label_latest_revision_plural: آخرین بازبینی‌ها
label_view_revisions: مشاهده بازبینی‌ها
label_view_all_revisions: مشاهده همه بازبینی‌ها
+ label_view_previous_annotation: مشاهده حاشیه‌نویسی قبل از این تغییر
label_x_revisions: "%{count} بازبینی"
label_max_size: بیشترین اندازه
label_roadmap: نقشه راه
@@ -963,6 +979,7 @@ fa:
label_optional_description: توضیح اختیاری
label_add_another_file: افزودن پرونده دیگر
label_auto_watch_on: ناظر خودکار
+ label_auto_watch_on_issue_created: مسئله‌هایی که من ساخته‌ام
label_auto_watch_on_issue_contributed_to: مسئله‌هایی که در آن‌ها مشارکت داشته‌ام
label_preferences: ترجیح‌ها
label_chronological_order: به ترتیب تاریخ
@@ -1115,6 +1132,8 @@ fa:
label_inherited_from_group: "ارث‌بری از گروه %{name}"
label_trackers_description: توضیحات انواع مسئله
label_open_trackers_description: نمایش توضیحات تمامی انواع مسئله
+ label_issue_statuses_description: توضیحات وضعیت‌های مسئله
+ label_open_issue_statuses_description: دیدن توضیحات همه وضعیت‌های مسئله‌ها
label_preferred_body_part_text: متن
label_preferred_body_part_html: HTML
label_issue_history_properties: تغییرات بخش‌ها
@@ -1132,6 +1151,7 @@ fa:
label_default_query: جستار پیش‌فرض
label_edited: ویرایش شده
label_time_by_author: "%{time} توسط %{author}"
+ label_involved_principals: نویسنده / مسئول قبلی
button_login: ورود
button_submit: ثبت
@@ -1197,6 +1217,7 @@ fa:
button_edit_object: ویرایش %{object_name}
button_delete_object: حذف %{object_name}
button_create_and_follow: ساخت و برگشت
+ button_apply_issues_filter: اعمال غربال مسئله‌ها
status_active: فعال
status_registered: ثبت‌نام‌شده
@@ -1273,6 +1294,7 @@ fa:
text_minimagick_available: MiniMagick در دست‌رس است (اختیاری)
text_convert_available: تبدیل ImageMagick در دست‌رس است (اختیاری)
text_gs_available: پشتیبانی ImageMagick PDF موجود است (اختیاری)
+ text_default_active_job_queue_changed: آداپتور پیش‌فرض صف که به شکل مناسبی کار می‌کند تنها برای توسعه/آزمون تغییر کرد
text_destroy_time_entries_question: "روی مسئله‌هایی که می‌خواهید حذف کنید، %{hours} ساعت زمان ثبت شده است. می‌خواهید چه‌کار کنید؟"
text_destroy_time_entries: ساعت‌های ثبت شده حذف شوند
text_assign_time_entries_to_project: ساعت‌های ثبت شده به پروژه واگذار شوند
@@ -1309,11 +1331,11 @@ fa:
text_project_closed: این پروژه بسته و فقط‌خواندنی است.
text_turning_multiple_off: "اگر این گزینه را غیرفعال کنید، در بخش‌های سفارشیِ ذخیره‌شده، فقط یک مقدار باقی خواهد ماند و بقیه موارد حذف خواهند شد."
text_select_apply_tracker: "انتخاب نوع مسئله"
+ text_select_apply_issue_status: "وضعیت مسئله را انتخا کنید"
text_avatar_server_config_html: خادم فعلی آواتار <a href="%{url}">%{url}</a> می‌باشد. شما می‌توانید آن را در config/configuration.yml تغییر دهید.
text_no_subject: بدون موضوع
text_allowed_queries_to_select: تنها جستارهای عمومی (برای همه کاربران) قابل انتخاب است
- text_setting_config_change: می‌توانید رفتار را در config/configuration.yml ویرایش کنید.
- لطفا پس از تغییر، برنامه را دوباره اجرا کنید.
+ text_setting_config_change: می‌توانید رفتار را در config/configuration.yml ویرایش کنید. لطفا پس از تغییر، برنامه را دوباره اجرا کنید.
default_role_manager: مدیر
default_role_developer: برنامه‌نویس
@@ -1364,22 +1386,18 @@ fa:
label_import_time_entries: ورود زمان‌ها
label_import_users: وارد کردن فهرست کاربران
sudo_mode_new_info_html: "<strong>چه اتفاقی می‌افتد؟</strong> قبل از انجام هرگونه اقداماتِ راه‌بری باید گذرواژه خود را دوباره وارد کنید، این کار تضمین می‌کند که حساب شما محافظت شود."
+
twofa__totp__name: برنامک احراز هویت
- twofa__totp__text_pairing_info_html: لطفا کد QR را را در یک برنامه TOTP اسکن کنید یا کلید متنی را در آن وارد کنید (به عنوان مثال <a href="https://support.google.com/accounts/answer/1066447"> Google
- Authenticator </a>، <a href="https://authy.com/download/"> Authy </a>، <a href = "https://guide.duo.com/third-party-accounts" > Duo
- Mobile </a>) سپس کدی که برنامه در اختیار شما می‌گذارد را در قسمت زیر وارد نمایید تا احراز هویت دو مرحله ای فعال شود.
+ twofa__totp__text_pairing_info_html: لطفا کد QR را را در یک برنامه TOTP اسکن کنید یا کلید متنی را در آن وارد کنید (به عنوان مثال <a href="https://support.google.com/accounts/answer/1066447"> Google Authenticator </a>، <a href="https://authy.com/download/"> Authy </a>، <a href = "https://guide.duo.com/third-party-accounts" > Duo Mobile </a>) سپس کدی که برنامه در اختیار شما می‌گذارد را در قسمت زیر وارد نمایید تا احراز هویت دو مرحله ای فعال شود.
twofa__totp__label_plain_text_key: کلید متنی
twofa__totp__label_activate: فعال‌سازی برنامک احراز هویت
twofa_currently_active: 'فعال: %{twofa_scheme_name}'
twofa_not_active: فعال نشده است
twofa_label_code: کد
twofa_hint_disabled_html: تنظیم <strong>%{label}</strong> احراز هویت دو عاملی را غیرفعال کرده و اتصال همه دستگاه‌های تمامی کاربران را قطع می‌کند.
- twofa_hint_optional_html: تنظیم <strong>%{label}</strong> به کاربران اجازه می‌دهد
- احراز هویت دوعاملی را در صورت تمایل فعال کنند، مگر این‌که توسط یکی از گروه‌های کاربری الزامی شده باشد.
+ twofa_hint_optional_html: تنظیم <strong>%{label}</strong> به کاربران اجازه می‌دهد احراز هویت دوعاملی را در صورت تمایل فعال کنند، مگر این‌که توسط یکی از گروه‌های کاربری الزامی شده باشد.
twofa_hint_required_html: تنظیم <strong>%{label}</strong> تمامی کاربران را ملزم می‌کند تا احراز هویت دو عاملی را در اولین ورود بعدی فعال کنند.
- twofa_hint_required_administrators_html: تنظیم <strong>%{label}</strong> شبیه
- احراز هویت دوعاملی اختیاری عمل می‌کند, اما برای راه‌برها احراز هویت دوعاملی را
- در اولین ورود بعدی الزامی می‌کند.
+ twofa_hint_required_administrators_html: تنظیم <strong>%{label}</strong> شبیه احراز هویت دوعاملی اختیاری عمل می‌کند, اما برای راه‌برها احراز هویت دوعاملی را در اولین ورود بعدی الزامی می‌کند.
twofa_label_setup: فعال‌سازی احراز هویت دو عاملی
twofa_label_deactivation_confirmation: غیرفعال‌سازی احراز هویت دو عاملی
twofa_notice_select: 'لطفاً طرح دو عاملی را که می خواهید استفاده کنید انتخاب کنید:'
@@ -1407,28 +1425,4 @@ fa:
twofa_text_group_disabled: "این تنظیم تنها زمانی مؤثر است که احراز هویت دوعاملی روی «اختیاری» تنظیم شده باشد. در حال حاضر، احراز هویت دوعاملی غیرفعال است."
text_user_destroy_confirmation: مطمئنید که می خواهید این کاربر و همه ارجاع‌های مربوط به او را حذف کنید؟ این کار قابل بازگشت نیست. غالبا، قفل کردن کاربر به جای حذف، راه حل به‌تری است. برای تأیید، لطفاً شناسه ورود کاربر (%{login}) را در زیر وارد کنید.
text_project_destroy_enter_identifier: برای تایید، لطفا شناسه پروژه (%{identifier}) را وارد کنید.
- permission_select_project_publicity: تنظیم عمومی یا خصوصی بودن پروژه
- label_auto_watch_on_issue_created: مسئله‌هایی که من ساخته‌ام
- field_any_searchable: هر متن قابل جستجویی
- label_contains_any_of: شامل یکی از
- button_apply_issues_filter: اعمال غربال مسئله‌ها
- label_view_previous_annotation: مشاهده حاشیه‌نویسی قبل از این تغییر
- label_has_been: بوده است
- label_has_never_been: نبوده است
- label_changed_from: تغییر یافته از
- label_issue_statuses_description: توضیحات وضعیت‌های مسئله
- label_open_issue_statuses_description: مشاهده توضیحات همه وضعیت‌های مسئله‌ها
- text_select_apply_issue_status: وضعیت مسئله را انتخاب کنید
field_name_or_email_or_login: نام، رایانامه یا شناسه کاربری
- text_default_active_job_queue_changed: آداپتور پیش‌فرض صف که به شکل مناسبی کار می‌کند تنها برای توسعه/آزمون تغییر کرد
- label_issue_attachment_added: Attachment added
- field_estimated_remaining_hours: Estimated remaining time
- field_last_activity_date: Last activity
- setting_issue_done_ratio_interval: Done ratio options interval
- setting_copy_attachments_on_issue_copy: Copy attachments on copy
- field_thousands_delimiter: Thousands delimiter
- label_involved_principals: Author / Previous assignee
- label_attachment_summary:
- zero: "%{filename}"
- one: "%{filename} and 1 file"
- other: "%{filename} and %{count} files"
diff --git a/config/locales/sv.yml b/config/locales/sv.yml
index 949435b00..5c92e8f15 100644
--- a/config/locales/sv.yml
+++ b/config/locales/sv.yml
@@ -2,6 +2,7 @@
# by Johan Lundström (johanlunds@gmail.com),
# with parts taken from http://github.com/daniel/swe_rails
# Update based on Redmine 2.6.0 by Khedron Wilk (khedron.wilk@gmail.com) 6th Dec 2014
+# Update based on Redmine 6.0.1 by Jimmy 'Grovkillen' Westberg (jimmy@grovkillen.com) 5th Dec 2024
sv:
number:
# Used in number_with_delimiter()
@@ -135,14 +136,14 @@ sv:
circular_dependency: "Denna relation skulle skapa ett cirkulärt beroende"
cant_link_an_issue_with_a_descendant: "Ett ärende kan inte länkas till ett av dess underärenden"
earlier_than_minimum_start_date: "kan inte vara tidigare än %{date} på grund av föregående ärenden"
- not_a_regexp: "is not a valid regular expression"
- open_issue_with_closed_parent: "An open issue cannot be attached to a closed parent task"
- must_contain_uppercase: "must contain uppercase letters (A-Z)"
- must_contain_lowercase: "must contain lowercase letters (a-z)"
- must_contain_digits: "must contain digits (0-9)"
- must_contain_special_chars: "must contain special characters (!, $, %, ...)"
- domain_not_allowed: "contains a domain not allowed (%{domain})"
- too_simple: "is too simple"
+ not_a_regexp: "är inte korrekt formaterad RegEx"
+ open_issue_with_closed_parent: "Ett öppet ärende kan inte kopplas till ett stängt föräldraärende"
+ must_contain_uppercase: "måste innehålla versaler (A-Z)"
+ must_contain_lowercase: "måste innehålla gemener (a-z)"
+ must_contain_digits: "måste innehålla siffror (0-9)"
+ must_contain_special_chars: "måste innehålla specialtecken (!, $, %, ...)"
+ domain_not_allowed: "består av en ej godkänd domän (%{domain})"
+ too_simple: "är för simpel"
direction: ltr
date:
@@ -216,16 +217,16 @@ sv:
notice_feeds_access_key_reseted: Din Atom-nyckel återställdes.
notice_api_access_key_reseted: Din API-nyckel återställdes.
notice_failed_to_save_issues: "Misslyckades med att spara %{count} ärende(n) på %{total} valda: %{ids}."
- notice_failed_to_save_time_entries: "Misslyckades med att spara %{count} tidloggning(ar) på %{total} valda: %{ids}."
+ notice_failed_to_save_time_entries: "Misslyckades med att spara %{count} tidsstämpling(ar) på %{total} valda: %{ids}."
notice_failed_to_save_members: "Misslyckades med att spara medlem(mar): %{errors}."
notice_account_pending: "Ditt konto skapades och avvaktar nu administratörens godkännande."
notice_default_data_loaded: Standardkonfiguration inläst.
notice_unable_delete_version: Denna version var inte möjlig att ta bort.
- notice_unable_delete_time_entry: Tidloggning kunde inte tas bort.
+ notice_unable_delete_time_entry: Tidsstämplingen kunde inte tas bort.
notice_issue_done_ratios_updated: "% klart uppdaterade."
notice_gantt_chart_truncated: "Schemat förminskades eftersom det överskrider det maximala antalet aktiviteter som kan visas (%{max})"
notice_issue_successful_create: "Ärende %{id} skapades."
- notice_issue_update_conflict: "Detta ärende har uppdaterats av en annan användare samtidigt som du redigerade det."
+ notice_issue_update_conflict: "Detta ärende har uppdaterats av en annan användare under tiden du redigerade det."
notice_account_deleted: "Ditt konto har avslutats permanent."
notice_user_successful_create: "Användare %{id} skapad."
@@ -236,9 +237,9 @@ sv:
error_scm_annotate_big_text_file: Inlägget kan inte annoteras eftersom det överskrider maximal storlek för textfiler.
error_issue_not_found_in_project: 'Ärendet hittades inte eller så tillhör det inte detta projekt'
error_no_tracker_in_project: 'Ingen ärendetyp är associerad med projektet. Vänligen kontrollera projektinställningarna.'
- error_no_default_issue_status: 'Ingen status är definierad som standard för nya ärenden. Vänligen kontrollera din konfiguration (Gå till "Administration -> Ärendestatus").'
- error_can_not_delete_custom_field: Kan inte ta bort användardefinerat fält
- error_can_not_delete_tracker_html: "Det finns ärenden av denna typ och den är därför inte möjlig att ta bort.<p>The following projects have issues with this tracker:<br>%{projects}</p>"
+ error_no_default_issue_status: 'Ingen status är definierad som standard för nya ärenden. Vänligen kontrollera din konfiguration (gå till "Administration -> Ärendestatus").'
+ error_can_not_delete_custom_field: Kan inte ta bort anpassat fält
+ error_can_not_delete_tracker_html: "Det finns ärenden av denna typ och den är därför inte möjlig att ta bort.<p>Följande projekt har ärenden kopplad till denna ärendetyp:<br>%{projects}</p>"
error_can_not_remove_role: "Denna roll används och den är därför inte möjlig att ta bort."
error_can_not_reopen_issue_on_closed_version: 'Ett ärende tilldelat en stängd version kan inte öppnas på nytt'
error_can_not_archive_project: Detta projekt kan inte arkiveras
@@ -266,7 +267,6 @@ sv:
mail_subject_wiki_content_updated: "'%{id}' wikisida har uppdaterats"
mail_body_wiki_content_updated: "The '%{id}' wikisida har uppdaterats av %{author}."
-
field_name: Namn
field_description: Beskrivning
field_summary: Sammanfattning
@@ -277,14 +277,14 @@ sv:
field_filename: Fil
field_filesize: Storlek
field_downloads: Nerladdningar
- field_author: Författare
+ field_author: Skapare
field_created_on: Skapad
field_updated_on: Uppdaterad
field_closed_on: Stängd
field_field_format: Format
field_is_for_all: För alla projekt
field_possible_values: Möjliga värden
- field_regexp: Reguljärt uttryck
+ field_regexp: Reguljärt uttryck (RegEx)
field_min_length: Minimilängd
field_max_length: Maxlängd
field_value: Värde
@@ -299,18 +299,18 @@ sv:
field_tracker: Ärendetyp
field_subject: Ämne
field_due_date: Deadline
- field_assigned_to: Tilldelad till
+ field_assigned_to: Tilldelad
field_priority: Prioritet
field_fixed_version: Versionsmål
field_user: Användare
- field_principal: User or Group
+ field_principal: Användare eller grupp
field_role: Roll
field_homepage: Hemsida
field_is_public: Publik
field_parent: Underprojekt till
field_is_in_roadmap: Visa ärenden i roadmap
field_login: Användarnamn
- field_mail_notification: Mailnotifieringar
+ field_mail_notification: E-postaviseringar
field_admin: Administratör
field_last_login_on: Senaste inloggning
field_language: Språk
@@ -333,7 +333,7 @@ sv:
field_done_ratio: "% Klart"
field_auth_source: Autentiseringsläge
field_hide_mail: Dölj min mailadress
- field_comments: Kommentar
+ field_comments: Kommentarer
field_url: URL
field_start_page: Startsida
field_subproject: Underprojekt
@@ -346,7 +346,7 @@ sv:
field_delay: Fördröjning
field_assignable: Ärenden kan tilldelas denna roll
field_redirect_existing_links: Omdirigera existerande länkar
- field_estimated_hours: Estimerad tid
+ field_estimated_hours: Beräknad tid
field_column_names: Kolumner
field_time_entries: Spenderad tid
field_time_zone: Tidszon
@@ -398,7 +398,7 @@ sv:
setting_feeds_limit: Innehållsgräns för Feed
setting_default_projects_public: Nya projekt är publika
setting_autofetch_changesets: Automatisk hämtning av commits
- setting_sys_api_enabled: Aktivera WS för versionsarkivhantering
+ setting_sys_api_enabled: Aktivera API för versionsarkivhantering
setting_commit_ref_keywords: Referens-nyckelord
setting_commit_fix_keywords: Fix-nyckelord
setting_autologin: Automatisk inloggning
@@ -408,7 +408,7 @@ sv:
setting_cross_project_issue_relations: Tillåt ärenderelationer mellan projekt
setting_issue_list_default_columns: Standardkolumner i ärendelistan
setting_repositories_encodings: Encoding för bilagor och versionsarkiv
- setting_emails_header: Mail-header
+ setting_emails_header: Inledning
setting_emails_footer: Signatur
setting_protocol: Protokoll
setting_per_page_options: Alternativ, objekt per sida
@@ -417,7 +417,7 @@ sv:
setting_display_subprojects_issues: Visa ärenden från underprojekt i huvudprojekt
setting_enabled_scm: Aktivera SCM
setting_mail_handler_body_delimiters: "Trunkera mail efter en av följande rader"
- setting_mail_handler_api_enabled: Aktivera WS för inkommande mail
+ setting_mail_handler_api_enabled: Aktivera API för inkommande mail
setting_mail_handler_api_key: API-nyckel
setting_sequential_project_identifiers: Generera projektidentifierare sekventiellt
setting_gravatar_enabled: Använd Gravatar-avatarer
@@ -428,21 +428,21 @@ sv:
setting_password_min_length: Minsta tillåtna lösenordslängd
setting_new_project_user_role_id: Tilldelad roll för en icke-administratör som skapar ett projekt
setting_default_projects_modules: Aktiverade moduler för nya projekt
- setting_issue_done_ratio: Beräkna % klart med
+ setting_issue_done_ratio: Beräkna "% Klart" med
setting_issue_done_ratio_issue_field: Använd ärendefältet
setting_issue_done_ratio_issue_status: Använd ärendestatus
setting_start_of_week: Första dagen i veckan
- setting_rest_api_enabled: Aktivera REST webbtjänst
- setting_cache_formatted_text: Cacha formaterad text
- setting_default_notification_option: Standard notifieringsalternativ
- setting_commit_logtime_enabled: Aktivera tidloggning
- setting_commit_logtime_activity_id: Aktivitet för loggad tid
- setting_gantt_items_limit: Maximalt antal aktiviteter som visas i gantt-schemat
- setting_issue_group_assignment: Tillåt att ärenden tilldelas till grupper
+ setting_rest_api_enabled: Aktivera REST-API
+ setting_cache_formatted_text: Förladda formaterad text
+ setting_default_notification_option: Standardaviseringsalternativ
+ setting_commit_logtime_enabled: Aktivera tidsstämpling
+ setting_commit_logtime_activity_id: Aktivitet för stämplad tid
+ setting_gantt_items_limit: Maximalt antal aktiviteter som visas i Gantt-schemat
+ setting_issue_group_assignment: Tillåt att ärenden tilldelas grupper
setting_default_issue_start_date_to_creation_date: Använd dagens datum som startdatum för nya ärenden
- setting_commit_cross_project_ref: Tillåt ärende i alla de andra projekten att bli refererade och fixade
+ setting_commit_cross_project_ref: Tillåt ärenden i alla projekt att refereras och åtgärdas
setting_unsubscribe: Tillåt användare att avsluta prenumereration
- setting_session_lifetime: Maximal sessionslivslängd
+ setting_session_lifetime: Maximal sessionslängd
setting_session_timeout: Tidsgräns för sessionsinaktivitet
setting_thumbnails_enabled: Visa miniatyrbilder av bilagor
setting_thumbnails_size: Storlek på miniatyrbilder (i pixlar)
@@ -471,17 +471,17 @@ sv:
permission_view_private_notes: Visa privata anteckningar
permission_set_notes_private: Ställa in anteckningar som privata
permission_delete_issues: Ta bort ärenden
- permission_manage_public_queries: Hantera publika frågor
- permission_save_queries: Spara frågor
+ permission_manage_public_queries: Hantera publika filtreringar
+ permission_save_queries: Spara filtreringar
permission_view_gantt: Visa Gantt-schema
permission_view_calendar: Visa kalender
permission_view_issue_watchers: Visa bevakarlista
permission_add_issue_watchers: Lägga till bevakare
permission_delete_issue_watchers: Ta bort bevakare
- permission_log_time: Logga spenderad tid
+ permission_log_time: Stämpla spenderad tid
permission_view_time_entries: Visa spenderad tid
- permission_edit_time_entries: Ändra tidloggningar
- permission_edit_own_time_entries: Ändra egna tidloggningar
+ permission_edit_time_entries: Ändra tidsstämplingar
+ permission_edit_own_time_entries: Ändra egna tidsstämplingar
permission_manage_news: Hantera nyheter
permission_comment_news: Kommentera nyheter
permission_view_documents: Visa dokument
@@ -500,7 +500,7 @@ sv:
permission_protect_wiki_pages: Skydda wikisidor
permission_manage_repository: Hantera versionsarkiv
permission_browse_repository: Bläddra i versionsarkiv
- permission_view_changesets: Visa changesets
+ permission_view_changesets: Visa kodändringar
permission_commit_access: Commit-åtkomst
permission_manage_boards: Hantera forum
permission_view_messages: Visa meddelanden
@@ -570,10 +570,10 @@ sv:
label_issue_category: Ärendekategori
label_issue_category_plural: Ärendekategorier
label_issue_category_new: Ny kategori
- label_custom_field: Användardefinerat fält
- label_custom_field_plural: Användardefinerade fält
- label_custom_field_new: Nytt användardefinerat fält
- label_enumerations: Uppräkningar
+ label_custom_field: Anpassat fält
+ label_custom_field_plural: Anpassade fält
+ label_custom_field_new: Nytt anpassat fält
+ label_enumerations: Värdelistor
label_enumeration_new: Nytt värde
label_information: Information
label_information_plural: Information
@@ -587,8 +587,8 @@ sv:
label_login: Logga in
label_logout: Logga ut
label_help: Hjälp
- label_reported_issues: Rapporterade ärenden
- label_assigned_to_me_issues: Ärenden tilldelade till mig
+ label_reported_issues: Skapade ärenden
+ label_assigned_to_me_issues: Ärenden tilldelade mig
label_registered_on: Registrerad
label_activity: Aktivitet
label_user_activity: "Aktiviteter för %{value}"
@@ -667,7 +667,7 @@ sv:
label_next: Nästa
label_previous: Föregående
label_used_by: Använd av
- label_details: Detaljer
+ label_details: Sammanfattning
label_add_note: Lägg till anteckning
label_calendar: Kalender
label_months_from: månader från
@@ -684,12 +684,12 @@ sv:
label_comment_add: Lägg till kommentar
label_comment_added: Kommentar tillagd
label_comment_delete: Ta bort kommentar
- label_query: Användardefinerad fråga
- label_query_plural: Användardefinerade frågor
- label_query_new: Ny fråga
- label_my_queries: Mina egna frågor
- label_filter_add: Lägg till filter
- label_filter_plural: Filter
+ label_query: Anpassad filtrering
+ label_query_plural: Anpassade filtreringar
+ label_query_new: Ny filtrering
+ label_my_queries: Mina egna filtreringar
+ label_filter_add: Lägg till filtreringsrad
+ label_filter_plural: Filtrering
label_equals: är
label_not_equals: är inte
label_in_less_than: om mindre än
@@ -738,7 +738,7 @@ sv:
label_view_revisions: Visa revisioner
label_view_all_revisions: Visa alla revisioner
label_max_size: Maxstorlek
- label_roadmap: Vägkarta
+ label_roadmap: Utvecklingsplan
label_roadmap_due_in: "Färdig om %{value}"
label_roadmap_overdue: "%{value} sen"
label_roadmap_no_issues: Inga ärenden för denna version
@@ -764,7 +764,7 @@ sv:
label_change_plural: Ändringar
label_statistics: Statistik
label_commits_per_month: Commits per månad
- label_commits_per_author: Commits per författare
+ label_commits_per_author: Commits per användare
label_diff: skillnader
label_view_diff: Visa skillnader
label_diff_inline: i texten
@@ -795,7 +795,7 @@ sv:
label_board_new: Nytt forum
label_board_plural: Forum
label_board_locked: Låst
- label_board_sticky: Klibbig
+ label_board_sticky: Fäst
label_topic_plural: Ämnen
label_message_plural: Meddelanden
label_message_last: Senaste meddelande
@@ -810,7 +810,7 @@ sv:
label_date_to: Till
label_language_based: Språkbaserad
label_sort_by: "Sortera på %{value}"
- label_send_test_email: Skicka testmail
+ label_send_test_email: Skicka ett testmejl
label_feeds_access_key: Atom-nyckel
label_missing_feeds_access_key: Saknar en Atom-nyckel
label_feeds_access_key_created_on: "Atom-nyckel skapad för %{value} sedan"
@@ -832,8 +832,8 @@ sv:
label_user_mail_option_selected: "För alla händelser i markerade projekt..."
label_user_mail_option_none: "Inga händelser"
label_user_mail_option_only_my_events: "Endast för saker jag bevakar eller är inblandad i"
- label_user_mail_no_self_notified: "Jag vill inte bli underrättad om ändringar som jag har gjort"
- label_registration_activation_by_email: kontoaktivering med mail
+ label_user_mail_no_self_notified: "Jag vill inte bli underrättad om ändringar som jag själv gjort"
+ label_registration_activation_by_email: kontoaktivering med e-post
label_registration_manual_activation: manuell kontoaktivering
label_registration_automatic_activation: automatisk kontoaktivering
label_display_per_page: "Per sida: %{value}"
@@ -843,13 +843,13 @@ sv:
label_scm: SCM
label_plugins: Tillägg
label_ldap_authentication: LDAP-autentisering
- label_downloads_abbr: Nerl.
+ label_downloads_abbr: Nedl.
label_optional_description: Valfri beskrivning
label_add_another_file: Lägg till ytterligare en fil
label_preferences: Användarinställningar
label_chronological_order: I kronologisk ordning
label_reverse_chronological_order: I omvänd kronologisk ordning
- label_incoming_emails: Inkommande mail
+ label_incoming_emails: Inkommande e-post
label_generate_key: Generera en nyckel
label_issue_watchers: Bevakare
label_example: Exempel
@@ -879,14 +879,14 @@ sv:
label_api_access_key_created_on: "API-nyckel skapad för %{value} sedan"
label_profile: Profil
label_subtask_plural: Underaktiviteter
- label_project_copy_notifications: Skicka mailnotifieringar när projektet kopieras
+ label_project_copy_notifications: Skicka e-postaviseringar när projektet kopieras
label_principal_search: "Sök efter användare eller grupp:"
label_user_search: "Sök efter användare:"
label_additional_workflow_transitions_for_author: Ytterligare övergångar tillåtna när användaren är den som skapat ärendet
label_additional_workflow_transitions_for_assignee: Ytterligare övergångar tillåtna när användaren är den som tilldelats ärendet
label_issues_visibility_all: Alla ärenden
label_issues_visibility_public: Alla icke-privata ärenden
- label_issues_visibility_own: Ärenden skapade av eller tilldelade till användaren
+ label_issues_visibility_own: Ärenden skapade av eller tilldelade användaren
label_git_report_last_commit: Rapportera senaste commit av filer och mappar
label_parent_revision: Förälder
label_child_revision: Barn
@@ -903,7 +903,7 @@ sv:
label_required: Nödvändig
label_attribute_of_project: Projektets %{name}
label_attribute_of_issue: Ärendets %{name}
- label_attribute_of_author: Författarens %{name}
+ label_attribute_of_author: Skaparens %{name}
label_attribute_of_assigned_to: Tilldelad användares %{name}
label_attribute_of_user: Användarens %{name}
label_attribute_of_fixed_version: Målversionens %{name}
@@ -922,7 +922,7 @@ sv:
button_expand_all: Expandera alla
button_delete: Ta bort
button_create: Skapa
- button_create_and_continue: Skapa och fortsätt
+ button_create_and_continue: Skapa och ny
button_test: Testa
button_edit: Ändra
button_edit_associated_wikipage: "Ändra associerad Wikisida: %{page_title}"
@@ -936,15 +936,15 @@ sv:
button_list: Lista
button_view: Visa
button_move: Flytta
- button_move_and_follow: Flytta och följ efter
+ button_move_and_follow: Flytta och följ
button_back: Tillbaka
button_cancel: Avbryt
button_activate: Aktivera
button_sort: Sortera
- button_log_time: Logga tid
+ button_log_time: Stämpla tid
button_rollback: Återställ till denna version
button_watch: Bevaka
- button_unwatch: Stoppa bevakning
+ button_unwatch: Ta bort bevakning
button_reply: Svara
button_archive: Arkivera
button_unarchive: Ta bort från arkiv
@@ -952,7 +952,7 @@ sv:
button_rename: Byt namn
button_change_password: Ändra lösenord
button_copy: Kopiera
- button_copy_and_follow: Kopiera och följ efter
+ button_copy_and_follow: Kopiera och följ
button_annotate: Kommentera
button_update: Uppdatera
button_configure: Konfigurera
@@ -979,12 +979,12 @@ sv:
field_active: Aktiv
- text_select_mail_notifications: Välj för vilka händelser mail ska skickas.
- text_regexp_info: eg. ^[A-Z0-9]+$
+ text_select_mail_notifications: Välj för vilka händelser e-post ska skickas.
+ text_regexp_info: t.ex. ^[A-Z0-9]+$
text_project_destroy_confirmation: Är du säker på att du vill ta bort detta projekt och all relaterad data?
text_subprojects_destroy_warning: "Alla underprojekt: %{value} kommer också tas bort."
text_workflow_edit: Välj en roll och en ärendetyp för att ändra arbetsflöde
- text_are_you_sure: Är du säker ?
+ text_are_you_sure: Är du säker?
text_journal_changed: "%{label} ändrad från %{old} till %{new}"
text_journal_changed_no_detail: "%{label} uppdaterad"
text_journal_set_to: "%{label} satt till %{value}"
@@ -1004,8 +1004,8 @@ sv:
text_issues_ref_in_commit_messages: Referera och fixa ärenden i commit-meddelanden
text_issue_added: "Ärende %{id} har rapporterats (av %{author})."
text_issue_updated: "Ärende %{id} har uppdaterats (av %{author})."
- text_wiki_destroy_confirmation: Är du säker på att du vill ta bort denna wiki och allt dess innehåll ?
- text_issue_category_destroy_question: "Några ärenden (%{count}) är tilldelade till denna kategori. Vad vill du göra ?"
+ text_wiki_destroy_confirmation: Är du säker på att du vill ta bort denna wiki och allt dess innehåll?
+ text_issue_category_destroy_question: "Några ärenden (%{count}) är tilldelade denna kategori. Vad vill du göra?"
text_issue_category_destroy_assignments: Ta bort kategoritilldelningar
text_issue_category_reassign_to: Återtilldela ärenden till denna kategori
text_user_mail_option: "För omarkerade projekt kommer du bara bli underrättad om saker du bevakar eller är inblandad i (T.ex. ärenden du skapat eller tilldelats)."
@@ -1013,25 +1013,25 @@ sv:
text_load_default_configuration: Läs in standardkonfiguration
text_status_changed_by_changeset: "Tilldelad i changeset %{value}."
text_time_logged_by_changeset: "Tilldelad i changeset %{value}."
- text_issues_destroy_confirmation: 'Är du säker på att du vill radera markerade ärende(n) ?'
+ text_issues_destroy_confirmation: 'Är du säker på att du vill radera markerade ärende(n)?'
text_issues_destroy_descendants_confirmation: Detta kommer även ta bort %{count} underaktivitet(er).
text_time_entries_destroy_confirmation: Är du säker på att du vill ta bort valda tidloggningar?
text_select_project_modules: 'Välj vilka moduler som ska vara aktiva för projektet:'
text_default_administrator_account_changed: Standardadministratörens konto ändrat
text_file_repository_writable: Arkivet för bifogade filer är skrivbart
- text_plugin_assets_writable: Arkivet för plug-ins är skrivbart
+ text_plugin_assets_writable: Arkivet för tillägg är skrivbart
text_minimagick_available: MiniMagick tillgängligt (ej obligatoriskt)
- text_destroy_time_entries_question: "%{hours} timmar har rapporterats på ärendena du är på väg att ta bort. Vad vill du göra ?"
+ text_destroy_time_entries_question: "%{hours} timmar har rapporterats på ärendena du är på väg att ta bort. Vad vill du göra?"
text_destroy_time_entries: Ta bort rapporterade timmar
text_assign_time_entries_to_project: Tilldela rapporterade timmar till projektet
text_reassign_time_entries: 'Återtilldela rapporterade timmar till detta ärende:'
text_user_wrote: "%{value} skrev:"
text_user_wrote_in: "%{value} skrev (%{link}):"
- text_enumeration_destroy_question: "%{count} objekt är tilldelade till detta värde."
+ text_enumeration_destroy_question: "%{count} objekt är tilldelade detta värde."
text_enumeration_category_reassign_to: 'Återtilldela till detta värde:'
- text_email_delivery_not_configured: "Mailfunktionen har inte konfigurerats, och notifieringar via mail kan därför inte skickas.\nKonfigurera din SMTP-server i config/configuration.yml och starta om applikationen för att aktivera dem."
- text_repository_usernames_mapping: "Välj eller uppdatera den Redmine-användare som är mappad till varje användarnamn i versionarkivloggen.\nAnvändare med samma användarnamn eller mailadress i både Redmine och versionsarkivet mappas automatiskt."
- text_diff_truncated: '... Denna diff har förminskats eftersom den överskrider den maximala storlek som kan visas.'
+ text_email_delivery_not_configured: "E-postfunktionen har inte konfigurerats, och aviseringar via e-post kan därför inte skickas.\nKonfigurera din SMTP-server i <strong>config/configuration.yml</strong> och starta om applikationen för att aktivera dem."
+ text_repository_usernames_mapping: "Välj eller uppdatera den Redmine-användare som är mappad till varje användarnamn i versionarkivloggen.\nAnvändare med samma användarnamn eller e-postadress i både Redmine och versionsarkivet mappas automatiskt."
+ text_diff_truncated: '... Denna diff har kortats ner eftersom den överskrider den maximala storlek som kan visas.'
text_custom_field_possible_values_info: 'Ett värde per rad'
text_wiki_page_destroy_question: "Denna sida har %{descendants} underliggande sidor. Vad vill du göra?"
text_wiki_page_nullify_children: "Behåll undersidor som rotsidor"
@@ -1046,13 +1046,13 @@ sv:
text_mercurial_repository_note: Lokalt versionsarkiv (t.ex. /hgrepo, c:\hgrepo)
text_scm_command: Kommando
text_scm_command_version: Version
- text_scm_config: Du kan konfigurera dina scm-kommando i config/configuration.yml. Vänligen starta om applikationen när ändringar gjorts.
+ text_scm_config: Du kan konfigurera dina scm-kommando i <strong>config/configuration.yml</strong>. Vänligen starta om applikationen när ändringar gjorts.
text_scm_command_not_available: Scm-kommando är inte tillgängligt. Vänligen kontrollera inställningarna i administratörspanelen.
text_issue_conflict_resolution_overwrite: "Använd mina ändringar i alla fall (tidigare anteckningar kommer behållas men några ändringar kan bli överskrivna)"
text_issue_conflict_resolution_add_notes: "Lägg till mina anteckningar och kasta mina andra ändringar"
text_issue_conflict_resolution_cancel: "Kasta alla mina ändringar och visa igen %{link}"
text_account_destroy_confirmation: "Är du säker på att du vill fortsätta?\nDitt konto kommer tas bort permanent, utan möjlighet att återaktivera det."
- text_session_expiration_settings: "Varning: ändring av dessa inställningar kan få alla nuvarande sessioner, inklusive din egen, att gå ut."
+ text_session_expiration_settings: "Varning: ändring av dessa inställningar kan få alla nuvarande sessioner, inklusive din egen, att avslutas."
text_project_closed: Detta projekt är stängt och skrivskyddat.
text_turning_multiple_off: "Om du inaktiverar möjligheten till flera värden kommer endast ett värde per objekt behållas."
@@ -1090,14 +1090,14 @@ sv:
description_message_content: Meddelandeinnehåll
description_query_sort_criteria_attribute: Sorteringsattribut
description_query_sort_criteria_direction: Sorteringsriktning
- description_user_mail_notification: Mailnotifieringsinställningar
- description_available_columns: Tillgängliga Kolumner
- description_selected_columns: Valda Kolumner
+ description_user_mail_notification: Inställningar för e-postaviseringar
+ description_available_columns: Tillgängliga kolumner
+ description_selected_columns: Valda kolumner
description_all_columns: Alla kolumner
description_issue_category_reassign: Välj ärendekategori
description_wiki_subpages_reassign: Välj ny föräldersida
text_repository_identifier_info: 'Endast gemener (a-z), siffror, streck och understreck är tillåtna.<br />När identifieraren sparats kan den inte ändras.'
- notice_account_not_activated_yet: Du har inte aktiverat ditt konto än. Om du vill få ett nytt aktiveringsbrev, <a href="%{url}"> klicka på denna länk </a>.
+ notice_account_not_activated_yet: Du har inte aktiverat ditt konto än. Om du vill få ett nytt aktiveringsbrev, <a href="%{url}">klicka på denna länk</a>.
notice_account_locked: Ditt konto är låst.
label_hidden: Dold
label_visibility_private: endast för mig
@@ -1109,21 +1109,21 @@ sv:
text_convert_available: ImageMagick-konvertering tillgänglig (valbart)
label_link: Länk
label_only: endast
- label_drop_down_list: droppmeny
+ label_drop_down_list: rullgardinsmeny
label_checkboxes: kryssrutor
label_link_values_to: Länka värden till URL
setting_force_default_language_for_anonymous: Lås till förvalt språk för anonyma användare
setting_force_default_language_for_loggedin: Lås till förvalt språk för inloggade användare
label_custom_field_select_type: Välj den typ av objekt som det anpassade fältet skall användas för
- label_issue_assigned_to_updated: Tilldelad har uppdaterats
+ label_issue_assigned_to_updated: Tilldelning har uppdaterats
label_check_for_updates: Leta efter uppdateringar
label_latest_compatible_version: Senaste kompatibla version
label_unknown_plugin: Okänt tillägg
label_radio_buttons: alternativknappar
label_group_anonymous: Anonyma användare
label_group_non_member: Icke-medlemsanvändare
- label_add_projects: Addera projekt
- field_default_status: Default-status
+ label_add_projects: Lägg till projekt
+ field_default_status: Standardstatus
text_subversion_repository_note: 'Exempel: file:///, http://, https://, svn://, svn+[tunnelscheme]://'
field_users_visibility: Användares synlighet
label_users_visibility_all: Alla aktiva användare
@@ -1137,17 +1137,16 @@ sv:
label_search_attachments_only: Sök endast bilagor
label_search_open_issues_only: Endast öppna ärenden
field_address: Epost
- setting_max_additional_emails: Max antal ytterligare epost-adresser
- label_email_address_plural: Epost
- label_email_address_add: Addera epostadress
- label_enable_notifications: Aktivera underrättelser
- label_disable_notifications: Avaktivera underrättelser
+ setting_max_additional_emails: Maximalt antal extra e-postadresser
+ label_email_address_plural: E-post
+ label_email_address_add: Addera e-postadress
+ label_enable_notifications: Aktivera aviseringar
+ label_disable_notifications: Avaktivera aviseringar
setting_search_results_per_page: Sökresultat per sida
label_blank_value: blank
permission_copy_issues: Kopiera ärenden
- error_password_expired: Ditt lösenord har gått ut eller administratören kräver att
- du ändrar det.
- field_time_entries_visibility: Synlighet för tidsloggar
+ error_password_expired: Ditt lösenord har gått ut eller administratören kräver att du ändrar det.
+ field_time_entries_visibility: Synlighet för tidsstämplingar
setting_password_max_age: Kräv lösenordsändring efter
label_parent_task_attributes: Attribut för föräldraaktiviteter
label_parent_task_attributes_derived: Kalkylerad från underaktiviteter
@@ -1162,8 +1161,7 @@ sv:
notice_import_finished: "%{count} artiklar har importerats"
notice_import_finished_with_errors: "%{count} av %{total} artiklar kunde inte importeras"
error_invalid_file_encoding: Filen är inte en %{encoding}-kodad fil
- error_invalid_csv_file_or_settings: Filen är inte en CSV-fil eller stämmer inte med
- inställningarna nedan (%{value})
+ error_invalid_csv_file_or_settings: Filen är inte en CSV-fil eller stämmer inte med inställningarna nedan (%{value})
error_can_not_read_import_file: Fel vid läsning av fil att importera
permission_import_issues: Importera ärenden
label_import_issues: Importera ärenden
@@ -1172,26 +1170,26 @@ sv:
label_fields_wrapper: Fältomslag
label_encoding: Kodning
label_comma_char: Komma
- label_semi_colon_char: Semicolon
- label_quote_char: Citationstecken
- label_double_quote_char: Dubbla citationstecken
- label_fields_mapping: Kartläggning av fält
+ label_semi_colon_char: Semikolon
+ label_quote_char: Citattecken
+ label_double_quote_char: Dubbla citattecken
+ label_fields_mapping: Mappning av fält
label_file_content_preview: Förhandsvisning av filinnehåll
label_create_missing_values: Skapa saknade värden
button_import: Importera
- field_total_estimated_hours: Totalt uppskattad tid
+ field_total_estimated_hours: Totalt beräknad tid
label_api: API
label_total_plural: Totaler
label_assigned_issues: Tilldelade ärenden
label_field_format_enumeration: Nyckel/värde-lista
label_f_hour_short: '%{value} h'
- field_default_version: Defaultversion
- error_attachment_extension_not_allowed: Otillåten utökning av bilaga %{extension}
- setting_attachment_extensions_allowed: Tillåtna utökningar
- setting_attachment_extensions_denied: Otillåtna utökningar
+ field_default_version: Standardversion
+ error_attachment_extension_not_allowed: Otillåten filtyp av bilaga %{extension}
+ setting_attachment_extensions_allowed: Tillåtna filtyper
+ setting_attachment_extensions_denied: Otillåtna filtyper
label_any_open_issues: några öppna ärenden
label_no_open_issues: inga öppna ärenden
- label_default_values_for_new_users: Defaultvärden för nya användare
+ label_default_values_for_new_users: Standardvärden för nya användare
error_ldap_bind_credentials: Ogiltigt LDAP konto/lösenord
setting_sys_api_key: API-nyckel
setting_lost_password: Glömt lösenord
@@ -1200,25 +1198,21 @@ sv:
mail_body_security_notification_change_to: ! '%{field} ändrades till %{value}.'
mail_body_security_notification_add: ! '%{field} %{value} las till.'
mail_body_security_notification_remove: ! '%{field} %{value} togs bort.'
- mail_body_security_notification_notify_enabled: Epostadress %{value} kommer att få
- underrättelser.
- mail_body_security_notification_notify_disabled: Epostadress %{value} får inga fler
- underrättelser.
+ mail_body_security_notification_notify_enabled: E-postadress %{value} kommer att få aviseringar.
+ mail_body_security_notification_notify_disabled: E-postadress %{value} får inga fler aviseringar.
mail_body_settings_updated: ! 'Följande inställningar ändrades:'
field_remote_ip: IP-adress
label_wiki_page_new: Ny wiki-sida
label_relations: Relationer
- button_filter: Filter
+ button_filter: Filtrering
mail_body_password_updated: Ditt lösenord är ändrat.
label_no_preview: Ingen förhandsvisning tillgänglig
- error_no_tracker_allowed_for_new_issue_in_project: Projektet har ingen spårare
- som du kan skapa ett ärende för
- label_tracker_all: All spårare
+ error_no_tracker_allowed_for_new_issue_in_project: Projektet har ingen ärendetyp som du kan skapa ett ärende för
+ label_tracker_all: All ärendetyper
label_new_project_issue_tab_enabled: Visa fliken "Nya ärenden"
setting_new_item_menu_tab: Projektmenyflik för att skapa nya objekt
- label_new_object_tab_enabled: Visa "+" droppmeny
- error_no_projects_with_tracker_allowed_for_new_issue: Inga projekt med spårare
- tillåtna för nya ärenden
+ label_new_object_tab_enabled: Visa "+" rullgardinsmenyn
+ error_no_projects_with_tracker_allowed_for_new_issue: Inga projekt med ärendetyper tillåtna för nya ärenden
field_textarea_font: Font som används för textytor
label_font_default: Default font
label_font_monospace: Monospaced font
@@ -1226,94 +1220,81 @@ sv:
setting_timespan_format: Format för tidsintervall
label_table_of_contents: Innehållsförteckning
setting_commit_logs_formatting: Använd textformatering för meddelanden
- setting_mail_handler_enable_regex: Aktivera reguljära uttryck
+ setting_mail_handler_enable_regex: Aktivera reguljära uttryck (RegEx)
error_move_of_child_not_possible: 'Underaktivitet %{child} kunde inte flyttas till det nya
projektet: %{errors}'
- error_cannot_reassign_time_entries_to_an_issue_about_to_be_deleted: Spenderad tid kan inte
- flyttas till ett ärende som är på väg att tas bort
- setting_timelog_required_fields: Nödvändiga fält för tidsangivelser
- label_attribute_of_object: '%{object_name}''s %{name}'
+ error_cannot_reassign_time_entries_to_an_issue_about_to_be_deleted: Spenderad tid kan inte flyttas till ett ärende som är på väg att tas bort
+ setting_timelog_required_fields: Nödvändiga fält för tidsstämplingar
+ label_attribute_of_object: '%{object_name}s %{name}'
label_user_mail_option_only_assigned: Bara för saker som jag följer eller är tilldelad
label_user_mail_option_only_owner: Bara för saker som jag följer eller äger
- warning_fields_cleared_on_bulk_edit: Ändringar kommer att orsaka automatiskt borttagande
- av värden från ett eller flera fält för de valda objekten
+ warning_fields_cleared_on_bulk_edit: Ändringar kommer att orsaka automatiskt borttagande av värden från ett eller flera fält för de valda objekten
field_updated_by: Uppdaterad av
field_last_updated_by: Senast uppdaterad av
field_full_width_layout: Layout med full bredd
label_last_notes: Senaste noteringar
field_digest: Checksumma
- field_default_assigned_to: Defaultmottagare
+ field_default_assigned_to: Standardmottagare
setting_show_custom_fields_on_registration: Visa anpassade fält vid registrering
permission_view_news: Visa nyheter
label_no_preview_alternative_html: Ingen förhandsvisning tillgänglig. %{link} filen istället.
- label_no_preview_download: Nerladdning
+ label_no_preview_download: Nedladdning
setting_close_duplicate_issues: Stäng duplicerade ärenden automatiskt
- error_exceeds_maximum_hours_per_day: Kan inte logga mer än %{max_hours} timmar
- samma dag (%{logged_hours} timmar har redan loggats)
- setting_time_entry_list_defaults: Defaults för tidsloggning
- setting_timelog_accept_0_hours: Acceptera tidsloggar med 0 timmar
- setting_timelog_max_hours_per_day: Max timmar som kan loggas per dag och användare
+ error_exceeds_maximum_hours_per_day: Kan inte stämpla mer än %{max_hours} timmar samma dag (%{logged_hours} timmar har redan stämplats)
+ setting_time_entry_list_defaults: Standard för tidsstämplingar
+ setting_timelog_accept_0_hours: Acceptera tidsstämplingar på 0 timmar
+ setting_timelog_max_hours_per_day: Max timmar som kan stämplas per dag och användare
label_x_revisions: "%{count} revisioner"
- error_can_not_delete_auth_source: Denna autenticeringsmetod är i bruk och kan inte
- tas bort.
+ error_can_not_delete_auth_source: Denna autentiseringsmetod är i bruk och kan inte tas bort.
button_actions: Handlingar
- mail_body_lost_password_validity: OBS att du endast kan ändra lösenordet en gång
- via denna länk.
- text_login_required_html: När autenticering inte krävs, är publika projekt och deras
- innehåll tillgängliga på nätverket. Du kan <a href="%{anonymous_role_path}"> ändra
- tillämpliga behörigheter</a>.
+ mail_body_lost_password_validity: Observera att du endast kan ändra lösenordet en gång via denna länk.
+ text_login_required_html: 'När autentisering inte krävs, är publika projekt och deras innehåll tillgängliga på nätverket. Du kan <a href="%{anonymous_role_path}"> ändra tillämpliga behörigheter</a>.'
label_login_required_yes: 'Ja'
label_login_required_no: Nej, tillåt anonym access till publika projekt
- text_project_is_public_non_member: Publika projekt och deras innehåll är tillgängliga
- för alla inloggade användare.
- text_project_is_public_anonymous: Publika projekt och deras innehåll är tillgängliga
- på nätverket.
+ text_project_is_public_non_member: Publika projekt och deras innehåll är tillgängliga för alla inloggade användare.
+ text_project_is_public_anonymous: Publika projekt och deras innehåll är tillgängliga på nätverket.
label_version_and_files: Versioner (%{count}) och filer
label_ldap: LDAP
label_ldaps_verify_none: LDAPS (utan certifikatkontroll)
label_ldaps_verify_peer: LDAPS
- label_ldaps_warning: Det rekommenderas att använda krypterad LDAPS-förbindelse med
- certifikatkontroll för att hindra manipulation av autenticeringsprocessen.
+ label_ldaps_warning: Det rekommenderas att använda krypterad LDAPS-koppling med certifikatkontroll för att hindra manipulation av autentiseringsprocessen.
label_nothing_to_preview: Ingenting att visa
error_token_expired: Lösenordslänken gäller inte längre, var god försök igen.
- error_spent_on_future_date: Kan inte logga tid på framtida datum
- setting_timelog_accept_future_dates: Acceptera tidsloggningar på framtida datum
+ error_spent_on_future_date: Kan inte stämpla tid på framtida datum
+ setting_timelog_accept_future_dates: Acceptera tidsstämplingar på framtida datum
label_delete_link_to_subtask: Ta bort relation
- error_not_allowed_to_log_time_for_other_users: Du får inte logga tid
- för andra användare
- permission_log_time_for_other_users: Logga spenderad tid för andra användare
+ error_not_allowed_to_log_time_for_other_users: Du får inte stämpla tid för andra användare
+ permission_log_time_for_other_users: Stämpla spenderad tid för andra användare
label_tomorrow: imorgon
label_next_week: nästa vecka
label_next_month: nästa månad
text_role_no_workflow: Inget arbetsflöde definierat för denna roll
- text_status_no_workflow: Ingen spårare använder denna status i arbetsflödet
- setting_mail_handler_preferred_body_part: Föredragen del av epost i flera delar(HTML)
- setting_show_status_changes_in_mail_subject: Visa statusändringar i ärendemailunderrättelsers
- ämne
+ text_status_no_workflow: Ingen arbetstyp använder denna status i arbetsflödet
+ setting_mail_handler_preferred_body_part: Föredragen del av epost i flera delar (HTML)
+ setting_show_status_changes_in_mail_subject: Visa statusändringar för ärenden i ämnet för e-postaviseringar
label_inherited_from_parent_project: Ärvd från föräldraprojekt
label_inherited_from_group: Ärvd från grupp%{name}
- label_trackers_description: Beskrivning för spårare
- label_open_trackers_description: Visa beskrivning för alla spårare
+ label_trackers_description: Beskrivning för ärendetyp
+ label_open_trackers_description: Visa beskrivning för alla ärendetyper
label_preferred_body_part_text: Text
label_preferred_body_part_html: HTML
field_parent_issue_subject: Ämne för föräldraärende
permission_edit_own_issues: Ändra egna ärenden
- text_select_apply_tracker: Välj spårare
- label_updated_issues: Updaterade ärenden
- text_avatar_server_config_html: Nuvarande avatar-server <a href="%{url}">%{url}</a>.
- Du kan konfigurera det i config/configuration.yml.
- setting_gantt_months_limit: Max antal månader på gantt-schemat
- permission_import_time_entries: Importera tidsangivelser
- label_import_notifications: Skicka underrättelser med epost under importen
- text_gs_available: ImageMagick PDF support tillgänglig (frivillig)
+ text_select_apply_tracker: Välj ärendetyp
+ label_updated_issues: Uppdaterade ärenden
+ text_avatar_server_config_html: 'Nuvarande avatar-server <a href="%{url}">%{url}</a>. Du kan konfigurera det i <strong>config/configuration.yml</strong>.'
+ setting_gantt_months_limit: Max antal månader på Gantt-schemat
+ permission_import_time_entries: Importera tidsstämplingar
+ label_import_notifications: Skicka aviseringar med e-post under importen
+ text_gs_available: ImageMagick PDF-support tillgänglig (frivillig)
field_recently_used_projects: Antal nyligen använda projekt
label_optgroup_bookmarks: Bokmärken
label_optgroup_recents: Nyligen använda
button_project_bookmark: Lägg till bokmärke
button_project_bookmark_delete: Ta bort bokmärke
- field_history_default_tab: Ärendes defaultflik för historik
+ field_history_default_tab: Ärendens standardflik för historik
label_issue_history_properties: Ändrade egenskaper
- label_issue_history_notes: Noter
+ label_issue_history_notes: Anteckningar
label_last_tab_visited: Senast besökta flik
field_unique_id: Unikt ID
text_no_subject: inget ämne
@@ -1323,211 +1304,162 @@ sv:
label_password_char_class_digits: siffror
label_password_char_class_special_chars: specialtecken
text_characters_must_contain: Måste innehålla %{character_classes}.
- label_starts_with: startar med
- label_ends_with: slutar med
+ label_starts_with: börjar på
+ label_ends_with: slutar på
label_issue_fixed_version_updated: Målversion uppdaterad
- setting_project_list_defaults: Projektlista defaults
+ setting_project_list_defaults: Projektlista standardinställningar
label_display_type: Visa resultat som
label_display_type_list: Lista
label_display_type_board: Anslagstavla
label_my_bookmarks: Mina bokmärken
- label_import_time_entries: Importera tidsangivelser
- notice_issue_not_closable_by_open_tasks: This issue cannot be closed because it has
- at least one open subtask.
- notice_issue_not_closable_by_blocking_issue: This issue cannot be closed because it
- is blocked by at least one open issue.
- notice_issue_not_reopenable_by_closed_parent_issue: This issue cannot be reopened
- because its parent issue is closed.
- error_attachments_too_many: This file cannot be uploaded because it exceeds the maximum
- number of files that can be attached simultaneously (%{max_number_of_files})
- error_bulk_download_size_too_big: These attachments cannot be bulk downloaded because
- the total file size exceeds the maximum allowed size (%{max_size})
- error_can_not_execute_macro_html: Error executing the <strong>%{name}</strong> macro
- (%{error})
- error_macro_does_not_accept_block: This macro does not accept a block of text
- error_childpages_macro_no_argument: With no argument, this macro can be called from
- wiki pages only
- error_circular_inclusion: Circular inclusion detected
- error_page_not_found: Page not found
- error_filename_required: Filename required
- error_invalid_size_parameter: Invalid size parameter
- error_attachment_not_found: Attachment %{name} not found
- field_passwd_changed_on: Password last changed
- field_toolbar_language_options: Code highlighting toolbar languages
- setting_bulk_download_max_size: Maximum total size for bulk download
- setting_email_domains_allowed: Allowed email domains
- setting_email_domains_denied: Disallowed email domains
- setting_twofa: Two-factor authentication
- permission_delete_project: Delete the project
- label_optional: optional
- label_user_mail_notify_about_high_priority_issues_html: Also notify me about issues
- with a priority of <em>%{prio}</em> or higher
- label_days_to_html: "%{days} days up to %{date}"
- label_required_lower: required
- label_download_all_attachments: Download all files
- label_relations_mapping: Relations mapping
- label_assign_to_me: Assign to me
- button_disable: Disable
- label_import_users: Import users
- twofa__totp__name: Authenticator app
- twofa__totp__text_pairing_info_html: Scan this QR code or enter the plain text key
- into a TOTP app (e.g. <a href="https://support.google.com/accounts/answer/1066447">Google
- Authenticator</a>, <a href="https://authy.com/download/">Authy</a>, <a href="https://guide.duo.com/third-party-accounts">Duo
- Mobile</a>) and enter the code in the field below to activate two-factor authentication.
- twofa__totp__label_plain_text_key: Plain text key
- twofa__totp__label_activate: Enable authenticator app
- twofa_currently_active: 'Currently active: %{twofa_scheme_name}'
- twofa_not_active: Not activated
- twofa_label_code: Code
- twofa_hint_disabled_html: Setting <strong>%{label}</strong> will deactivate and unpair
- two-factor authentication devices for all users.
- twofa_hint_required_html: Setting <strong>%{label}</strong> will require all users
- to set up two-factor authentication at their next login.
- twofa_label_setup: Enable two-factor authentication
- twofa_label_deactivation_confirmation: Disable two-factor authentication
- twofa_notice_select: 'Please select the two-factor scheme you would like to use:'
- twofa_warning_require: The administrator requires you to enable two-factor authentication.
- twofa_activated: Two-factor authentication successfully enabled. It is recommended
- to <a data-method="post" href="%{bc_path}">generate backup codes</a> for your account.
- twofa_deactivated: Two-factor authentication disabled.
- twofa_mail_body_security_notification_paired: Two-factor authentication successfully
- enabled using %{field}.
- twofa_mail_body_security_notification_unpaired: Two-factor authentication disabled
- for your account.
- twofa_mail_body_backup_codes_generated: New two-factor authentication backup codes
- generated.
- twofa_mail_body_backup_code_used: A two-factor authentication backup code has been
- used.
- twofa_invalid_code: Code is invalid or outdated.
- twofa_label_enter_otp: Please enter your two-factor authentication code.
- twofa_too_many_tries: Too many tries.
- twofa_resend_code: Resend code
- twofa_code_sent: An authentication code has been sent to you.
- twofa_generate_backup_codes: Generate backup codes
- twofa_text_generate_backup_codes_confirmation: This will invalidate all existing backup
- codes and generate new ones. Would you like to continue?
- twofa_notice_backup_codes_generated: Your backup codes have been generated.
- twofa_warning_backup_codes_generated_invalidated: New backup codes have been generated.
- Your existing codes from %{time} are now invalid.
- twofa_label_backup_codes: Two-factor authentication backup codes
- twofa_text_backup_codes_hint: Use these codes instead of a one-time password should
- you not have access to your second factor. Each code can only be used once. It is
- recommended to print and store them in a safe place.
- twofa_text_backup_codes_created_at: Backup codes generated %{datetime}.
- twofa_backup_codes_already_shown: Backup codes cannot be shown again, please <a data-method="post"
- href="%{bc_path}">generate new backup codes</a> if required.
- field_twofa_scheme: Two-factor authentication scheme
- text_user_destroy_confirmation: Are you sure you want to delete this user and remove
- all references to them? This cannot be undone. Often, locking a user instead of
- deleting them is the better solution. To confirm, please enter their login (%{login})
- below.
- text_project_destroy_enter_identifier: To confirm, please enter the project's identifier
- (%{identifier}) below.
- button_add_subtask: Add subtask
- notice_invalid_watcher: 'Invalid watcher: User will not receive any notifications
- because they do not have access to view this object.'
- button_fetch_changesets: Fetch commits
- permission_view_message_watchers: View message watchers list
- permission_add_message_watchers: Add message watchers
- permission_delete_message_watchers: Delete message watchers
- label_message_watchers: Watchers
- button_copy_link: Copy link
- error_invalid_authenticity_token: Invalid form authenticity token.
- error_query_statement_invalid: An error occurred while executing the query and has
- been logged. Please report this error to your Redmine administrator.
- permission_view_wiki_page_watchers: View wiki page watchers list
- permission_add_wiki_page_watchers: Add wiki page watchers
- permission_delete_wiki_page_watchers: Delete wiki page watchers
- label_wiki_page_watchers: Watchers
- label_attachment_description: File description
- error_no_data_in_file: The file does not contain any data
- field_twofa_required: Require two factor authentication
- twofa_hint_optional_html: Setting <strong>%{label}</strong> will let users set up
- two-factor authentication at will, unless it is required by one of their groups.
- twofa_text_group_required: This setting is only effective when the global two factor
- authentication setting is set to 'optional'. Currently, two factor authentication
- is required for all users.
- twofa_text_group_disabled: This setting is only effective when the global two factor
- authentication setting is set to 'optional'. Currently, two factor authentication
- is disabled.
- field_default_issue_query: Default issue query
+ label_import_time_entries: Importera tidsstämplingar
+ notice_issue_not_closable_by_open_tasks: Det här ärendet kan inte stängas eftersom det har minst en öppen underaktivitet.
+ notice_issue_not_closable_by_blocking_issue: Det här ärendet kan inte stängas eftersom det blockeras av minst ett öppet ärende.
+ notice_issue_not_reopenable_by_closed_parent_issue: Det här ärendet kan inte återöppnas eftersom dess överordnade ärende är stängt.
+ error_attachments_too_many: 'Den här filen kan inte laddas upp eftersom den överskriderdet maximala antalet filer som kan bifogas samtidigt (%{max_number_of_files}).'
+ error_bulk_download_size_too_big: 'Dessa bilagor kan inte laddas ner samtidigt eftersom den totala filstorleken överskrider den maximalt tillåtna storleken (%{max_size}).'
+ error_can_not_execute_macro_html: 'Fel vid körning av makrot <strong>%{name}</strong> (%{error}).'
+ error_macro_does_not_accept_block: Det här makrot accepterar inte ett textblock
+ error_childpages_macro_no_argument: Utan argument kan det här makrot endast anropas från wikisidor
+ error_circular_inclusion: Cirkulär inbäddning upptäckt
+ error_page_not_found: Sidan kunde inte hittas
+ error_filename_required: Filnamn krävs
+ error_invalid_size_parameter: Ogiltig storleksparameter
+ error_attachment_not_found: Bilagan %{name} kunde inte hittas
+ field_passwd_changed_on: Lösenordet ändrades
+ field_toolbar_language_options: Språk för verktygsfält för kodmarkering
+ setting_bulk_download_max_size: Maximal totalstorlek för samtidig nedladdning
+ setting_email_domains_allowed: Tillåtna e-postdomäner
+ setting_email_domains_denied: Otillåtna e-postdomäner
+ setting_twofa: Tvåfaktorsautentisering
+ permission_delete_project: Ta bort projektet
+ label_optional: valfritt
+ label_user_mail_notify_about_high_priority_issues_html: 'Avisera mig också om ärenden med en prioritet på <em>%{prio}</em> eller högre'
+ label_days_to_html: "%{days} dagar fram till %{date}"
+ label_required_lower: obligatorisk
+ label_download_all_attachments: Ladda ner samtliga filer
+ label_relations_mapping: Relationsmappning
+ label_assign_to_me: Tilldela mig
+ button_disable: Inaktivera
+ label_import_users: Importera användare
+ twofa__totp__name: Autentiseringsapp
+ twofa__totp__text_pairing_info_html: 'Skanna den här QR-koden eller ange nyckeln i en autentiseringsapp (t.ex. <a href="https://support.google.com/accounts/answer/1066447">Google Authenticator</a>, <a href="https://authy.com/download/">Authy</a>, <a href="https://guide.duo.com/third-party-accounts"> Duo Mobile</a>) och ange sedan koden i fältet nedan för att aktivera tvåfaktorsautentisering.'
+ twofa__totp__label_plain_text_key: Nyckel i klartext
+ twofa__totp__label_activate: Aktivera autentiseringsapp
+ twofa_currently_active: 'För närvarande aktiv: %{twofa_scheme_name}'
+ twofa_not_active: Inte aktiverad
+ twofa_label_code: Kod
+ twofa_hint_disabled_html: Inställningen <strong>%{label}</strong> kommer att inaktivera och koppla bort tvåfaktorsautentiseringsenheter för alla användare.
+ twofa_hint_required_html: Inställningen <strong>%{label}</strong> kommer att kräva att alla användare konfigurerar tvåfaktorsautentisering vid sin nästa inloggning.
+ twofa_label_setup: Aktivera tvåfaktorsautentisering
+ twofa_label_deactivation_confirmation: Inaktivera tvåfaktorsautentisering
+ twofa_notice_select: 'Välj det tvåfaktorsalternativ du vill använda:'
+ twofa_warning_require: Administratören kräver att du aktiverar tvåfaktorsautentisering.
+ twofa_activated: 'Tvåfaktorsautentisering har aktiverats framgångsrikt. Det rekommenderas att du <a data-method="post" href="%{bc_path}">genererar reservkoder</a> för ditt konto.'
+ twofa_deactivated: Tvåfaktorsautentisering inaktiverad.
+ twofa_mail_body_security_notification_paired: 'Tvåfaktorsautentisering har framgångsrikt aktiverats med hjälp av %{field}.'
+ twofa_mail_body_security_notification_unpaired: Tvåfaktorsautentisering har inaktiverats för ditt konto.
+ twofa_mail_body_backup_codes_generated: Nya reservkoder för tvåfaktorsautentisering har genererats.
+ twofa_mail_body_backup_code_used: En reservkod för tvåfaktorsautentisering har använts.
+ twofa_invalid_code: Koden är ogiltig eller föråldrad.
+ twofa_label_enter_otp: Ange din kod för tvåfaktorsautentisering.
+ twofa_too_many_tries: För många försök.
+ twofa_resend_code: Skicka kod på nytt
+ twofa_code_sent: En autentiseringskod har skickats till dig.
+ twofa_generate_backup_codes: Generera reservkoder
+ twofa_text_generate_backup_codes_confirmation: Detta kommer att göra alla befintliga reservkoder obrukbara och generera nya. Vill du fortsätta?
+ twofa_notice_backup_codes_generated: Dina reservkoder har genererats
+ twofa_warning_backup_codes_generated_invalidated: 'Nya reservkoder har genererats. Dina befintliga koder från %{time} är nu obrukbara.'
+ twofa_label_backup_codes: Reservkoder för tvåfaktorsautentisering.
+ twofa_text_backup_codes_hint: Använd dessa koder istället för en engångslösenkod om du inte har tillgång till din tvåfaktorsautentiseringsapp. Varje kod kan endast användas en gång. Det rekommenderas att skriva ut dem och förvara dem på en säker plats.
+ twofa_text_backup_codes_created_at: Reservkoder genererades %{datetime}.
+ twofa_backup_codes_already_shown: 'Reservkoder kan inte visas igen, men du kan <a data-method="post" href="%{bc_path}">generera nya reservkoder</a> om så behövs.'
+ field_twofa_scheme: Tvåfaktorsautentiseringssystem
+ text_user_destroy_confirmation: 'Är du säker på att du vill ta bort den här användaren och ta bort alla referenser till denne? Detta kan inte ångras. Ofta är det bättre att låsa en användare istället för att ta bort den. För att bekräfta, vänligen ange deras inloggning (%{login}) nedan.'
+ text_project_destroy_enter_identifier: 'För att bekräfta, vänligen ange projektets identifierare (%{identifier}) nedan.'
+ button_add_subtask: Lägg till underaktivitet
+ notice_invalid_watcher: 'Ogiltig bevakare: Användaren kommer inte att ta emot några aviseringar eftersom de inte har åtkomst att visa detta objekt.'
+ button_fetch_changesets: Hämta commits
+ permission_view_message_watchers: Visa lista över meddelandebevakare
+ permission_add_message_watchers: Lägg till meddelandebevakare
+ permission_delete_message_watchers: Ta bort meddelandebevakare
+ label_message_watchers: Bevakare
+ button_copy_link: Kopiera länk
+ error_invalid_authenticity_token: Ogiltig autentiseringstoken för formuläret.
+ error_query_statement_invalid: Ett fel inträffade vid körning av frågan och har loggats. Vänligen rapportera detta fel till din Redmine-administratör.
+ permission_view_wiki_page_watchers: Visa lista över bevakare för wikisida
+ permission_add_wiki_page_watchers: Lägg till bevakare för wikisida
+ permission_delete_wiki_page_watchers: Ta bort bevakare för wikisida
+ label_wiki_page_watchers: Bevakare
+ label_attachment_description: Filbeskrivning
+ error_no_data_in_file: Filen innehåller inga data
+ field_twofa_required: Kräv tvåfaktorsautentisering
+ twofa_hint_optional_html: Inställningen <strong>%{label}</strong> kommer att låta användare konfigurera tvåfaktorsautentisering efter eget önskemål, om det inte krävs av någon av deras grupper.
+ twofa_text_group_required: Den här inställningen är endast giltig när den globala inställningen för tvåfaktorsautentisering är inställd på "valfritt". För närvarande krävs tvåfaktorsautentisering för alla användare.
+ twofa_text_group_disabled: Den här inställningen är endast giltig när den globala inställningen för tvåfaktorsautentisering är inställd på "valfritt". För närvarande är tvåfaktorsautentisering inaktiverad.
+ field_default_issue_query: Standardfiltrering för ärenden
label_default_queries:
- for_all_projects: For all projects
- for_current_project: For current project
- for_all_users: For all users
- for_this_user: For this user
- text_allowed_queries_to_select: Public (to any users) queries only selectable
- text_all_migrations_have_been_run: All database migrations have been run
- button_save_object: Save %{object_name}
- button_edit_object: Edit %{object_name}
- button_delete_object: Delete %{object_name}
- text_setting_config_change: You can configure the behaviour in config/configuration.yml.
- Please restart the application after editing it.
- label_bulk_edit: Bulk edit
- button_create_and_follow: Create and follow
- label_subtask: Subtask
- label_default_query: Default query
- field_default_project_query: Default project query
- label_required_administrators: required for administrators
- twofa_hint_required_administrators_html: Setting <strong>%{label}</strong> behaves
- like optional, but will require all users with administration rights to set up two-factor
- authentication at their next login.
- label_auto_watch_on: Auto watch
- label_auto_watch_on_issue_contributed_to: Issues I contributed to
- text_project_close_confirmation: Are you sure you want to close the '%{value}' project
- to make it read-only?
- text_project_reopen_confirmation: Are you sure you want to reopen the '%{value}' project?
- text_project_archive_confirmation: Are you sure you want to archive the '%{value}'
- project?
- mail_destroy_project_failed: Project %{value} could not be deleted.
- mail_destroy_project_successful: Project %{value} was deleted successfully.
- mail_destroy_project_with_subprojects_successful: Project %{value} and its subprojects
- were deleted successfully.
- project_status_scheduled_for_deletion: scheduled for deletion
- text_projects_bulk_destroy_confirmation: Are you sure you want to delete the selected
- projects and related data?
- text_projects_bulk_destroy_head: |
- You are about to permanently delete the following projects, including possible subprojects and any related data.
- Please review the information below and confirm that this is indeed what you want to do.
- This action cannot be undone.
- text_projects_bulk_destroy_confirm: To confirm, please enter "%{yes}" in the box below.
- text_subprojects_bulk_destroy: 'including its subproject(s): %{value}'
- field_current_password: Current password
- sudo_mode_new_info_html: "<strong>What's happening?</strong> You need to reconfirm
- your password before taking any administrative actions, this ensures your account
- stays protected."
- label_edited: Edited
- label_time_by_author: "%{time} by %{author}"
- field_default_time_entry_activity: Default spent time activity
- field_is_member_of_group: Member of group
- text_users_bulk_destroy_head: You are about to delete the following users and remove
- all references to them. This cannot be undone. Often, locking users instead of deleting
- them is the better solution.
- text_users_bulk_destroy_confirm: To confirm, please enter "%{yes}" below.
- permission_select_project_publicity: Set project public or private
- label_auto_watch_on_issue_created: Issues I created
- field_any_searchable: Any searchable text
- label_contains_any_of: contains any of
- button_apply_issues_filter: Apply issues filter
- label_view_previous_annotation: View annotation prior to this change
- label_has_been: has been
- label_has_never_been: has never been
- label_changed_from: changed from
- label_issue_statuses_description: Issue statuses description
- label_open_issue_statuses_description: View all issue statuses description
- text_select_apply_issue_status: Select issue status
- field_name_or_email_or_login: Name, email or login
- text_default_active_job_queue_changed: Default queue adapter which is well suited
- only for dev/test changed
- label_option_auto_lang: auto
- label_issue_attachment_added: Attachment added
- field_estimated_remaining_hours: Estimated remaining time
- field_last_activity_date: Last activity
- setting_issue_done_ratio_interval: Done ratio options interval
- setting_copy_attachments_on_issue_copy: Copy attachments on copy
- field_thousands_delimiter: Thousands delimiter
- label_involved_principals: Author / Previous assignee
+ for_all_projects: För alla projekt
+ for_current_project: För aktivt projekt
+ for_all_users: För alla användare
+ for_this_user: För den här användaren
+ text_allowed_queries_to_select: Endast publika (för alla användare) filtreringar kan väljas
+ text_all_migrations_have_been_run: Alla databas-migrationer har körts
+ button_save_object: 'Spara %{object_name}'
+ button_edit_object: 'Redigera %{object_name}'
+ button_delete_object: 'Ta bort %{object_name}'
+ text_setting_config_change: 'Du kan konfigurera beteendet i <strong>config/configuration.yml</strong>. Vänligen starta om applikationen efter att du har redigerat den.'
+ label_bulk_edit: Massredigera
+ button_create_and_follow: Skapa och öppna
+ label_subtask: Underaktivitet
+ label_default_query: Standardfiltrering
+ field_default_project_query: Standardfiltrering för projekt
+ label_required_administrators: obligatoriskt för administratörer
+ twofa_hint_required_administrators_html: 'Inställningen <strong>%{label}</strong> fungerar som valfri, men kommer att kräva att alla användare med administratörsrättigheter konfigurerar tvåfaktorsautentisering vid sin nästa inloggning.'
+ label_auto_watch_on: Automatisk bevakning
+ label_auto_watch_on_issue_contributed_to: Ärenden jag varit delaktig i
+ text_project_close_confirmation: 'Är du säker på att du vill stänga projektet "%{value}" för att göra det skrivskyddat?'
+ text_project_reopen_confirmation: 'Är du säker på att du vill återöppna projektet "%{value}"?'
+ text_project_archive_confirmation: 'Är du säker på att du vill arkivera projektet "%{value}"?'
+ mail_destroy_project_failed: 'Projektet %{value} kunde inte raderas.'
+ mail_destroy_project_successful: 'Projektet %{value} raderades framgångsrikt.'
+ mail_destroy_project_with_subprojects_successful: 'Projektet %{value} och dess underprojekt raderades framgångsrikt.'
+ project_status_scheduled_for_deletion: schemalagd för radering
+ text_projects_bulk_destroy_confirmation: Är du säker på att du vill radera de valda projekten och relaterad data?
+ text_projects_bulk_destroy_head: Du är på väg att permanent radera följande projekt, inklusive eventuella underprojekt och relaterad data. Vänligen granska informationen nedan och bekräfta att detta verkligen är vad du vill göra. Denna åtgärd kan inte ångras.
+ text_projects_bulk_destroy_confirm: 'För att bekräfta, vänligen ange "%{yes}" i rutan nedan.'
+ text_subprojects_bulk_destroy: 'inklusive dess underprojekt: %{value}'
+ field_current_password: Nuvarande lösenord
+ sudo_mode_new_info_html: '<strong>Vad händer?</strong> Du måste bekräfta ditt lösenord igen innan du vidtar några administrativa åtgärder, detta säkerställer att ditt konto förblir skyddat.'
+ label_edited: Redigerad
+ label_time_by_author: '%{time} av %{author}'
+ field_default_time_entry_activity: Standardaktivitet för spenderad tid
+ field_is_member_of_group: Medlem i grupp
+ text_users_bulk_destroy_head: Du är på väg att ta bort följande användare och ta bort alla referenser till dem. Detta kan inte ångras. Ofta är det bättre att låsa användare istället för att ta bort dem.
+ text_users_bulk_destroy_confirm: 'För att bekräfta, vänligen ange "%{yes}" nedan.'
+ permission_select_project_publicity: Ställ in projektet som publikt eller privat
+ label_auto_watch_on_issue_created: Ärenden jag har skapat
+ field_any_searchable: All sökbart text
+ label_contains_any_of: innehåller någon av
+ button_apply_issues_filter: Tillämpa filter för ärenden
+ label_view_previous_annotation: Visa kommentar före denna ändring
+ label_has_been: har varit
+ label_has_never_been: har aldrig varit
+ label_changed_from: ändrad från
+ label_issue_statuses_description: Beskrivning av ärendestatusar
+ label_open_issue_statuses_description: Visa alla beskrivningar av ärendestatusar
+ text_select_apply_issue_status: Välj ärendestatus
+ field_name_or_email_or_login: Namn, e-post eller inloggning
+ text_default_active_job_queue_changed: Standardköadapter som är lämpad endast för utveckling/test har ändrats
+ label_option_auto_lang: automatisk
+ label_issue_attachment_added: Bilaga tillagd
+ field_estimated_remaining_hours: Beräknad återstående tid
+ field_last_activity_date: Senaste aktivitet
+ setting_issue_done_ratio_interval: 'Alternativ för intervall för "% Klar"'
+ setting_copy_attachments_on_issue_copy: Kopiera bilagor vid kopiering
+ field_thousands_delimiter: Tusenavgränsare
+ label_involved_principals: 'Skapare / Tidigare tilldelad'
label_attachment_summary:
- zero: "%{filename}"
- one: "%{filename} and 1 file"
- other: "%{filename} and %{count} files"
+ zero: '%{filename}'
+ one: '%{filename} och 1 fil'
+ other: '%{filename} och %{count} filer'
diff --git a/config/locales/tr.yml b/config/locales/tr.yml
index ff3eb27ea..02bf28435 100644
--- a/config/locales/tr.yml
+++ b/config/locales/tr.yml
@@ -280,7 +280,7 @@ tr:
field_is_filter: süzgeç olarak kullanılmış
field_issue_to: İlişkili iş
field_delay: Gecikme
- field_assignable: Bu role atanabilecek işler
+ field_assignable: Bu roldeki kullanıcılara iş atanabilir
field_redirect_existing_links: Mevcut bağlantıları yönlendir
field_estimated_hours: Kalan zaman
field_column_names: Sütunlar
diff --git a/config/routes.rb b/config/routes.rb
index 563ffe1e2..89927bee3 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -316,7 +316,7 @@ Rails.application.routes.draw do
# additional routes for having the file name at the end of url
get 'attachments/:id/:filename', :to => 'attachments#show', :id => /\d+/, :filename => /.*/, :as => 'named_attachment', :format => 'html'
- get 'attachments/download/:id/:filename', :to => 'attachments#download', :id => /\d+/, :filename => /.*/, :as => 'download_named_attachment'
+ get 'attachments/download/:id/:filename', :to => 'attachments#download', :id => /\d+/, :filename => /.*/, :as => 'download_named_attachment', :format => 'html'
get 'attachments/download/:id', :to => 'attachments#download', :id => /\d+/
get 'attachments/thumbnail/:id(/:size)', :to => 'attachments#thumbnail', :id => /\d+/, :size => /\d+/, :as => 'thumbnail'
resources :attachments, :only => [:show, :update, :destroy]
@@ -412,6 +412,10 @@ Rails.application.routes.draw do
get 'help/wiki_syntax/(:type)', :controller => 'help', :action => 'show_wiki_syntax', :constraints => { :type => /detailed/ }, :as => 'help_wiki_syntax'
get 'help/code_highlighting', :controller => 'help', :action => 'show_code_highlighting', :as => 'help_code_highlighting'
+ # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
+ # Can be used by load balancers and uptime monitors to verify that the app is live.
+ get "up" => "rails/health#show", :as => :rails_health_check
+
Redmine::Plugin.directory.glob("*/config/routes.rb").sort.each do |plugin_routes_path|
instance_eval(plugin_routes_path.read, plugin_routes_path.to_s)
rescue SyntaxError, StandardError => e
diff --git a/doc/CHANGELOG b/doc/CHANGELOG
index 8175c7ec0..4a7ed0745 100644
--- a/doc/CHANGELOG
+++ b/doc/CHANGELOG
@@ -4,6 +4,266 @@ Redmine - project management software
Copyright (C) 2006- Jean-Philippe Lang
https://www.redmine.org/
+== 2025-07-07 v6.0.6
+
+=== [Attachments]
+
+* Defect #42920: Missing icon in attachments upload form
+
+=== [Code cleanup/refactoring]
+
+* Defect #42687: Fix random failures in several system tests with Chrome 133 and later
+* Patch #42422: Use Capybara's assert_current_path in "log_user" steps to wait for page in ApplicationSystemTestCase
+* Patch #42600: Suppress "Change your password" popup for stable system tests
+* Patch #42756: Update tests for rails-dom-testing 2.3.0 whitespace collapsing
+
+=== [Database]
+
+* Defect #42622: Joining both atom_token and api_token on the User model causes an error due to the ambiguous column name "action"
+
+=== [Documentation]
+
+* Defect #42644: Noto Sans fonts are not applied on Wiki syntax help pages
+* Defect #42657: Update documentation links and fix config flag typo
+* Patch #42618: Add missing allowed CSS properties to the CommonMark Markdown help
+
+=== [Email receiving]
+
+* Defect #42962: Mail handler fails to create issues from emails over 4MB on Rack >= 3.1.14
+
+=== [Gems support]
+
+* Defect #42606: RuboCop warning about deprecated `EnsureNode#body` with rubocop-ast >= 1.41
+
+=== [I18n]
+
+* Defect #42815: Limit available locales to those defined by Redmine itself no longer works
+
+=== [Issues list]
+
+* Defect #42807: Progress Bar in issues list has a border above it
+
+=== [Issues workflow]
+
+* Defect #42875: "Page not found" error when saving workflows with many statuses on Rack >= 3.1.14
+
+=== [No category]
+
+* Patch #42688: Run system tests on GitHub CI
+
+=== [Performance]
+
+* Defect #42933: Fix N+1 query issue in Wiki history page when loading authors of Wiki content versions
+
+=== [SCM]
+
+* Defect #42839: Downloading .js files from the repository browser fails with a 422 error due to ActionController::InvalidCrossOriginRequest
+* Patch #42597: Skip some Mercurial tests when using Mercurial 5.1 or later in Redmine 6.0 or 5.1
+
+=== [Security]
+
+* Patch #42662: Require net-imap gem 0.2.5, 0.3.9, 0.4.20, 0.5.7, or later to address CVE-2025-43857
+
+=== [Text formatting]
+
+* Defect #42332: "Edit this section" button is missing for headings rendered as multiline HTML
+* Defect #42648: Wiki/CommonMark: Broken references for multiple footnote usage
+
+=== [Translations]
+
+* Patch #42739: Persian translate update for 6.0-stable
+* Patch #42754: Swedish translation update for 6.0-stable
+
+=== [UI]
+
+* Defect #42640: Query totals overlaps query buttons when an RTL language is used
+* Defect #42654: Text in project jump box is vertically misaligned
+* Defect #42773: Padding in the Preview tab is larger than in the actual rendered content
+* Defect #42786: "Clear" button for custom queries has incorrect styling inside the flyout menu
+* Defect #42797: Loading and waiting icons use legacy icons and overlap filename when attaching files
+* Defect #42953: Replace legacy other download icon with SVG icon
+* Patch #42641: Improve SVG contrast when an item is selected in administration sidebar
+* Patch #42794: Hide irrelevant information when printing
+
+=== [Wiki]
+
+* Defect #42558: JPEG images are not shown in exported PDF files
+
+== 2025-04-20 v6.0.5
+
+=== [Administration]
+
+* Defect #42584: NoMethodError when creating a user with an invalid email address and domain restrictions are enabled
+
+=== [Attachments]
+
+* Defect #42394: Inconsistent behaviour between attachment download routes with and without filename
+
+=== [Code cleanup/refactoring]
+
+* Patch #42562: Fix random test failure in ProjectAdminQueryTest due to missing language setting
+* Patch #42572: Fix random test failure in MemberTest#test_update_roles_with_inherited_roles due to non-deterministic ordering
+
+=== [Custom fields]
+
+* Defect #42342: Missing thousands separator in Integer and Float custom field totals
+* Patch #41935: Add "editable" attribute in the custom fields API response
+
+=== [Gantt]
+
+* Defect #42145: MiniMagick (> 5) removed cli_path, result crash when supplied imagemagick_convert_command
+
+=== [Issues]
+
+* Defect #42458: "For all projects" checkbox should be disabled when editing an existing query in which the checkbox is already checked
+
+=== [Performance]
+
+* Defect #40728: Slow loading of global spent time list in MySQL
+* Feature #42574: Optimize autocomplete issue listing triggered by typing "##" by eager loading trackers
+
+=== [Plugin API]
+
+* Defect #42509: Plugin activity icons broken when multiple plugins are loaded
+
+=== [Projects]
+
+* Patch #42440: Fix project selector focus by explicitly targeting the first selected item
+
+=== [SCM]
+
+* Patch #42500: Skip repository tests when the SCM client command is unavailable
+
+=== [Text formatting]
+
+* Defect #42545: Commit message in issue history might be rendered in incorrect context
+
+=== [UI]
+
+* Defect #41828: In mobile view, delete relation svg icon in 'Related Issues' on an issue page, overflow text
+* Defect #41833: Tabs left / right buttons use legacy icons
+* Defect #41947: Collapse arrow shows the wrong direction at /workflows/edit
+* Defect #41952: Flash notice icons use the legacy icons
+* Defect #41967: Replace SCM action legacy icons with SVG icons in the tree view of the repository browser
+* Defect #42181: Project jump box uses legacy caret icons
+* Defect #42285: Icon expanded for closed fixed versions missing
+* Defect #42286: Context menu right arrow uses the legacy icon
+* Defect #42369: Expander icons not switch in Collapse all/Expand all
+* Defect #42465: Improve SVG icon compatibility with RTL languages
+* Defect #42487: Improve SVG contrast when a row is selected on table list
+* Defect #42520: PNG icon displayed instead of SVG in subtasks list when viewing all tasks
+* Defect #42532: Expander icon not working in repository tree
+* Defect #42575: Fix sidebar switch button display in RTL language
+* Defect #42576: Newly attached files are displayed using the legacy icons
+* Patch #42497: Adjust the position of the news comment delete button
+* Patch #42577: Replace legacy Atom icon with SVG icon
+* Patch #42596: Do not show user icon in add watchers modal when gravatar is disabled
+
+== 2025-03-10 v6.0.4
+
+=== [Administration]
+
+* Feature #42008: Expose default Rails health check endpoint "/up" for load balancers and uptime monitoring
+
+=== [Code cleanup/refactoring]
+
+* Defect #42200: InlineAutocompleteSystemTest login test fails randomly
+* Patch #42244: Fix random failures in IssuesTest#test_bulk_copy due to StaleElementReferenceError
+
+=== [Custom fields]
+
+* Defect #42233: Float custom values with ',' as decimal separator are not converted to '.' and cause SQL errors when sorting or summing
+
+=== [Gems support]
+
+* Defect #42245: 5.1-stable: Redmine fails to start with error: Unknown database adapter `"mysql2"` found in config/database.yml
+
+=== [No category]
+
+* Feature #30069: Use GitHub Actions as a secondary CI solution to run tests through the existing mirroring
+
+=== [Project settings]
+
+* Defect #42192: Project settings members tab may raise ArgumentError if orphaned member records exist
+
+=== [Security]
+* Defect #42238: Stored Cross-Site Scripting (XSS) in custom query
+* Defect #42326: Stored Cross-Site Scripting (XSS) in macros
+* Defect #42352: ProjectQuery leaks details of private projects
+* Defect #42194: /my/account does not correctly enforce sudo mode
+* Patch #42333: Update Nokogiri to 1.18.3
+
+=== [Time tracking]
+
+* Defect #42172: `format_hours` method produces incorrect output for negative time values when `Setting.timespan_format` is "minutes"
+
+=== [Translations]
+
+* Defect #42170: Fix Turkish translation of field_assignable
+* Patch #42239: Czech translation update for 6.0-stable
+
+=== [UI]
+
+* Defect #42229: Latest news box on home page misses icons
+
+=== [UI - Responsive]
+
+* Defect #42182: Poor color contrast of icons on flyout menu
+
+== 2025-01-29 v6.0.3
+
+=== [Accounts / authentication]
+
+* Defect #41930: Redirection after signing in fails when the back_url includes a port number
+
+=== [Activity view]
+
+* Defect #42003: Misalignment of icons and titles in Activity view
+* Defect #42070: Whitespace missing after hyphen between project name and event title in Activity view
+* Feature #42038: Improve readability by adjusting font sizes and colors in activity view and search results
+
+=== [Attachments]
+
+* Defect #42084: Placeholder icon for non-existent thumbnail flickers rapidly on hover
+
+=== [Code cleanup/refactoring]
+
+* Defect #42088: Fix incorrect syntax in application.css on 6.0-stable
+* Patch #41961: Use `fixtures :all` to ensure consistent test data and improve test reliability
+* Patch #42089: Fix Lint workflow error on 6.0-stable due to unsupported ruby/setup-ruby on Ubuntu 24.04
+* Patch #42140: Update footer copyright year to 2025
+
+=== [Gantt]
+
+* Defect #41925: Context menu submenus close unexpectedly on Gantt chart due to z-index conflict
+
+=== [Gems support]
+
+* Defect #42013: Redmine fails to start with error: Unknown database adapter `"mysql2"` found in config/database.yml
+
+=== [Issues]
+
+* Defect #42066: NoMethodError exception occurs in IssuePriority#high and #low when both default and active priorities are absent
+
+=== [Permissions and roles]
+
+* Defect #42106: Member roles are incorrectly added when a user's memberships are updated
+
+=== [Rails support]
+
+* Defect #42113: Redmine 5.x not starting with ActiveSupport Logger error
+* Patch #41970: Updates Rails to 7.2.2.1
+
+=== [UI]
+
+* Defect #42023: Search results page uses legacy icons
+* Defect #42051: "Font used for text areas" setting causes inconsistent font size
+* Defect #42117: Key-value list reorder icon uses legacy icon
+* Defect #42126: The member table layout breaks due to .icon class on td elements
+* Defect #42130: Multiselect toggle uses legacy icons
+* Feature #42005: Improve readability of error pages by updating fonts and layout
+* Feature #42072: Adjust font size for breadcrumb and subtitle to improve readability and consistency
+
== 2024-12-11 v6.0.2
=== [Accounts / authentication]
diff --git a/doc/INSTALL b/doc/INSTALL
index 89c85479d..5f971517d 100644
--- a/doc/INSTALL
+++ b/doc/INSTALL
@@ -136,10 +136,10 @@ Please do not enter your SMTP settings in environment.rb.
== References
-* https://www.redmine.org/wiki/redmine/RedmineInstall
-* https://www.redmine.org/wiki/redmine/EmailConfiguration
-* https://www.redmine.org/wiki/redmine/RedmineSettings
-* https://www.redmine.org/wiki/redmine/RedmineRepositories
-* https://www.redmine.org/wiki/redmine/RedmineReceivingEmails
-* https://www.redmine.org/wiki/redmine/RedmineReminderEmails
-* https://www.redmine.org/wiki/redmine/RedmineLDAP
+* https://www.redmine.org/projects/redmine/wiki/RedmineInstall
+* https://www.redmine.org/projects/redmine/wiki/EmailConfiguration
+* https://www.redmine.org/projects/redmine/wiki/RedmineSettings
+* https://www.redmine.org/projects/redmine/wiki/RedmineRepositories
+* https://www.redmine.org/projects/redmine/wiki/RedmineReceivingEmails
+* https://www.redmine.org/projects/redmine/wiki/RedmineReminderEmails
+* https://www.redmine.org/projects/redmine/wiki/RedmineLDAP
diff --git a/doc/UPGRADING b/doc/UPGRADING
index 091b86283..b990ddba2 100644
--- a/doc/UPGRADING
+++ b/doc/UPGRADING
@@ -62,7 +62,7 @@ https://www.redmine.org/
directory for web server delivery.
By default, Redmine automatically recompiles assets in production mode when the application starts.
- This behavior can be controlled using the "config.assets.redmine_detect_update flag" from configuration file.
+ This behavior can be controlled using the "config.assets.redmine_detect_update" flag from configuration file.
To manually compile assets or if automatic compilation is disabled:
@@ -101,4 +101,4 @@ https://www.redmine.org/
== References
-* https://www.redmine.org/wiki/redmine/RedmineUpgrade
+* https://www.redmine.org/projects/redmine/wiki/RedmineUpgrade
diff --git a/lib/redmine/activity.rb b/lib/redmine/activity.rb
index 826b81c9e..c972eeee2 100644
--- a/lib/redmine/activity.rb
+++ b/lib/redmine/activity.rb
@@ -19,11 +19,11 @@
module Redmine
module Activity
- mattr_accessor :available_event_types, :default_event_types, :plugins_event_types, :providers
+ mattr_accessor :available_event_types, :default_event_types, :plugins_event_classes, :providers
@@available_event_types = []
@@default_event_types = []
- @@plugins_event_types = {}
+ @@plugins_event_classes = {}
@@providers = Hash.new {|h, k| h[k]=[]}
class << self
@@ -41,19 +41,22 @@ module Redmine
@@available_event_types << event_type unless @@available_event_types.include?(event_type)
@@default_event_types << event_type unless options[:default] == false
- @@plugins_event_types = { event_type => options[:plugin].to_s } unless options[:plugin].nil?
+ if options[:plugin]
+ providers.each do |provider|
+ @@plugins_event_classes[provider] = options[:plugin].to_s
+ end
+ end
@@providers[event_type] += providers
end
def delete(event_type)
@@available_event_types.delete event_type
@@default_event_types.delete event_type
- @@plugins_event_types.delete(event_type)
@@providers.delete(event_type)
end
- def plugin_name(event_type)
- @@plugins_event_types[event_type]
+ def plugin_name(class_name)
+ @@plugins_event_classes[class_name.to_s]
end
end
end
diff --git a/lib/redmine/field_format.rb b/lib/redmine/field_format.rb
index e3ebc0398..b41b2986c 100644
--- a/lib/redmine/field_format.rb
+++ b/lib/redmine/field_format.rb
@@ -130,6 +130,8 @@ module Redmine
if value.empty?
value << ''
end
+ elsif custom_field.field_format == 'float'
+ value = normalize_float(value)
else
value = value.to_s
end
@@ -540,7 +542,6 @@ module Redmine
end
def validate_single_value(custom_field, value, customized=nil)
- value = normalize_float(value)
errs = super
errs << ::I18n.t('activerecord.errors.messages.invalid') unless Kernel.Float(value, exception: false)
errs
diff --git a/lib/redmine/helpers/gantt.rb b/lib/redmine/helpers/gantt.rb
index 43e0986c6..bd784609b 100644
--- a/lib/redmine/helpers/gantt.rb
+++ b/lib/redmine/helpers/gantt.rb
@@ -396,7 +396,15 @@ module Redmine
Redmine::Configuration['rmagick_font_path'].presence
img = MiniMagick::Image.create(".#{format}")
if Redmine::Configuration['imagemagick_convert_command'].present?
- MiniMagick.cli_path = File.dirname(Redmine::Configuration['imagemagick_convert_command'])
+ if MiniMagick.respond_to?(:cli_path)
+ MiniMagick.cli_path = File.dirname(Redmine::Configuration['imagemagick_convert_command'])
+ else
+ Rails.logger.warn(
+ 'imagemagick_convert_command option is ignored ' \
+ 'because MiniMagick has removed the option to define a custom path for the binary. ' \
+ 'Please ensure the convert binary is available in your PATH.'
+ )
+ end
end
MiniMagick.convert do |gc|
gc.size('%dx%d' % [subject_width + g_width + 1, height])
diff --git a/lib/redmine/i18n.rb b/lib/redmine/i18n.rb
index a9cd1dd0d..2f39c06a2 100644
--- a/lib/redmine/i18n.rb
+++ b/lib/redmine/i18n.rb
@@ -94,8 +94,9 @@ module Redmine
minutes = (hours * 60).round
if Setting.timespan_format == 'minutes'
- h, m = minutes.divmod(60)
- "%d:%02d" % [h, m]
+ h, m = minutes.abs.divmod(60)
+ sign = minutes.negative? ? '-' : ''
+ "%s%d:%02d" % [sign, h, m]
else
number_with_delimiter(sprintf('%.2f', minutes.fdiv(60)), delimiter: nil)
end
@@ -110,7 +111,7 @@ module Redmine
# will clash with the dot separator.
def normalize_float(value)
separator = ::I18n.t('number.format.separator')
- value.gsub(/[#{separator}]/, separator => '.')
+ value.to_s.gsub(/[#{separator}]/, separator => '.')
end
def day_name(day)
@@ -176,13 +177,11 @@ module Redmine
# Custom backend based on I18n::Backend::Simple with the following changes:
# * available_locales are determined by looking at translation file names
class Backend < ::I18n::Backend::Simple
- module Implementation
- # Get available locales from the translations filenames
- def available_locales
- @available_locales ||= begin
- redmine_locales = Dir[Rails.root / 'config' / 'locales' / '*.yml'].map { |f| File.basename(f, '.yml').to_sym }
- super & redmine_locales
- end
+ # Get available locales from the translations filenames
+ def available_locales
+ @available_locales ||= begin
+ redmine_locales = Dir[Rails.root / 'config' / 'locales' / '*.yml'].map { |f| File.basename(f, '.yml').to_sym }
+ super & redmine_locales
end
end
diff --git a/lib/redmine/sudo_mode.rb b/lib/redmine/sudo_mode.rb
index 39daaeeed..806d06788 100644
--- a/lib/redmine/sudo_mode.rb
+++ b/lib/redmine/sudo_mode.rb
@@ -153,7 +153,7 @@ module Redmine
# Before Filter which is used by the require_sudo_mode class method.
class SudoRequestFilter < Struct.new(:parameters, :request_methods)
def before(controller)
- method_matches = request_methods.blank? || request_methods.include?(controller.request.method_symbol)
+ method_matches = request_methods.blank? || request_methods.include?(controller.request.request_method_symbol)
if controller.api_request?
true
elsif SudoMode.possible? && method_matches
diff --git a/lib/redmine/version.rb b/lib/redmine/version.rb
index 5388c4b42..fa581b756 100644
--- a/lib/redmine/version.rb
+++ b/lib/redmine/version.rb
@@ -24,7 +24,7 @@ module Redmine
module VERSION
MAJOR = 6
MINOR = 0
- TINY = 2
+ TINY = 6
# Branch values:
# * official release: nil
diff --git a/lib/redmine/wiki_formatting/common_mark/sanitization_filter.rb b/lib/redmine/wiki_formatting/common_mark/sanitization_filter.rb
index cdefc372b..046866e05 100644
--- a/lib/redmine/wiki_formatting/common_mark/sanitization_filter.rb
+++ b/lib/redmine/wiki_formatting/common_mark/sanitization_filter.rb
@@ -86,7 +86,7 @@ module Redmine
node = env[:node]
return unless node.name == "a" || node.name == "li"
return unless node.has_attribute?("id")
- return if node.name == "a" && node["id"] =~ /\Afnref-\d+\z/
+ return if node.name == "a" && node["id"] =~ /\Afnref(-\d+){1,2}\z/
return if node.name == "li" && node["id"] =~ /\Afn-\d+\z/
node.remove_attribute("id")
diff --git a/lib/redmine/wiki_formatting/macros.rb b/lib/redmine/wiki_formatting/macros.rb
index 4de145c4c..16abcb429 100644
--- a/lib/redmine/wiki_formatting/macros.rb
+++ b/lib/redmine/wiki_formatting/macros.rb
@@ -53,7 +53,7 @@ module Redmine
send(method_name, obj, args)
end
rescue => e
- %|<div class="flash error">#{::I18n.t(:error_can_not_execute_macro_html, :name => name, :error => e.to_s)}</div>|.html_safe
+ %|<div class="flash error">#{::I18n.t(:error_can_not_execute_macro_html, :name => ::ERB::Util.h(name), :error => ::ERB::Util.h(e.to_s))}</div>|.html_safe
end
end
@@ -248,7 +248,7 @@ module Redmine
hide_label = args[1] || args[0] || l(:button_hide)
js = "$('##{html_id}-show, ##{html_id}-hide').toggle(); $('##{html_id}').fadeToggle(150);"
out = ''.html_safe
- out << link_to_function(sprite_icon('angle-right', show_label), js, :id => "#{html_id}-show", :class => 'icon icon-collapsed collapsible')
+ out << link_to_function(sprite_icon('angle-right', show_label, rtl: true), js, :id => "#{html_id}-show", :class => 'icon icon-collapsed collapsible')
out <<
link_to_function(
sprite_icon('angle-down', hide_label), js,
diff --git a/public/404.html b/public/404.html
index a1d7e970f..fe293183e 100644
--- a/public/404.html
+++ b/public/404.html
@@ -4,9 +4,9 @@
<meta charset="utf-8" />
<title>Redmine 404 error</title>
<style>
- body {font-family: "Trebuchet MS", Georgia, "Times New Roman", serif; color: #303030; margin: 10px;}
- h1 {font-size:1.5em;}
- p {font-size:0.8em;}
+ body {font-family: sans-serif; color: #303030; margin: 20px; line-height: 1.6;}
+ h1 {font-size: 1.5rem;}
+ p {font-size: 0.875rem;}
</style>
</head>
<body>
diff --git a/public/500.html b/public/500.html
index 254e20df2..91b804a7c 100644
--- a/public/500.html
+++ b/public/500.html
@@ -4,9 +4,9 @@
<meta charset="utf-8" />
<title>Redmine 500 error</title>
<style>
- body {font-family: "Trebuchet MS", Georgia, "Times New Roman", serif; color: #303030; margin: 10px;}
- h1 {font-size:1.5em;}
- p {font-size:0.8em;}
+ body {font-family: sans-serif; color: #303030; margin: 20px; line-height: 1.6;}
+ h1 {font-size: 1.5rem;}
+ p {font-size: 0.875rem;}
</style>
</head>
<body>
diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb
index 4a6fd0d30..38d69e7c8 100644
--- a/test/application_system_test_case.rb
+++ b/test/application_system_test_case.rb
@@ -43,6 +43,11 @@ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
driver_option.add_preference 'download.default_directory', DOWNLOADS_PATH.gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
driver_option.add_preference 'download.prompt_for_download', false
driver_option.add_preference 'plugins.plugins_disabled', ["Chrome PDF Viewer"]
+ # Disable "Change your password" popup shown after login due to leak detection
+ driver_option.add_preference 'profile.password_manager_leak_detection', false
+ # Disable password saving prompts
+ driver_option.add_preference 'profile.password_manager_enabled', false
+ driver_option.add_preference 'credentials_enable_service', false
end
setup do
@@ -68,13 +73,13 @@ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
# using default browser locale which depend on system locale for "real" browsers drivers
def log_user(login, password)
visit '/my/page'
- assert_equal '/login', current_path
+ assert_current_path '/login', :ignore_query => true
within('#login-form form') do
fill_in 'username', :with => login
fill_in 'password', :with => password
find('input[name=login]').click
end
- assert_equal '/my/page', current_path
+ assert_current_path '/my/page', :ignore_query => true
end
def wait_for_ajax
diff --git a/test/fixtures/changesets.yml b/test/fixtures/changesets.yml
index 247dda375..8eaca6788 100644
--- a/test/fixtures/changesets.yml
+++ b/test/fixtures/changesets.yml
@@ -102,3 +102,15 @@ changesets_010:
user_id: 3
repository_id: 10
committer: dlopper
+changesets_011:
+ commit_date: "2025-04-07"
+ comments: |-
+ This commit references an issue and a [[wiki]] page
+ Refs #2
+ committed_on: 2025-04-07 19:00:00
+ revision: "11"
+ id: 110
+ scmid:
+ user_id: 3
+ repository_id: 10
+ committer: dlopper
diff --git a/test/fixtures/queries.yml b/test/fixtures/queries.yml
index cc0ea220f..c7a4e276d 100644
--- a/test/fixtures/queries.yml
+++ b/test/fixtures/queries.yml
@@ -81,7 +81,7 @@ queries_005:
project_id:
visibility: 2
name: Open issues by priority and tracker
- description: Description for Oepn issues by priority and tracker
+ description: Description for Open issues by priority and tracker
filters: |
---
status_id:
diff --git a/test/functional/account_controller_test.rb b/test/functional/account_controller_test.rb
index e62dce943..31ba88fb5 100644
--- a/test/functional/account_controller_test.rb
+++ b/test/functional/account_controller_test.rb
@@ -658,4 +658,22 @@ class AccountControllerTest < Redmine::ControllerTest
end
end
end
+
+ def test_validate_back_url
+ request.host = 'example.com'
+
+ assert_equal '/admin', @controller.send(:validate_back_url, 'http://example.com/admin')
+ assert_equal '/admin', @controller.send(:validate_back_url, 'http://dlopper:foo@example.com/admin')
+ assert_equal '/issues?query_id=1#top', @controller.send(:validate_back_url, 'http://example.com/issues?query_id=1#top')
+ assert_equal false, @controller.send(:validate_back_url, 'http://invalid.example.com/issues')
+ end
+
+ def test_validate_back_url_with_port
+ request.host = 'example.com:3000'
+
+ assert_equal '/admin', @controller.send(:validate_back_url, 'http://example.com:3000/admin')
+ assert_equal '/admin', @controller.send(:validate_back_url, 'http://dlopper:foo@example.com:3000/admin')
+ assert_equal '/issues?query_id=1#top', @controller.send(:validate_back_url, 'http://example.com:3000/issues?query_id=1#top')
+ assert_equal false, @controller.send(:validate_back_url, 'http://invalid.example.com:3000/issues')
+ end
end
diff --git a/test/functional/attachments_controller_test.rb b/test/functional/attachments_controller_test.rb
index 04fdb15d2..c2e7e2f7b 100644
--- a/test/functional/attachments_controller_test.rb
+++ b/test/functional/attachments_controller_test.rb
@@ -42,7 +42,7 @@ class AttachmentsControllerTest < Redmine::ControllerTest
assert_response :success
assert_equal 'text/html', @response.media_type
- assert_select 'th.filename', :text => /issues_controller.rb\t\(révision 1484\)/
+ assert_select 'th.filename', :text => /issues_controller\.rb \(révision 1484\)/
assert_select 'td.line-code', :text => /Demande créée avec succès/
end
end
@@ -61,7 +61,7 @@ class AttachmentsControllerTest < Redmine::ControllerTest
assert_response :success
assert_equal 'text/html', @response.media_type
- assert_select 'th.filename', :text => /issues_controller.rb\t\(r\?vision 1484\)/
+ assert_select 'th.filename', :text => /issues_controller\.rb \(r\?vision 1484\)/
assert_select 'td.line-code', :text => /Demande cr\?\?e avec succ\?s/
end
end
@@ -81,7 +81,7 @@ class AttachmentsControllerTest < Redmine::ControllerTest
assert_response :success
assert_equal 'text/html', @response.media_type
- assert_select 'th.filename', :text => /issues_controller.rb\t\(révision 1484\)/
+ assert_select 'th.filename', :text => /issues_controller\.rb \(révision 1484\)/
assert_select 'td.line-code', :text => /Demande créée avec succès/
end
end
diff --git a/test/functional/documents_controller_test.rb b/test/functional/documents_controller_test.rb
index b59ecdc81..944f0b30f 100644
--- a/test/functional/documents_controller_test.rb
+++ b/test/functional/documents_controller_test.rb
@@ -113,9 +113,9 @@ class DocumentsControllerTest < Redmine::ControllerTest
# adds a long description to the first document
doc = documents(:documents_001)
doc.update(:description => <<~LOREM)
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut egestas, mi vehicula varius varius, ipsum massa fermentum orci, eget tristique ante sem vel mi. Nulla facilisi. Donec enim libero, luctus ac sagittis sit amet, vehicula sagittis magna. Duis ultrices molestie ante, eget scelerisque sem iaculis vitae. Etiam fermentum mauris vitae metus pharetra condimentum fermentum est pretium. Proin sollicitudin elementum quam quis pharetra. Aenean facilisis nunc quis elit volutpat mollis. Aenean eleifend varius euismod. Ut dolor est, congue eget dapibus eget, elementum eu odio. Integer et lectus neque, nec scelerisque nisi. EndOfLineHere
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut egestas, mi vehicula varius varius, ipsum massa fermentum orci, eget tristique ante sem vel mi. Nulla facilisi. Donec enim libero, luctus ac sagittis sit amet, vehicula sagittis magna. Duis ultrices molestie ante, eget scelerisque sem iaculis vitae. Etiam fermentum mauris vitae metus pharetra condimentum fermentum est pretium. Proin sollicitudin elementum quam quis pharetra. Aenean facilisis nunc quis elit volutpat mollis. Aenean eleifend varius euismod. Ut dolor est, congue eget dapibus eget, elementum eu odio. Integer et lectus neque, nec scelerisque nisi. EndOfLineHere
- Vestibulum non velit mi. Aliquam scelerisque libero ut nulla fringilla a sollicitudin magna rhoncus. Praesent a nunc lorem, ac porttitor eros. Sed ac diam nec neque interdum adipiscing quis quis justo. Donec arcu nunc, fringilla eu dictum at, venenatis ac sem. Vestibulum quis elit urna, ac mattis sapien. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Vestibulum non velit mi. Aliquam scelerisque libero ut nulla fringilla a sollicitudin magna rhoncus. Praesent a nunc lorem, ac porttitor eros. Sed ac diam nec neque interdum adipiscing quis quis justo. Donec arcu nunc, fringilla eu dictum at, venenatis ac sem. Vestibulum quis elit urna, ac mattis sapien. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
LOREM
get(:index, :params => {:project_id => 'ecookbook'})
assert_response :success
diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb
index 2ed9ac9ea..386705439 100644
--- a/test/functional/issues_controller_test.rb
+++ b/test/functional/issues_controller_test.rb
@@ -332,7 +332,7 @@ class IssuesControllerTest < Redmine::ControllerTest
# assert link properties
assert_select(
'a.query.selected[title=?][href=?]',
- 'Description for Oepn issues by priority and tracker',
+ 'Description for Open issues by priority and tracker',
'/projects/ecookbook/issues?query_id=5',
:text => "Open issues by priority and tracker"
)
@@ -1737,7 +1737,7 @@ class IssuesControllerTest < Redmine::ControllerTest
assert_select 'td.last_notes[colspan="4"]', :text => 'Some notes with Redmine links: #2, r2.'
assert_select(
'td.last_notes[colspan="4"]',
- :text => 'A comment with inline image: and a reference to #1 and r2.'
+ :text => 'A comment with inline image: and a reference to #1 and r2.'
)
get(
:index,
@@ -2003,12 +2003,34 @@ class IssuesControllerTest < Redmine::ControllerTest
def test_index_with_int_custom_field_total
field = IssueCustomField.generate!(:field_format => 'int', :is_for_all => true)
- CustomValue.create!(:customized => Issue.find(1), :custom_field => field, :value => '2')
- CustomValue.create!(:customized => Issue.find(2), :custom_field => field, :value => '7')
- get(:index, :params => {:t => ["cf_#{field.id}"]})
+ CustomValue.create!(:customized => Issue.find(1), :custom_field => field, :value => '9800')
+ CustomValue.create!(:customized => Issue.find(2), :custom_field => field, :value => '10')
+
+ field_with_delimiter = IssueCustomField.generate!(:field_format => 'int', :thousands_delimiter => '1', :is_for_all => true)
+ CustomValue.create!(:customized => Issue.find(1), :custom_field => field_with_delimiter, :value => '9800')
+ CustomValue.create!(:customized => Issue.find(2), :custom_field => field_with_delimiter, :value => '10')
+
+ get(:index, :params => {:t => ["cf_#{field.id}", "cf_#{field_with_delimiter.id}"]})
assert_response :success
assert_select '.query-totals'
- assert_select ".total-for-cf-#{field.id} span.value", :text => '9'
+ assert_select ".total-for-cf-#{field.id} span.value", :text => '9810'
+ assert_select ".total-for-cf-#{field_with_delimiter.id} span.value", :text => '9,810'
+ end
+
+ def test_index_with_float_custom_field_total
+ field = IssueCustomField.generate!(field_format: 'float', is_for_all: true)
+ CustomValue.create!(customized: Issue.find(1), custom_field: field, value: '1000000.01')
+ CustomValue.create!(customized: Issue.find(2), custom_field: field, value: '99.01')
+
+ field_with_delimiter = IssueCustomField.generate!(field_format: 'float', thousands_delimiter: '1', is_for_all: true)
+ CustomValue.create!(customized: Issue.find(1), custom_field: field_with_delimiter, value: '1000000.01')
+ CustomValue.create!(customized: Issue.find(2), custom_field: field_with_delimiter, value: '99.01')
+
+ get(:index, params: {t: ["cf_#{field.id}", "cf_#{field_with_delimiter.id}"]})
+ assert_response :success
+ assert_select '.query-totals'
+ assert_select ".total-for-cf-#{field.id} span.value", text: '1000099.02'
+ assert_select ".total-for-cf-#{field_with_delimiter.id} span.value", text: '1,000,099.02'
end
def test_index_with_spent_time_total_should_sum_visible_spent_time_only
@@ -3233,6 +3255,22 @@ class IssuesControllerTest < Redmine::ControllerTest
end
end
+ def test_show_render_changeset_comments_in_original_context
+ issue = Issue.find(9)
+ issue.changeset_ids = [110]
+ issue.save!
+
+ @request.session[:user_id] = 2
+ get :issue_tab, params: {id: issue.id, name: 'changesets', format: 'js'}, xhr: true
+
+ assert_select 'div#changeset-110' do
+ # assert_select 'div.tabs a[id=?]', 'tab-changesets', text: 'unicorns'
+ assert_select 'div.changeset-comments' do
+ assert_select 'a[href=?]', '/projects/ecookbook/wiki/Wiki', text: 'wiki'
+ end
+ end
+ end
+
def test_show_should_display_spent_time_tab_for_issue_with_time_entries
@request.session[:user_id] = 1
get :show, :params => {:id => 3}
diff --git a/test/functional/queries_controller_test.rb b/test/functional/queries_controller_test.rb
index 28423366e..4f0827da8 100644
--- a/test/functional/queries_controller_test.rb
+++ b/test/functional/queries_controller_test.rb
@@ -600,11 +600,11 @@ class QueriesControllerTest < Redmine::ControllerTest
def test_create_admin_projects_query_should_redirect_to_admin_projects
@request.session[:user_id] = 1
- q = new_record(ProjectQuery) do
+ q = new_record(ProjectAdminQuery) do
post(
:create,
:params => {
- :type => 'ProjectQuery',
+ :type => 'ProjectAdminQuery',
:default_columns => '1',
:f => ["status"],
:op => {
@@ -615,13 +615,12 @@ class QueriesControllerTest < Redmine::ControllerTest
},
:query => {
"name" => "test_new_project_public_query", "visibility" => "2"
- },
- :admin_projects => 1
+ }
}
)
end
- assert_redirected_to :controller => 'admin', :action => 'projects', :query_id => q.id, :admin_projects => 1
+ assert_redirected_to :controller => 'admin', :action => 'projects', :query_id => q.id
end
def test_edit_global_public_query
@@ -676,7 +675,7 @@ class QueriesControllerTest < Redmine::ControllerTest
get(:edit, :params => {:id => 5})
assert_response :success
- assert_select 'input[name="query[description]"][value=?]', 'Description for Oepn issues by priority and tracker'
+ assert_select 'input[name="query[description]"][value=?]', 'Description for Open issues by priority and tracker'
end
def test_edit_invalid_query
@@ -738,7 +737,7 @@ class QueriesControllerTest < Redmine::ControllerTest
end
def test_update_admin_projects_query
- q = ProjectQuery.create(:name => 'project_query')
+ q = ProjectAdminQuery.create(:name => 'project_query')
@request.session[:user_id] = 1
put(
@@ -755,12 +754,11 @@ class QueriesControllerTest < Redmine::ControllerTest
},
:query => {
"name" => "test_project_query_updated", "visibility" => "2"
- },
- :admin_projects => 1
+ }
}
)
- assert_redirected_to :controller => 'admin', :action => 'projects', :query_id => q.id, :admin_projects => 1
+ assert_redirected_to :controller => 'admin', :action => 'projects', :query_id => q.id
assert Query.find_by_name('test_project_query_updated')
end
@@ -1032,4 +1030,44 @@ class QueriesControllerTest < Redmine::ControllerTest
assert_include ["Development", "10"], json
assert_include ["Inactive Activity", "14"], json
end
+
+ def test_new_query_is_for_all_checkbox_not_disabled
+ @request.session[:user_id] = 1
+ get :new
+ assert_response :success
+ # Verify that the "For all projects" checkbox is not disabled when creating a new query
+ assert_select 'input[name=query_is_for_all][type=checkbox][checked]:not([disabled])'
+ end
+
+ def test_new_project_query_is_for_all_checkbox_not_disabled
+ @request.session[:user_id] = 1
+ get(:new, :params => {:project_id => 1})
+ assert_response :success
+ # Verify that the checkbox is not disabled when creating a new query within a project
+ assert_select 'input[name=query_is_for_all][type=checkbox]:not([checked]):not([disabled])'
+ end
+
+ def test_edit_global_query_is_for_all_checkbox_disabled
+ @request.session[:user_id] = 1
+ # Create a global query (project_id = nil)
+ query = IssueQuery.create!(:name => 'test_global_query', :user_id => 1, :project_id => nil)
+
+ get(:edit, :params => {:id => query.id})
+ assert_response :success
+
+ # Verify that the "For all projects" checkbox is disabled when editing an existing global query
+ assert_select 'input[name=query_is_for_all][type=checkbox][checked][disabled]'
+ end
+
+ def test_edit_project_query_is_for_all_checkbox_not_disabled
+ @request.session[:user_id] = 1
+ # Create a project-specific query
+ query = IssueQuery.create!(:name => 'test_project_query', :user_id => 1, :project_id => 1)
+
+ get(:edit, :params => {:id => query.id})
+ assert_response :success
+
+ # Verify that the checkbox is not disabled when editing a project-specific query
+ assert_select 'input[name=query_is_for_all][type=checkbox]:not([checked]):not([disabled])'
+ end
end
diff --git a/test/functional/repositories_bazaar_controller_test.rb b/test/functional/repositories_bazaar_controller_test.rb
index 2122bf3b9..34e70348d 100644
--- a/test/functional/repositories_bazaar_controller_test.rb
+++ b/test/functional/repositories_bazaar_controller_test.rb
@@ -37,6 +37,7 @@ class RepositoriesBazaarControllerTest < Redmine::RepositoryControllerTest
:log_encoding => 'UTF-8'
)
assert @repository
+ skip "SCM command is unavailable" unless @repository.class.scm_available
end
if File.directory?(REPOSITORY_PATH)
diff --git a/test/functional/repositories_controller_test.rb b/test/functional/repositories_controller_test.rb
index beae44f68..7937e59e6 100644
--- a/test/functional/repositories_controller_test.rb
+++ b/test/functional/repositories_controller_test.rb
@@ -186,6 +186,7 @@ class RepositoriesControllerTest < Redmine::RepositoryControllerTest
def test_show_without_main_repository_should_display_first_repository
skip unless repository_configured?('subversion')
+ skip unless Repository::Subversion.scm_available
project = Project.find(1)
repos = project.repositories
@@ -208,6 +209,7 @@ class RepositoriesControllerTest < Redmine::RepositoryControllerTest
def test_show_should_show_diff_button_depending_on_browse_repository_permission
skip unless repository_configured?('subversion')
+ skip unless Repository::Subversion.scm_available
@request.session[:user_id] = 2
role = Role.find(1)
diff --git a/test/functional/repositories_cvs_controller_test.rb b/test/functional/repositories_cvs_controller_test.rb
index 558e58d0a..bb30ebc19 100644
--- a/test/functional/repositories_cvs_controller_test.rb
+++ b/test/functional/repositories_cvs_controller_test.rb
@@ -40,6 +40,7 @@ class RepositoriesCvsControllerTest < Redmine::RepositoryControllerTest
:url => MODULE_NAME,
:log_encoding => 'UTF-8')
assert @repository
+ skip "SCM command is unavailable" unless @repository.class.scm_available
end
if File.directory?(REPOSITORY_PATH)
diff --git a/test/functional/repositories_git_controller_test.rb b/test/functional/repositories_git_controller_test.rb
index 32fa90e29..cb8788a03 100644
--- a/test/functional/repositories_git_controller_test.rb
+++ b/test/functional/repositories_git_controller_test.rb
@@ -41,6 +41,7 @@ class RepositoriesGitControllerTest < Redmine::RepositoryControllerTest
:path_encoding => 'ISO-8859-1'
)
assert @repository
+ skip "SCM command is unavailable" unless @repository.class.scm_available
end
def test_create_and_update
diff --git a/test/functional/repositories_mercurial_controller_test.rb b/test/functional/repositories_mercurial_controller_test.rb
index 4af2fcf99..f5f0e034f 100644
--- a/test/functional/repositories_mercurial_controller_test.rb
+++ b/test/functional/repositories_mercurial_controller_test.rb
@@ -37,6 +37,8 @@ class RepositoriesMercurialControllerTest < Redmine::RepositoryControllerTest
:path_encoding => 'ISO-8859-1'
)
assert @repository
+ skip "SCM command is unavailable" unless @repository.class.scm_available
+
@diff_c_support = true
end
diff --git a/test/functional/repositories_subversion_controller_test.rb b/test/functional/repositories_subversion_controller_test.rb
index 60dd213e4..0a430317f 100644
--- a/test/functional/repositories_subversion_controller_test.rb
+++ b/test/functional/repositories_subversion_controller_test.rb
@@ -34,6 +34,7 @@ class RepositoriesSubversionControllerTest < Redmine::RepositoryControllerTest
@repository = Repository::Subversion.create(:project => @project,
:url => self.class.subversion_repository_url)
assert @repository
+ skip "SCM command is unavailable" unless @repository.class.scm_available
end
if repository_configured?('subversion')
@@ -357,6 +358,27 @@ class RepositoriesSubversionControllerTest < Redmine::RepositoryControllerTest
assert_equal "attachment; filename=\"helloworld.c\"; filename*=UTF-8''helloworld.c", @response.headers['Content-Disposition']
end
+ def test_entry_should_return_text_plain_for_js_files
+ # JavaScript files should be served as 'text/plain' instead of
+ # 'application/javascript' to avoid
+ # ActionController::InvalidCrossOriginRequest exception
+ assert_equal 0, @repository.changesets.count
+ @repository.fetch_changesets
+ @project.reload
+ assert_equal NUM_REV, @repository.changesets.count
+ get(
+ :raw,
+ :params => {
+ :id => PRJ_ID,
+ :repository_id => @repository.id,
+ :path => repository_path_hash(['subversion_test', 'foo.js'])[:param]
+ }
+ )
+ assert_response :success
+ assert_equal 'text/plain', @response.media_type
+ assert_match /attachment/, @response.headers['Content-Disposition']
+ end
+
def test_directory_entry
assert_equal 0, @repository.changesets.count
@repository.fetch_changesets
diff --git a/test/functional/search_controller_test.rb b/test/functional/search_controller_test.rb
index 1a624c3c7..5e32e5656 100644
--- a/test/functional/search_controller_test.rb
+++ b/test/functional/search_controller_test.rb
@@ -66,16 +66,18 @@ class SearchControllerTest < Redmine::ControllerTest
assert_response :success
assert_select '#search-results' do
- assert_select 'dt.issue a', :text => /Feature request #2/
+ assert_select 'dt.issue a', :text => /Bug #1/
assert_select 'dt.issue a', :text => /Bug #5/
assert_select 'dt.changeset a', :text => /Revision 1/
- assert_select 'dt.issue a', :text => /Add ingredients categories/
- assert_select 'dd', :text => /should be classified by categories/
+ assert_select 'dt.issue a', :text => /Cannot print recipes/
+ assert_select 'dd', :text => /Unable to print/
end
assert_select '#search-results-counts' do
- assert_select 'a', :text => 'Changesets (5)'
+ assert_select 'a', :text => 'Changesets (6)'
+ assert_select 'a', :text => 'Issues (5)'
+ assert_select 'a', :text => 'Projects (4)'
end
end
diff --git a/test/functional/workflows_controller_test.rb b/test/functional/workflows_controller_test.rb
index b30559d80..dcdc8d5bb 100644
--- a/test/functional/workflows_controller_test.rb
+++ b/test/functional/workflows_controller_test.rb
@@ -211,6 +211,45 @@ class WorkflowsControllerTest < Redmine::ControllerTest
assert w.assignee
end
+ def test_post_edit_with_large_number_of_statuses
+ # This test ensures that workflows with many statuses can be saved.
+ # Without setting `ENV['RACK_QUERY_PARSER_PARAMS_LIMIT']`, this raises
+ # ActionController::BadRequest exception due to exceeding the default
+ # query parameter limit of 4096.
+ WorkflowTransition.delete_all
+
+ num_statuses = 40
+ transitions_data = {}
+
+ # Allowed statuses for a new issue (status_id = 0)
+ transitions_data['0'] = {}
+ (1..num_statuses).each do |status_id|
+ transitions_data['0'][status_id.to_s] = {'always' => '1'}
+ end
+
+ # Status transitions between statuses
+ (1..num_statuses).each do |status_id_from| # rubocop:disable RuboCopStyle/CombinableLoops
+ transitions_data[status_id_from.to_s] = {}
+ (1..num_statuses).each do |status_id_to|
+ # skip self-transitions
+ next if status_id_from == status_id_to
+
+ transitions_data[status_id_from.to_s][status_id_to.to_s] = {
+ 'always' => '1', 'author' => '1', 'assignee' => '1'
+ }
+ end
+ end
+
+ assert_nothing_raised do
+ patch :update, :params => {
+ :role_id => 2,
+ :tracker_id => 1,
+ :transitions => transitions_data
+ }
+ end
+ assert_response :found
+ end
+
def test_get_permissions
get :permissions
diff --git a/test/helpers/application_helper_test.rb b/test/helpers/application_helper_test.rb
index 036ef774e..7c3950405 100644
--- a/test/helpers/application_helper_test.rb
+++ b/test/helpers/application_helper_test.rb
@@ -1732,6 +1732,46 @@ class ApplicationHelperTest < Redmine::HelperTest
end
end
+ def test_section_edit_links_with_multiline_heading
+ raw = <<~RAW
+ # Wiki
+
+ ## `Foo` Bar
+
+ The heading above generates multiline HTML.
+ Don't assume heading tags are always single-line.
+
+ ```
+ <h2>
+ <code>Foo</code> Bar</h2>
+ ```
+ RAW
+ @project = Project.find(1)
+ set_language_if_valid 'en'
+ with_settings :text_formatting => 'common_mark' do
+ result =
+ textilizable(
+ raw,
+ :edit_section_links =>
+ {:controller => 'wiki', :action => 'edit',
+ :project_id => '1', :id => 'Test'}
+ ).delete("\n")
+
+ assert_match(
+ Regexp.new(
+ '<div class="contextual heading-2" title="Edit this section" id="section-2">' \
+ '<a class="icon-only icon-edit" href="/projects/1/wiki/Test/edit\?section=2">' \
+ '<svg class="s18 icon-svg" aria-hidden="true"><use href="/assets/icons-.*\.svg#icon--edit"></use></svg>' \
+ '<span class="icon-label">Edit this section</span>' \
+ '</a></div>' \
+ '<a name="Foo-Bar"></a>' \
+ '<h2 ><code>Foo</code> Bar<a href="#Foo-Bar" class="wiki-anchor">&para;</a></h2>'
+ ),
+ result
+ )
+ end
+ end
+
def test_default_formatter
with_settings :text_formatting => 'unknown' do
text = 'a *link*: http://www.example.net/'
@@ -1921,8 +1961,8 @@ class ApplicationHelperTest < Redmine::HelperTest
a = Attachment.find(3)
assert_select_in(
thumbnail_tag(a),
- 'a[href=?] img[title=?][src=?][loading="lazy"]',
- "/attachments/3", "logo.gif", "/attachments/thumbnail/3/200")
+ 'a[href=?] img[title=?][alt=?][src=?][loading="lazy"]',
+ "/attachments/3", "logo.gif", "logo.gif", "/attachments/thumbnail/3/200")
end
def test_link_to_project
@@ -2209,11 +2249,15 @@ class ApplicationHelperTest < Redmine::HelperTest
set_language_if_valid 'en'
with_settings :timespan_format => 'minutes' do
+ assert_equal '-0:45', format_hours(-0.75)
+ assert_equal '0:00', format_hours(0)
assert_equal '0:45', format_hours(0.75)
assert_equal '0:45 h', l_hours_short(0.75)
assert_equal '0:45 hour', l_hours(0.75)
end
with_settings :timespan_format => 'decimal' do
+ assert_equal '-0.75', format_hours(-0.75)
+ assert_equal '0.00', format_hours(0)
assert_equal '0.75', format_hours(0.75)
assert_equal '0.75 h', l_hours_short(0.75)
assert_equal '0.75 hour', l_hours(0.75)
diff --git a/test/integration/api_test/attachments_test.rb b/test/integration/api_test/attachments_test.rb
index 9bb079c3d..524399bdc 100644
--- a/test/integration/api_test/attachments_test.rb
+++ b/test/integration/api_test/attachments_test.rb
@@ -63,7 +63,7 @@ class Redmine::ApiTest::AttachmentsTest < Redmine::ApiTest::Base
test "GET /attachments/download/:id/:filename should deny access without credentials" do
get '/attachments/download/7/archive.zip'
- assert_response :unauthorized
+ assert_response :redirect
end
test "GET /attachments/thumbnail/:id should return the thumbnail" do
diff --git a/test/integration/api_test/custom_fields_test.rb b/test/integration/api_test/custom_fields_test.rb
index 0df56e59a..4fb06636e 100644
--- a/test/integration/api_test/custom_fields_test.rb
+++ b/test/integration/api_test/custom_fields_test.rb
@@ -37,6 +37,8 @@ class Redmine::ApiTest::CustomFieldsTest < Redmine::ApiTest::Base
end
assert_select 'trackers[type=array]'
assert_select 'roles[type=array]'
+ assert_select 'visible', :text => 'true'
+ assert_select 'editable', :text => 'true'
end
end
end
diff --git a/test/integration/api_test/news_test.rb b/test/integration/api_test/news_test.rb
index bd9f2bb6d..399b2b347 100644
--- a/test/integration/api_test/news_test.rb
+++ b/test/integration/api_test/news_test.rb
@@ -62,7 +62,7 @@ class Redmine::ApiTest::NewsTest < Redmine::ApiTest::Base
assert_select "author[id=2][name=\"John Smith\"]"
assert_select 'title', 'eCookbook first release !'
assert_select 'summary', 'First version was released...'
- assert_select 'description', "eCookbook 1.0 has been released.\n\nVisit http://ecookbook.somenet.foo/"
+ assert_select 'description', 'eCookbook 1.0 has been released. Visit http://ecookbook.somenet.foo/'
assert_select 'created_on', News.find(1).created_on.iso8601
end
end
diff --git a/test/integration/attachments_test.rb b/test/integration/attachments_test.rb
index f6a30d061..80d2040a1 100644
--- a/test/integration/attachments_test.rb
+++ b/test/integration/attachments_test.rb
@@ -267,6 +267,16 @@ class AttachmentsTest < Redmine::IntegrationTest
end
end
+ def test_unauthorized_named_download_link_should_redirect_to_login
+ with_settings login_required: '1' do
+ get "/attachments/download/1"
+ assert_redirected_to "/login?back_url=http%3A%2F%2Fwww.example.com%2Fattachments%2Fdownload%2F1"
+
+ get "/attachments/download/1/error281.txt"
+ assert_redirected_to "/login?back_url=http%3A%2F%2Fwww.example.com%2Fattachments%2Fdownload%2F1%2Ferror281.txt"
+ end
+ end
+
private
def ajax_upload(filename, content, attachment_id=1)
diff --git a/test/integration/repositories_git_test.rb b/test/integration/repositories_git_test.rb
index 20d643449..793b49458 100644
--- a/test/integration/repositories_git_test.rb
+++ b/test/integration/repositories_git_test.rb
@@ -35,6 +35,7 @@ class RepositoriesGitTest < Redmine::IntegrationTest
:path_encoding => 'ISO-8859-1'
)
assert @repository
+ skip "SCM command is unavailable" unless @repository.class.scm_available
end
if File.directory?(REPOSITORY_PATH)
diff --git a/test/integration/routing/attachments_test.rb b/test/integration/routing/attachments_test.rb
index 15e61635b..18b411f99 100644
--- a/test/integration/routing/attachments_test.rb
+++ b/test/integration/routing/attachments_test.rb
@@ -26,7 +26,7 @@ class RoutingAttachmentsTest < Redmine::RoutingTest
should_route 'GET /attachments/1/filename.txt' => 'attachments#show', :id => '1', :filename => 'filename.txt', :format => 'html'
should_route 'GET /attachments/download/1' => 'attachments#download', :id => '1'
- should_route 'GET /attachments/download/1/filename.ext' => 'attachments#download', :id => '1', :filename => 'filename.ext'
+ should_route 'GET /attachments/download/1/filename.ext' => 'attachments#download', :id => '1', :filename => 'filename.ext', :format => 'html'
should_route 'GET /attachments/thumbnail/1' => 'attachments#thumbnail', :id => '1'
should_route 'GET /attachments/thumbnail/1/200' => 'attachments#thumbnail', :id => '1', :size => '200'
diff --git a/test/integration/sudo_mode_test.rb b/test/integration/sudo_mode_test.rb
index 228e527ed..9fbbfd725 100644
--- a/test/integration/sudo_mode_test.rb
+++ b/test/integration/sudo_mode_test.rb
@@ -190,7 +190,7 @@ class SudoModeTest < Redmine::IntegrationTest
expire_sudo_mode!
get '/my/account'
assert_response :success
- put('/my/account', :params => {:user => {:mail => 'newmail@test.com'}})
+ post('/my/account', :params => {:_method => 'put', :user => {:mail => 'newmail@test.com'}})
assert_response :success
assert_select 'h2', 'Confirm your password to continue'
assert_select 'form[action="/my/account"]'
diff --git a/test/system/inline_autocomplete_test.rb b/test/system/inline_autocomplete_test.rb
index 4b2271b79..9bd5ac25c 100644
--- a/test/system/inline_autocomplete_test.rb
+++ b/test/system/inline_autocomplete_test.rb
@@ -40,8 +40,9 @@ class InlineAutocompleteSystemTest < ApplicationSystemTestCase
log_user('admin', 'admin')
visit 'projects/ecookbook/issues/new'
- fill_in 'Description', :with => '##Closed'
+ fill_in 'Description', :with => '##Cl'
+ assert_selector '.tribute-container li', count: 3
within('.tribute-container') do
assert page.has_text? 'Bug #12: Closed issue on a locked version'
assert page.has_text? 'Bug #11: Closed issue on a closed version'
@@ -57,12 +58,13 @@ class InlineAutocompleteSystemTest < ApplicationSystemTestCase
log_user('jsmith', 'jsmith')
visit 'issues/new'
- fill_in 'Description', :with => '#Closed'
+ fill_in 'Description', :with => '#Cl'
+ assert_selector '.tribute-container li', count: 3
within('.tribute-container') do
assert page.has_text? 'Bug #12: Closed issue on a locked version'
assert page.has_text? 'Bug #11: Closed issue on a closed version'
- assert_not page.has_text? 'Bug #1: Cannot print recipes'
+ assert page.has_text? 'Bug #8: Closed issue'
end
end
@@ -75,7 +77,7 @@ class InlineAutocompleteSystemTest < ApplicationSystemTestCase
fill_in 'Description', :with => '#'
end
- page.has_css?('.tribute-container li', minimum: 1)
+ assert_selector '.tribute-container li', minimum: 1
end
def test_inline_autocomplete_on_issue_edit_notes_should_show_autocomplete
@@ -86,7 +88,7 @@ class InlineAutocompleteSystemTest < ApplicationSystemTestCase
find('#issue_notes').click
fill_in 'issue[notes]', :with => '#'
- page.has_css?('.tribute-container li', minimum: 1)
+ assert_selector '.tribute-container li', minimum: 1
end
def test_inline_autocomplete_on_issue_custom_field_with_full_text_formatting_should_show_autocomplete
@@ -101,7 +103,7 @@ class InlineAutocompleteSystemTest < ApplicationSystemTestCase
fill_in 'Full width field', :with => '#'
- page.has_css?('.tribute-container li', minimum: 1)
+ assert_selector '.tribute-container li', minimum: 1
end
def test_inline_autocomplete_on_wiki_should_show_autocomplete
@@ -112,7 +114,7 @@ class InlineAutocompleteSystemTest < ApplicationSystemTestCase
find('.wiki-edit').click
fill_in 'content[text]', :with => '#'
- page.has_css?('.tribute-container li', minimum: 1)
+ assert_selector '.tribute-container li', minimum: 1
end
def test_inline_autocomplete_on_news_description_should_show_autocomplete
@@ -125,7 +127,7 @@ class InlineAutocompleteSystemTest < ApplicationSystemTestCase
find('.wiki-edit').click
fill_in 'Description', :with => '#'
- page.has_css?('.tribute-container li', minimum: 1)
+ assert_selector '.tribute-container li', minimum: 1
end
def test_inline_autocomplete_on_new_message_description_should_show_autocomplete
@@ -138,7 +140,7 @@ class InlineAutocompleteSystemTest < ApplicationSystemTestCase
find('.wiki-edit').click
fill_in 'message[content]', :with => '#'
- page.has_css?('.tribute-container li', minimum: 1)
+ assert_selector '.tribute-container li', minimum: 1
end
def test_inline_autocompletion_of_wiki_page_links
@@ -152,10 +154,14 @@ class InlineAutocompleteSystemTest < ApplicationSystemTestCase
assert page.has_text? 'Page_with_sections'
end
- fill_in 'Description', :with => '[[page'
+ fill_in 'Description', :with => '[[p'
+
+ assert_selector '.tribute-container li', count: 3
within('.tribute-container') do
assert page.has_text? 'Page_with_sections'
+ assert page.has_text? 'Page_with_an_inline_image'
assert page.has_text? 'Another_page'
+
assert_not page.has_text? 'Child_1_1'
first('li').click
@@ -169,8 +175,9 @@ class InlineAutocompleteSystemTest < ApplicationSystemTestCase
log_user('jsmith', 'jsmith')
visit 'projects/1/issues/new'
- fill_in 'Description', :with => '#This'
+ fill_in 'Description', :with => '#Th'
+ assert_selector '.tribute-container li', count: 1
within('.tribute-container') do
assert page.has_text? "Bug ##{issue.id}: This issue has a <select> element"
end
@@ -181,19 +188,18 @@ class InlineAutocompleteSystemTest < ApplicationSystemTestCase
visit '/issues/1/edit'
find('#issue_notes').click
- fill_in 'issue[notes]', :with => '@lopper'
+ fill_in 'issue[notes]', :with => '@'
- within('.tribute-container') do
- assert page.has_text? "Dave Lopper"
- end
+ assert_selector '.tribute-container li', minimum: 1
page.find('#issue_status_id').select('Feedback')
find('#issue_notes').click
- fill_in 'issue[notes]', :with => '@lopper'
+ fill_in 'issue[notes]', :with => '@j'
+ assert_selector '.tribute-container li', count: 1
within('.tribute-container') do
- assert page.has_text? "Dave Lopper"
+ assert page.has_text? 'John Smith'
end
end
@@ -202,14 +208,15 @@ class InlineAutocompleteSystemTest < ApplicationSystemTestCase
visit '/issues/bulk_edit?ids[]=1&ids[]=2'
find('#notes').click
- fill_in 'notes', :with => '@lopper'
+ fill_in 'notes', :with => '@j'
+ assert_selector '.tribute-container li', count: 1
within('.tribute-container') do
- assert page.has_text? 'Dave Lopper'
+ assert page.has_text? 'John Smith'
first('li').click
end
- assert_equal '@dlopper ', find('#notes').value
+ assert_equal '@jsmith ', find('#notes').value
end
def test_inline_autocomplete_for_users_on_issues_without_edit_issue_permission
diff --git a/test/system/issues_test.rb b/test/system/issues_test.rb
index c23cbd27c..c161538e7 100644
--- a/test/system/issues_test.rb
+++ b/test/system/issues_test.rb
@@ -34,6 +34,8 @@ class IssuesSystemTest < ApplicationSystemTestCase
find('input[name=commit]').click
end
+ assert_text /Issue #\d+ created./
+
# find created issue
issue = Issue.find_by_subject("new test issue")
assert_kind_of Issue, issue
@@ -86,6 +88,7 @@ class IssuesSystemTest < ApplicationSystemTestCase
fill_in field2.name, :with => 'CF2 value'
assert_difference 'Issue.count' do
page.first(:button, 'Create').click
+ assert_text /Issue #\d+ created./
end
issue = Issue.order('id desc').first
@@ -125,6 +128,7 @@ class IssuesSystemTest < ApplicationSystemTestCase
end
assert_difference 'Issue.count' do
find('input[name=commit]').click
+ assert_text /Issue #\d+ created./
end
issue = Issue.order('id desc').first
@@ -141,6 +145,7 @@ class IssuesSystemTest < ApplicationSystemTestCase
attach_file 'attachments[dummy][file]', Rails.root.join('test/fixtures/files/testfile.txt')
fill_in 'attachments[1][description]', :with => 'Some description'
click_on 'Create'
+ assert_text /Issue #\d+ created./
end
assert_equal 1, issue.attachments.count
assert_equal 'Some description', issue.attachments.first.description
@@ -163,6 +168,7 @@ class IssuesSystemTest < ApplicationSystemTestCase
attach_file 'attachments[dummy][file]', Rails.root.join('test/fixtures/files/testfile.txt')
fill_in 'attachments[1][description]', :with => 'Some description'
click_on 'Create'
+ assert_text /Issue #\d+ created./
end
assert_equal 1, issue.attachments.count
assert_equal 'Some description', issue.attachments.first.description
@@ -181,6 +187,7 @@ class IssuesSystemTest < ApplicationSystemTestCase
click_on 'Create'
end
click_on 'Create'
+ assert_text /Issue #\d+ created./
end
end
@@ -200,6 +207,7 @@ class IssuesSystemTest < ApplicationSystemTestCase
end
assert_difference 'Issue.count' do
click_button('Create')
+ assert_text /Issue #\d+ created./
end
issue = Issue.order('id desc').first
@@ -230,6 +238,7 @@ class IssuesSystemTest < ApplicationSystemTestCase
fill_in 'Form update CF', :with => 'CF value'
assert_no_difference 'Issue.count' do
page.first(:button, 'Submit').click
+ assert_text 'Successful update.'
end
assert page.has_css?('#flash_notice')
issue = Issue.find(1)
@@ -245,6 +254,7 @@ class IssuesSystemTest < ApplicationSystemTestCase
page.find("#issue_status_id").select("Closed")
assert_no_difference 'Issue.count' do
page.first(:button, 'Submit').click
+ assert_text 'Successful update.'
end
assert page.has_css?('#flash_notice')
assert_equal 5, issue.reload.status.id
@@ -267,6 +277,7 @@ class IssuesSystemTest < ApplicationSystemTestCase
click_on 'Submit'
+ assert_text 'Successful update.'
assert_equal 3, Issue.find(2).attachments.count
end
@@ -497,8 +508,9 @@ class IssuesSystemTest < ApplicationSystemTestCase
assert_equal 'Copy', submit_buttons[0].value
page.find('#issue_project_id').select('OnlineStore')
- # wait for ajax response
- assert page.has_select?('issue_project_id', selected: 'OnlineStore')
+ # Verify that the target version field has been rewritten by the OnlineStore project settings
+ # and wait for the project change to complete.
+ assert_select 'issue_fixed_version_id', options: ['(No change)', 'none', 'Alpha', 'Systemwide visible version']
assert_selector 'input[type=submit]', count: 2
submit_buttons = page.all('input[type=submit]')
diff --git a/test/system/sudo_mode_test.rb b/test/system/sudo_mode_test.rb
index 73e755acd..307d465ff 100644
--- a/test/system/sudo_mode_test.rb
+++ b/test/system/sudo_mode_test.rb
@@ -48,7 +48,6 @@ class SudoModeSystemTest < ApplicationSystemTestCase
find('input[name=commit]').click
end
- assert_equal '/users', current_path
assert page.has_content?("Confirm your password to continue")
assert page.has_css?('form#sudo-form')
@@ -56,6 +55,8 @@ class SudoModeSystemTest < ApplicationSystemTestCase
fill_in 'Password', :with => 'admin'
click_button 'Submit'
end
+
+ assert_text /User johnpaul created./
end
end
diff --git a/test/system/timelog_test.rb b/test/system/timelog_test.rb
index 57c521096..38c3ae19c 100644
--- a/test/system/timelog_test.rb
+++ b/test/system/timelog_test.rb
@@ -49,6 +49,8 @@ class TimelogTest < ApplicationSystemTestCase
select 'QA', :from => 'Activity'
page.first(:button, 'Submit').click
+ assert_text 'Successful update.'
+
entries = TimeEntry.where(:id => [1, 2, 3]).to_a
assert entries.all? {|entry| entry.hours == 8.5}
assert entries.all? {|entry| entry.activity.name == 'QA'}
@@ -89,6 +91,7 @@ class TimelogTest < ApplicationSystemTestCase
select 'Tracker', :from => 'Available Columns'
page.first('input[type=button].move-right').click
click_on 'Save'
+ assert_text 'Successful update.'
# Display the list with updated settings
visit '/time_entries'
diff --git a/test/unit/changeset_test.rb b/test/unit/changeset_test.rb
index ca1e010e3..3ad8b1cbf 100644
--- a/test/unit/changeset_test.rb
+++ b/test/unit/changeset_test.rb
@@ -479,7 +479,7 @@ class ChangesetTest < ActiveSupport::TestCase
end
def test_next_nil
- changeset = Changeset.find_by_revision('10')
+ changeset = Changeset.find_by_revision('11')
assert_nil changeset.next
end
diff --git a/test/unit/email_address_test.rb b/test/unit/email_address_test.rb
index 9d57beb97..923df897a 100644
--- a/test/unit/email_address_test.rb
+++ b/test/unit/email_address_test.rb
@@ -63,6 +63,12 @@ class EmailAddressTest < ActiveSupport::TestCase
end
end
+ def test_domain_in_should_not_raise_exception_when_domain_is_nil
+ assert_nothing_raised do
+ assert_not EmailAddress.domain_in?(nil, 'example.com')
+ end
+ end
+
def test_should_reject_invalid_email
assert_not EmailAddress.new(address: 'invalid,email@example.com').valid?
end
diff --git a/test/unit/issue_priority_test.rb b/test/unit/issue_priority_test.rb
index e076afe67..80dc11e1c 100644
--- a/test/unit/issue_priority_test.rb
+++ b/test/unit/issue_priority_test.rb
@@ -156,4 +156,20 @@ class IssuePriorityTest < ActiveSupport::TestCase
IssuePriority.find_by_position_name('highest').destroy
assert_equal %w(lowest default high2 highest), IssuePriority.active.to_a.sort.map(&:position_name)
end
+
+ def test_high_should_return_false_when_no_default_priority_and_no_active_priorities
+ IssuePriority.update_all(active: false, is_default: false)
+ priority = IssuePriority.order(:position).last # Highest priority
+ assert_nothing_raised do
+ assert_equal false, priority.high?
+ end
+ end
+
+ def test_low_should_return_false_when_no_default_priority_and_no_active_priorities
+ IssuePriority.update_all(active: false, is_default: false)
+ priority = IssuePriority.order(:position).first # Lowest priority
+ assert_nothing_raised do
+ assert_equal false, priority.low?
+ end
+ end
end
diff --git a/test/unit/lib/redmine/field_format/numeric_format_test.rb b/test/unit/lib/redmine/field_format/numeric_format_test.rb
index 2c9ecdc2e..7e5194d9a 100644
--- a/test/unit/lib/redmine/field_format/numeric_format_test.rb
+++ b/test/unit/lib/redmine/field_format/numeric_format_test.rb
@@ -33,13 +33,21 @@ class Redmine::NumericFieldFormatTest < ActionView::TestCase
assert_equal '<a href="http://foo/3" class="external">3</a>', field.format.formatted_custom_value(self, custom_value, true)
end
- def test_float_field_value_should_validate_when_given_with_various_separator
+ def test_float_field_should_normalize_decimal_separator
field = IssueCustomField.generate!(field_format: 'float')
issue = Issue.generate!(tracker: Tracker.find(1), status: IssueStatus.find(1), priority: IssuePriority.find(6))
- to_test = {'en' => '3.33', 'de' => '3,33'}
- to_test.each do |locale, expected|
- with_locale locale do
- assert field.format.validate_single_value(field, expected, issue)
+
+ with_locale 'de' do
+ issue.custom_field_values = { field.id => '3,33' }
+ assert issue.save!
+ assert_equal '3.33', issue.reload.custom_field_values.last.value
+ end
+
+ # Comma decimal separator is not allowed in English locale
+ with_locale 'en' do
+ issue.custom_field_values = { field.id => '3,33' }
+ assert_raise ActiveRecord::RecordInvalid do
+ issue.save!
end
end
end
diff --git a/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb b/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb
index c0bff9b1f..9d6cd6b32 100644
--- a/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb
+++ b/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb
@@ -27,6 +27,7 @@ class BazaarAdapterTest < ActiveSupport::TestCase
def setup
@adapter = Redmine::Scm::Adapters::BazaarAdapter.
new(File.join(REPOSITORY_PATH, "trunk"))
+ skip "SCM command is unavailable" unless @adapter.class.client_available
end
def test_scm_version
diff --git a/test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb b/test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb
index 2ed9dc618..3bfe24997 100644
--- a/test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb
+++ b/test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb
@@ -27,6 +27,7 @@ class CvsAdapterTest < ActiveSupport::TestCase
if File.directory?(REPOSITORY_PATH)
def setup
@adapter = Redmine::Scm::Adapters::CvsAdapter.new(MODULE_NAME, REPOSITORY_PATH)
+ skip "SCM command is unavailable" unless @adapter.class.client_available
end
def test_scm_version
diff --git a/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb b/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb
index bf054860a..3f0451601 100644
--- a/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb
+++ b/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb
@@ -42,13 +42,6 @@ class GitAdapterTest < ActiveSupport::TestCase
WINDOWS_SKIP_STR = "TODO: This test fails in Git for Windows above 1.7.10"
def setup
- adapter_class = Redmine::Scm::Adapters::GitAdapter
- assert adapter_class
- assert adapter_class.client_command
- assert_equal true, adapter_class.client_available
- assert_equal true, adapter_class.client_version_above?([1])
- assert_equal true, adapter_class.client_version_above?([1, 0])
-
@adapter =
Redmine::Scm::Adapters::GitAdapter.
new(
@@ -59,6 +52,8 @@ class GitAdapterTest < ActiveSupport::TestCase
'ISO-8859-1'
)
assert @adapter
+ skip "SCM is unavailable" unless @adapter.class.client_available
+
@char_1 = 'Ü'
@str_felix_hex = "Felix Sch\xC3\xA4fer".b
end
diff --git a/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb b/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb
index b4f284103..81741a746 100644
--- a/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb
+++ b/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb
@@ -30,12 +30,6 @@ class MercurialAdapterTest < ActiveSupport::TestCase
if File.directory?(REPOSITORY_PATH)
def setup
- adapter_class = Redmine::Scm::Adapters::MercurialAdapter
- assert adapter_class
- assert adapter_class.client_command
- assert_equal true, adapter_class.client_available
- assert_equal true, adapter_class.client_version_above?([0, 9, 5])
-
@adapter =
Redmine::Scm::Adapters::MercurialAdapter.new(
REPOSITORY_PATH,
@@ -44,6 +38,8 @@ class MercurialAdapterTest < ActiveSupport::TestCase
nil,
'ISO-8859-1'
)
+ skip "SCM command is unavailable" unless @adapter.class.client_available
+
@diff_c_support = true
@char_1 = 'Ü'
@tag_char_1 = 'tag-Ü-00'
diff --git a/test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb b/test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb
index fe574a4ff..edc3541d1 100644
--- a/test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb
+++ b/test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb
@@ -23,6 +23,7 @@ class SubversionAdapterTest < ActiveSupport::TestCase
if repository_configured?('subversion')
def setup
@adapter = Redmine::Scm::Adapters::SubversionAdapter.new(self.class.subversion_repository_url)
+ skip "SCM command is unavailable" unless @adapter.class.client_available
end
def test_client_version
diff --git a/test/unit/lib/redmine/wiki_formatting/common_mark/sanitization_filter_test.rb b/test/unit/lib/redmine/wiki_formatting/common_mark/sanitization_filter_test.rb
index d3956e802..bf7e5655f 100644
--- a/test/unit/lib/redmine/wiki_formatting/common_mark/sanitization_filter_test.rb
+++ b/test/unit/lib/redmine/wiki_formatting/common_mark/sanitization_filter_test.rb
@@ -47,10 +47,14 @@ if Object.const_defined?(:CommonMarker)
end
def test_should_support_footnotes
- input = %(<a href="#fn-1" id="fnref-1">foo</a>)
- assert_equal input, filter(input)
- input = %(<ol><li id="fn-1">footnote</li></ol>)
- assert_equal input, filter(input)
+ [
+ %(<a href="#fn-1" id="fnref-1">foo</a>),
+ %(<a href="#fn-1" id="fnref-1-2">foo</a>),
+ %(<ol><li id="fn-1">footnote</li></ol>),
+ ].each do |input|
+ assert_equal input, filter(input)
+ assert_equal input, filter(input)
+ end
end
def test_should_remove_invalid_ids
diff --git a/test/unit/lib/redmine/wiki_formatting/macros_test.rb b/test/unit/lib/redmine/wiki_formatting/macros_test.rb
index f23c76fdd..a41428266 100644
--- a/test/unit/lib/redmine/wiki_formatting/macros_test.rb
+++ b/test/unit/lib/redmine/wiki_formatting/macros_test.rb
@@ -140,12 +140,12 @@ class Redmine::WikiFormatting::MacrosTest < Redmine::HelperTest
def test_macro_exception_should_be_displayed
Redmine::WikiFormatting::Macros.macro :exception do |obj, args|
- raise "My message"
+ raise "My exception's message"
end
text = "{{exception}}"
assert_include(
- '<div class="flash error">Error executing the <strong>exception</strong> macro (My message)</div>',
+ '<div class="flash error">Error executing the <strong>exception</strong> macro (My exception&#39;s message)</div>',
textilizable(text)
)
end
diff --git a/test/unit/member_test.rb b/test/unit/member_test.rb
index a7b1843dc..42fba4783 100644
--- a/test/unit/member_test.rb
+++ b/test/unit/member_test.rb
@@ -58,6 +58,39 @@ class MemberTest < ActiveSupport::TestCase
assert_equal 2, @jsmith.reload.roles.size
end
+ def test_update_roles_with_inherited_roles
+ User.current = User.find(1)
+
+ project = Project.find(1)
+ group_a = Group.generate!
+ group_b = Group.generate!
+ test_user = User.generate!
+ group_a.users << test_user
+ group_b.users << test_user
+
+ # Verify that inherited roles are correctly assigned
+ group_a_member = Member.new(project: project, user_id: group_a.id)
+ group_a_member.set_editable_role_ids([1]) # Add Manager role to Group A
+ group_b_member = Member.new(project: project, user_id: group_b.id)
+ group_b_member.set_editable_role_ids([1, 2]) # Add Manager and Developer roles to Group B
+ project.members << [group_a_member, group_b_member]
+ test_user_member = test_user.members.find_by(project_id: project.id)
+ assert_equal [ # [role_id, inherited_from]
+ [1, group_a_member.member_roles.find_by(role_id: 1).id],
+ [1, group_b_member.member_roles.find_by(role_id: 1).id],
+ [2, group_b_member.member_roles.find_by(role_id: 2).id],
+ ].sort, test_user_member.member_roles.map{|r| [r.role_id, r.inherited_from]}.sort
+
+ # Verify that a new non-inherited role is added and inherited roles are maintained
+ test_user_member.set_editable_role_ids([3]) # Add Reporter role to test_user
+ assert_equal [ # [role_id, inherited_from]
+ [1, group_a_member.member_roles.find_by(role_id: 1).id],
+ [1, group_b_member.member_roles.find_by(role_id: 1).id],
+ [2, group_b_member.member_roles.find_by(role_id: 2).id],
+ [3, nil]
+ ].sort, test_user_member.member_roles.map{|r| [r.role_id, r.inherited_from]}.sort
+ end
+
def test_validate
member = Member.new(:project_id => 1, :user_id => 2, :role_ids => [2])
# same use cannot have more than one membership for a project
diff --git a/test/unit/project_admin_query_test.rb b/test/unit/project_admin_query_test.rb
new file mode 100644
index 000000000..8e58e2efb
--- /dev/null
+++ b/test/unit/project_admin_query_test.rb
@@ -0,0 +1,138 @@
+# frozen_string_literal: true
+
+# Redmine - project management software
+# Copyright (C) 2006- Jean-Philippe Lang
+#
+# 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.
+
+require_relative '../test_helper'
+
+class ProjectAdminQueryTest < ActiveSupport::TestCase
+ include Redmine::I18n
+
+ def test_filter_values_be_arrays
+ q = ProjectAdminQuery.new
+ assert_nil q.project
+
+ q.available_filters.each do |name, filter|
+ values = filter.values
+ assert (values.nil? || values.is_a?(Array)),
+ "#values for #{name} filter returned a #{values.class.name}"
+ end
+ end
+
+ def test_project_statuses_filter_should_return_project_statuses
+ set_language_if_valid 'en'
+ query = ProjectAdminQuery.new(:name => '_')
+ query.filters = {'status' => {:operator => '=', :values => []}}
+ values = query.available_filters['status'][:values]
+ assert_equal ['active', 'closed', 'archived', 'scheduled for deletion'], values.map(&:first)
+ assert_equal ['1', '5', '9', '10'], values.map(&:second)
+ end
+
+ def test_default_columns
+ q = ProjectAdminQuery.new
+ assert q.columns.any?
+ assert q.inline_columns.any?
+ assert q.block_columns.empty?
+ end
+
+ def test_available_columns_should_include_project_custom_fields
+ query = ProjectAdminQuery.new
+ assert_include :cf_3, query.available_columns.map(&:name)
+ end
+
+ def test_available_display_types_should_always_returns_list
+ query = ProjectAdminQuery.new
+ assert_equal ['list'], query.available_display_types
+ end
+
+ def test_display_type_should_returns_list
+ ProjectAdminQuery.new.available_display_types.each do |t|
+ with_settings :project_list_display_type => t do
+ q = ProjectAdminQuery.new
+ assert_equal 'list', q.display_type
+ end
+ end
+ end
+
+ def test_no_default_project_admin_query
+ user = User.find(1)
+ query = ProjectQuery.find(11)
+ user_query = ProjectQuery.find(12)
+ user_query.update(visibility: Query::VISIBILITY_PUBLIC)
+
+ [nil, user, User.anonymous].each do |u|
+ assert_nil ProjectAdminQuery.default(user: u)
+ end
+
+ # ignore the default_project_query for admin queries
+ with_settings :default_project_query => query.id do
+ [nil, user, User.anonymous].each do |u|
+ assert_nil ProjectAdminQuery.default(user: u)
+ end
+ end
+
+ # user default, overrides global default
+ user.pref.default_project_query = user_query.id
+ user.pref.save
+
+ with_settings :default_project_query => query.id do
+ assert_nil ProjectAdminQuery.default(user: user)
+ end
+ end
+
+ def test_project_statuses_values_should_return_all_statuses
+ set_language_if_valid 'en'
+ q = ProjectAdminQuery.new
+ assert_equal [
+ ["active", "1"],
+ ["closed", "5"],
+ ["archived", "9"],
+ ["scheduled for deletion", "10"]
+ ], q.project_statuses_values
+ end
+
+ def test_base_scope_should_return_all_projects
+ q = ProjectAdminQuery.new
+ assert_equal Project.all, q.base_scope
+ end
+
+ def test_results_scope_has_last_activity_date
+ q = ProjectAdminQuery.generate!(column_names: [:last_activity_date])
+ result_projects = q.results_scope({})
+
+ assert_kind_of ActiveRecord::Relation, result_projects
+ assert_equal Project, result_projects.klass
+
+ last_activitiy_date = result_projects.find{|p| p.id == 1}.instance_variable_get(:@last_activity_date)
+ assert_not_nil last_activitiy_date
+ assert_equal Redmine::Activity::Fetcher.new(User.current).events(nil, nil, :project => Project.find(1)).first.updated_on, last_activitiy_date
+ end
+
+ def test_results_scope_with_offset_and_limit
+ q = ProjectAdminQuery.new
+
+ ((q.results_scope.count / 2) + 1).times do |i|
+ limit = 2
+ offset = i * 2
+
+ scope_without = q.results_scope.offset(offset).limit(limit).ids
+ scope_with = q.results_scope(:offset => offset, :limit => limit).ids
+
+ assert_equal scope_without, scope_with
+ end
+ end
+end
diff --git a/test/unit/project_query_test.rb b/test/unit/project_query_test.rb
index 3e232f820..2b8c0ea8d 100644
--- a/test/unit/project_query_test.rb
+++ b/test/unit/project_query_test.rb
@@ -56,16 +56,9 @@ class ProjectQueryTest < ActiveSupport::TestCase
def test_available_display_types_should_returns_bord_and_list
query = ProjectQuery.new
- query.admin_projects = nil
assert_equal ['board', 'list'], query.available_display_types
end
- def test_available_display_types_should_always_returns_list_when_admin_projects_is_set
- query = ProjectQuery.new
- query.admin_projects = 1
- assert_equal ['list'], query.available_display_types
- end
-
def test_display_type_default_should_equal_with_setting_project_list_display_type
ProjectQuery.new.available_display_types.each do |t|
with_settings :project_list_display_type => t do
@@ -81,8 +74,10 @@ class ProjectQueryTest < ActiveSupport::TestCase
user_query = ProjectQuery.find(12)
user_query.update(visibility: Query::VISIBILITY_PUBLIC)
- [nil, user, User.anonymous].each do |u|
- assert_nil IssueQuery.default(user: u)
+ with_settings :default_project_query => nil do
+ [nil, user, User.anonymous].each do |u|
+ assert_nil ProjectQuery.default(user: u)
+ end
end
# only global default is set
@@ -110,38 +105,17 @@ class ProjectQueryTest < ActiveSupport::TestCase
assert_nil ProjectQuery.default
end
- def test_display_type_should_returns_list_when_admin_projects_is_set
- q = ProjectQuery.new
- q.admin_projects = 1
- assert_equal 'list', q.display_type
- end
-
def test_project_statuses_values_should_equal_ancestors_return
ancestor = Query.new
q = ProjectQuery.new
assert_equal ancestor.project_statuses_values, q.project_statuses_values
end
- def test_project_statuses_values_should_includes_project_status_archeved_when_admin_projects_is_set
- q = ProjectQuery.new
- q.admin_projects = 1
- assert_includes q.project_statuses_values, [l(:project_status_archived), Project::STATUS_ARCHIVED.to_s]
- Query.new.project_statuses_values.each do |status|
- assert_includes q.project_statuses_values, status
- end
- end
-
def test_base_scope_should_return_visible_projects
q = ProjectQuery.new
assert_equal Project.visible, q.base_scope
end
- def test_base_scope_should_return_all_projects_when_admin_projects_is_set
- q = ProjectQuery.new
- q.admin_projects = 1
- assert_equal Project.all, q.base_scope
- end
-
def test_results_scope_has_last_activity_date
q = ProjectQuery.generate!(column_names: [:last_activity_date])
result_projects = q.results_scope({})
diff --git a/test/unit/repository_bazaar_test.rb b/test/unit/repository_bazaar_test.rb
index 5af9e7f13..5fec37973 100644
--- a/test/unit/repository_bazaar_test.rb
+++ b/test/unit/repository_bazaar_test.rb
@@ -50,6 +50,7 @@ class RepositoryBazaarTest < ActiveSupport::TestCase
:log_encoding => 'UTF-8'
)
assert @repository
+ skip "SCM command is unavailable" unless @repository.class.scm_available
end
def test_blank_path_to_repository_error_message
@@ -165,7 +166,22 @@ class RepositoryBazaarTest < ActiveSupport::TestCase
end
if File.directory?(REPOSITORY_PATH_NON_ASCII) && RUN_LATIN1_OUTPUT_TEST
+ # https://www.redmine.org/issues/42024
+ def skip_bzr_failure_on_ubuntu24
+ return unless File.exist?('/etc/os-release')
+
+ os_release = File.read('/etc/os-release')
+ name = os_release[/^NAME="(.+?)"$/, 1]
+ version = os_release[/^VERSION_ID="(.+?)"$/, 1]
+
+ if name == 'Ubuntu' && version == '24.04'
+ skip 'bzr command fails on Ubuntu 24.04, causing this test to fail'
+ end
+ end
+
def test_cat_latin1_path
+ skip_bzr_failure_on_ubuntu24
+
latin1_repo = create_latin1_repo
buf =
latin1_repo.cat(
@@ -186,6 +202,8 @@ class RepositoryBazaarTest < ActiveSupport::TestCase
end
def test_annotate_latin1_path
+ skip_bzr_failure_on_ubuntu24
+
latin1_repo = create_latin1_repo
ann1 =
latin1_repo.annotate(
@@ -206,6 +224,8 @@ class RepositoryBazaarTest < ActiveSupport::TestCase
end
def test_diff_latin1_path
+ skip_bzr_failure_on_ubuntu24
+
latin1_repo = create_latin1_repo
diff1 =
latin1_repo.diff(
@@ -217,6 +237,8 @@ class RepositoryBazaarTest < ActiveSupport::TestCase
end
def test_entries_latin1_path
+ skip_bzr_failure_on_ubuntu24
+
latin1_repo = create_latin1_repo
entries = latin1_repo.entries("test-#{CHAR_1_UTF8_HEX}-dir", 2)
assert_kind_of Redmine::Scm::Adapters::Entries, entries
@@ -227,6 +249,8 @@ class RepositoryBazaarTest < ActiveSupport::TestCase
end
def test_entry_latin1_path
+ skip_bzr_failure_on_ubuntu24
+
latin1_repo = create_latin1_repo
["test-#{CHAR_1_UTF8_HEX}-dir",
"/test-#{CHAR_1_UTF8_HEX}-dir",
@@ -245,6 +269,8 @@ class RepositoryBazaarTest < ActiveSupport::TestCase
end
def test_changeset_latin1_path
+ skip_bzr_failure_on_ubuntu24
+
latin1_repo = create_latin1_repo
assert_equal 0, latin1_repo.changesets.count
latin1_repo.fetch_changesets
diff --git a/test/unit/repository_cvs_test.rb b/test/unit/repository_cvs_test.rb
index af995eac0..84d0ed80b 100644
--- a/test/unit/repository_cvs_test.rb
+++ b/test/unit/repository_cvs_test.rb
@@ -36,6 +36,7 @@ class RepositoryCvsTest < ActiveSupport::TestCase
:url => MODULE_NAME,
:log_encoding => 'UTF-8')
assert @repository
+ skip "SCM command is unavailable" unless @repository.class.scm_available
end
def test_blank_module_error_message
diff --git a/test/unit/repository_git_test.rb b/test/unit/repository_git_test.rb
index ec1ca5157..857be9442 100644
--- a/test/unit/repository_git_test.rb
+++ b/test/unit/repository_git_test.rb
@@ -41,6 +41,7 @@ class RepositoryGitTest < ActiveSupport::TestCase
:path_encoding => 'ISO-8859-1'
)
assert @repository
+ skip "SCM command is unavailable" unless @repository.class.scm_available
end
def test_nondefault_repo_with_blank_identifier_destruction
diff --git a/test/unit/repository_mercurial_test.rb b/test/unit/repository_mercurial_test.rb
index 861729bac..f9a18acb4 100644
--- a/test/unit/repository_mercurial_test.rb
+++ b/test/unit/repository_mercurial_test.rb
@@ -35,6 +35,7 @@ class RepositoryMercurialTest < ActiveSupport::TestCase
:path_encoding => 'ISO-8859-1'
)
assert @repository
+ skip "SCM command is unavailable" unless @repository.class.scm_available
end
def test_blank_path_to_repository_error_message
@@ -164,6 +165,10 @@ class RepositoryMercurialTest < ActiveSupport::TestCase
end
def test_fetch_changesets_from_scratch
+ # This test fails when using Mercurial >= 5.1 due to a change in behavior.
+ # See https://repo.mercurial-scm.org/hg/rev/0c72eddb4be5 for details.
+ skip "Test skipped because Mercurial >= 5.1 is used" if @repository.scm.class.client_version_above?([5, 1])
+
assert_equal 0, @repository.changesets.count
@repository.fetch_changesets
@project.reload
@@ -247,6 +252,10 @@ class RepositoryMercurialTest < ActiveSupport::TestCase
end
def test_latest_changesets
+ # This test fails when using Mercurial >= 5.1 due to a change in behavior.
+ # See https://repo.mercurial-scm.org/hg/rev/0c72eddb4be5 for details.
+ skip "Test skipped because Mercurial >= 5.1 is used" if @repository.scm.class.client_version_above?([5, 1])
+
assert_equal 0, @repository.changesets.count
@repository.fetch_changesets
@project.reload
diff --git a/test/unit/repository_subversion_test.rb b/test/unit/repository_subversion_test.rb
index b4590ce31..dfdf520e7 100644
--- a/test/unit/repository_subversion_test.rb
+++ b/test/unit/repository_subversion_test.rb
@@ -30,6 +30,7 @@ class RepositorySubversionTest < ActiveSupport::TestCase
@repository = Repository::Subversion.create(:project => @project,
:url => self.class.subversion_repository_url)
assert @repository
+ skip "SCM command is unavailable" unless @repository.class.scm_available
end
def test_invalid_url
diff --git a/test/unit/repository_test.rb b/test/unit/repository_test.rb
index 53b5e0ee7..84c22a73f 100644
--- a/test/unit/repository_test.rb
+++ b/test/unit/repository_test.rb
@@ -455,7 +455,7 @@ class RepositoryTest < ActiveSupport::TestCase
def test_stats_by_author_reflect_changesets_and_changes
repository = Repository.find(10)
- expected = {"Dave Lopper"=>{:commits_count=>10, :changes_count=>3}}
+ expected = {"Dave Lopper"=>{:commits_count=>11, :changes_count=>3}}
assert_equal expected, repository.stats_by_author
set = Changeset.create!(
@@ -467,7 +467,7 @@ class RepositoryTest < ActiveSupport::TestCase
)
Change.create!(:changeset => set, :action => 'A', :path => '/path/to/file1')
Change.create!(:changeset => set, :action => 'A', :path => '/path/to/file2')
- expected = {"Dave Lopper"=>{:commits_count=>11, :changes_count=>5}}
+ expected = {"Dave Lopper"=>{:commits_count=>12, :changes_count=>5}}
assert_equal expected, repository.stats_by_author
end
@@ -476,7 +476,7 @@ class RepositoryTest < ActiveSupport::TestCase
# to ensure things are dynamically linked to Users
User.find_by_login("dlopper").update_attribute(:firstname, "Dave's")
repository = Repository.find(10)
- expected = {"Dave's Lopper"=>{:commits_count=>10, :changes_count=>3}}
+ expected = {"Dave's Lopper"=>{:commits_count=>11, :changes_count=>3}}
assert_equal expected, repository.stats_by_author
end
@@ -502,7 +502,7 @@ class RepositoryTest < ActiveSupport::TestCase
# with committer="dlopper <dlopper@somefoo.net>"
repository = Repository.find(10)
- expected = {"Dave Lopper"=>{:commits_count=>10, :changes_count=>3}}
+ expected = {"Dave Lopper"=>{:commits_count=>11, :changes_count=>3}}
assert_equal expected, repository.stats_by_author
set = Changeset.create!(
@@ -513,7 +513,7 @@ class RepositoryTest < ActiveSupport::TestCase
:comments => 'Another commit by foo.'
)
- expected = {"Dave Lopper"=>{:commits_count=>11, :changes_count=>3}}
+ expected = {"Dave Lopper"=>{:commits_count=>12, :changes_count=>3}}
assert_equal expected, repository.stats_by_author
end
diff --git a/test/unit/user_query_test.rb b/test/unit/user_query_test.rb
index 1f8ce3464..ef31ba2c2 100644
--- a/test/unit/user_query_test.rb
+++ b/test/unit/user_query_test.rb
@@ -209,6 +209,30 @@ class UserQueryTest < ActiveSupport::TestCase
assert_equal [2, 1], users.pluck(:id)
end
+ def test_user_query_is_only_visible_to_admins
+ q = UserQuery.new(name: '_')
+ assert q.save
+
+ admin = User.admin(true).first
+ user = User.admin(false).first
+
+ assert q.visible?(admin)
+ assert_include q, UserQuery.visible(admin).to_a
+
+ assert_not q.visible?(user)
+ assert_not_include q, UserQuery.visible(user)
+ end
+
+ def test_user_query_is_only_editable_by_admins
+ q = UserQuery.new(name: '_')
+
+ admin = User.admin(true).first
+ user = User.admin(false).first
+
+ assert q.editable_by?(admin)
+ assert_not q.editable_by?(user)
+ end
+
def find_users_with_query(query)
User.where(query.statement).to_a
end