mirror of
https://github.com/opf/openproject.git
synced 2026-06-13 19:20:00 +00:00
Merge remote-tracking branch 'origin/dev' into epic/54751-meeting-agenda-items-backlog-for-recurring-meetings
This commit is contained in:
@@ -81,6 +81,11 @@ jobs:
|
||||
- name: "Fix root key in Portuguese crowdin translation files"
|
||||
run: |
|
||||
script/i18n/fix_crowdin_pt_language_root_key
|
||||
- name: "Fix storages.health.checks.failures.zero key in Arabic and Latvian crowdin translation files"
|
||||
# Only run if the file exists
|
||||
if: ${{ hashFiles('script/i18n/fix_crowdin_storages_health_checks_failures_zero_key') != '' }}
|
||||
run: |
|
||||
script/i18n/fix_crowdin_storages_health_checks_failures_zero_key
|
||||
- name: "Commit translations"
|
||||
env:
|
||||
BRANCH: ${{ matrix.branch }}
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
#Changelog
|
||||
# Changelog
|
||||
|
||||
Notable changes to the project are documented in the release notes at https://www.openproject.org/open-source/release-notes/.
|
||||
|
||||
+9
-9
@@ -4,7 +4,7 @@ We are pleased that you are thinking about contributing to OpenProject! This gui
|
||||
|
||||
## Get in touch
|
||||
|
||||
Please get in touch with us using our [development forum](https://community.openproject.org/projects/openproject/boards/7) or send us an email to info@openproject.com.
|
||||
Please get in touch with us using our [development forum](https://community.openproject.org/projects/openproject/boards/7) or send us an email to [info@openproject.com](mailto:info@openproject.com).
|
||||
|
||||
## Issue tracking and coordination
|
||||
|
||||
@@ -31,31 +31,31 @@ For contributing source code, please follow the git workflow below:
|
||||
- **Fork** OpenProject on GitHub
|
||||
- Clone your fork to your development machine:
|
||||
|
||||
```
|
||||
```bash
|
||||
git clone git@github.com/<username>/openproject
|
||||
```
|
||||
|
||||
- Optional: Add the original OpenProject repository as a remote, so you can fetch changes:
|
||||
|
||||
```
|
||||
```bash
|
||||
git remote add upstream git@github.com:opf/openproject
|
||||
```
|
||||
|
||||
- Make sure you're on the right branch. The main development branch is `dev`:
|
||||
|
||||
```
|
||||
```bash
|
||||
git checkout dev
|
||||
```
|
||||
|
||||
- Create a feature branch:
|
||||
|
||||
```
|
||||
```bash
|
||||
git checkout -b feature/<short description of your feature>
|
||||
```
|
||||
|
||||
- Make your changes, then push the branch into your ***own*** repository:
|
||||
|
||||
```
|
||||
```bash
|
||||
git push origin <your feature branch>
|
||||
```
|
||||
|
||||
@@ -83,7 +83,7 @@ More on this topic can be found in our [blog post](https://www.openproject.org/h
|
||||
|
||||
Please add tests to your code to verify functionality, especially if it is a new feature.
|
||||
|
||||
Pull requests will be verified by GitHub Actions as well,
|
||||
Pull requests will be verified by GitHub Actions,
|
||||
but please run them locally as well and make sure they are green before creating your pull request.
|
||||
We have a lot of pull requests coming in and it takes some time to run the complete suite for each one.
|
||||
|
||||
@@ -134,9 +134,9 @@ Instances of abusive, harassing, or otherwise unacceptable behavior may be repor
|
||||
by opening an issue or contacting one or more of the project maintainers.
|
||||
|
||||
This code of conduct is adapted from the
|
||||
[Contributor Covenant](http:contributor-covenant.org),
|
||||
[Contributor Covenant](https://www.contributor-covenant.org),
|
||||
version 1.0.0, available at
|
||||
[http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
|
||||
[http://contributor-covenant.org/version/1/0/0/](https://www.contributor-covenant.org/version/1/0/0/code-of-conduct/)
|
||||
|
||||
## Contributors license agreement
|
||||
|
||||
|
||||
@@ -151,14 +151,14 @@ gem "gon", "~> 6.4.0"
|
||||
gem "lograge", "~> 0.14.0"
|
||||
|
||||
# Structured warnings to selectively disable them in production
|
||||
gem "structured_warnings", "~> 0.4.0"
|
||||
gem "structured_warnings", "~> 0.5.0"
|
||||
|
||||
# catch exceptions and send them to any airbrake compatible backend
|
||||
# don't require by default, instead load on-demand when actually configured
|
||||
gem "airbrake", "~> 13.0.0", require: false
|
||||
|
||||
gem "markly", "~> 0.10" # another markdown parser like commonmarker, but with AST support used in PDF export
|
||||
gem "md_to_pdf", git: "https://github.com/opf/md-to-pdf", ref: "66893683b94a5fe8f1dc9a60600773307209cdd2"
|
||||
gem "markly", "~> 0.13" # another markdown parser like commonmarker, but with AST support used in PDF export
|
||||
gem "md_to_pdf", git: "https://github.com/opf/md-to-pdf", ref: "67d14c6c7a13f918d158bde1a51ef1067a8cf724"
|
||||
gem "prawn", "~> 2.4"
|
||||
gem "ttfunk", "~> 1.7.0" # remove after https://github.com/prawnpdf/prawn/issues/1346 resolved.
|
||||
|
||||
@@ -191,7 +191,7 @@ gem "puma", "~> 6.5"
|
||||
gem "puma-plugin-statsd", "~> 2.0"
|
||||
gem "rack-timeout", "~> 0.7.0", require: "rack/timeout/base"
|
||||
|
||||
gem "nokogiri", "~> 1.18.1"
|
||||
gem "nokogiri", "~> 1.18.8"
|
||||
|
||||
gem "carrierwave", "~> 1.3.4"
|
||||
gem "carrierwave_direct", "~> 2.1.0"
|
||||
@@ -201,7 +201,7 @@ gem "aws-sdk-core", "~> 3.107"
|
||||
# File upload via fog + screenshots on travis
|
||||
gem "aws-sdk-s3", "~> 1.91"
|
||||
|
||||
gem "openproject-token", "~> 5.2.0"
|
||||
gem "openproject-token", "~> 5.3.0"
|
||||
|
||||
gem "plaintext", "~> 0.3.2"
|
||||
|
||||
@@ -410,6 +410,6 @@ gemfiles.each do |file|
|
||||
send(:eval_gemfile, file) if File.readable?(file)
|
||||
end
|
||||
|
||||
gem "openproject-octicons", "~>19.24.2"
|
||||
gem "openproject-octicons_helper", "~>19.24.2"
|
||||
gem "openproject-primer_view_components", "~>0.61.1"
|
||||
gem "openproject-octicons", "~>19.25.0"
|
||||
gem "openproject-octicons_helper", "~>19.25.0"
|
||||
gem "openproject-primer_view_components", "~>0.63.0"
|
||||
|
||||
+98
-98
@@ -19,16 +19,16 @@ GIT
|
||||
|
||||
GIT
|
||||
remote: https://github.com/opf/md-to-pdf
|
||||
revision: 66893683b94a5fe8f1dc9a60600773307209cdd2
|
||||
ref: 66893683b94a5fe8f1dc9a60600773307209cdd2
|
||||
revision: 67d14c6c7a13f918d158bde1a51ef1067a8cf724
|
||||
ref: 67d14c6c7a13f918d158bde1a51ef1067a8cf724
|
||||
specs:
|
||||
md_to_pdf (0.2.0)
|
||||
md_to_pdf (0.2.1)
|
||||
base64 (~> 0.2)
|
||||
bigdecimal (~> 3.1)
|
||||
color_conversion (~> 0.1)
|
||||
front_matter_parser (~> 1.0)
|
||||
json-schema (~> 4.3)
|
||||
markly (~> 0.10)
|
||||
markly (~> 0.13)
|
||||
matrix (~> 0.4)
|
||||
nokogiri (~> 1.18)
|
||||
prawn (~> 2.4)
|
||||
@@ -352,8 +352,8 @@ GEM
|
||||
awesome_nested_set (3.8.0)
|
||||
activerecord (>= 4.0.0, < 8.1)
|
||||
aws-eventstream (1.3.2)
|
||||
aws-partitions (1.1082.0)
|
||||
aws-sdk-core (3.222.1)
|
||||
aws-partitions (1.1088.0)
|
||||
aws-sdk-core (3.222.2)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
@@ -372,12 +372,12 @@ GEM
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.11.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
axe-core-api (4.10.2)
|
||||
axe-core-api (4.10.3)
|
||||
dumb_delegator
|
||||
ostruct
|
||||
virtus
|
||||
axe-core-rspec (4.10.2)
|
||||
axe-core-api (= 4.10.2)
|
||||
axe-core-rspec (4.10.3)
|
||||
axe-core-api (= 4.10.3)
|
||||
dumb_delegator
|
||||
ostruct
|
||||
virtus
|
||||
@@ -448,7 +448,7 @@ GEM
|
||||
compare-xml (0.66)
|
||||
nokogiri (~> 1.8)
|
||||
concurrent-ruby (1.3.4)
|
||||
connection_pool (2.5.0)
|
||||
connection_pool (2.5.1)
|
||||
cookiejar (0.3.4)
|
||||
cose (1.3.1)
|
||||
cbor (~> 0.5.9)
|
||||
@@ -566,7 +566,7 @@ GEM
|
||||
factory_bot_rails (6.4.4)
|
||||
factory_bot (~> 6.5)
|
||||
railties (>= 5.0.0)
|
||||
faraday (2.12.2)
|
||||
faraday (2.13.0)
|
||||
faraday-net_http (>= 2.0, < 3.5)
|
||||
json
|
||||
logger
|
||||
@@ -575,19 +575,19 @@ GEM
|
||||
faraday-net_http (3.4.0)
|
||||
net-http (>= 0.5.0)
|
||||
fastimage (2.3.1)
|
||||
ffi (1.17.1)
|
||||
ffi (1.17.1-aarch64-linux-gnu)
|
||||
ffi (1.17.1-aarch64-linux-musl)
|
||||
ffi (1.17.1-arm-linux-gnu)
|
||||
ffi (1.17.1-arm-linux-musl)
|
||||
ffi (1.17.1-arm64-darwin)
|
||||
ffi (1.17.1-x86-linux-gnu)
|
||||
ffi (1.17.1-x86-linux-musl)
|
||||
ffi (1.17.1-x86_64-darwin)
|
||||
ffi (1.17.1-x86_64-linux-gnu)
|
||||
ffi (1.17.1-x86_64-linux-musl)
|
||||
ffi (1.17.2)
|
||||
ffi (1.17.2-aarch64-linux-gnu)
|
||||
ffi (1.17.2-aarch64-linux-musl)
|
||||
ffi (1.17.2-arm-linux-gnu)
|
||||
ffi (1.17.2-arm-linux-musl)
|
||||
ffi (1.17.2-arm64-darwin)
|
||||
ffi (1.17.2-x86-linux-gnu)
|
||||
ffi (1.17.2-x86-linux-musl)
|
||||
ffi (1.17.2-x86_64-darwin)
|
||||
ffi (1.17.2-x86_64-linux-gnu)
|
||||
ffi (1.17.2-x86_64-linux-musl)
|
||||
flamegraph (0.9.5)
|
||||
fog-aws (3.30.0)
|
||||
fog-aws (3.31.0)
|
||||
base64 (~> 0.2.0)
|
||||
fog-core (~> 2.6)
|
||||
fog-json (~> 1.1)
|
||||
@@ -636,7 +636,7 @@ GEM
|
||||
mutex_m
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
google-apis-gmail_v1 (0.41.0)
|
||||
google-apis-gmail_v1 (0.42.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-cloud-env (2.2.2)
|
||||
base64 (~> 0.2)
|
||||
@@ -734,7 +734,7 @@ GEM
|
||||
addressable (~> 2.8)
|
||||
childprocess (~> 5.0)
|
||||
logger (~> 1.6)
|
||||
lefthook (1.11.10)
|
||||
lefthook (1.11.11)
|
||||
letter_opener (1.10.0)
|
||||
launchy (>= 2.2, < 4)
|
||||
letter_opener_web (3.0.0)
|
||||
@@ -759,7 +759,7 @@ GEM
|
||||
loofah (2.24.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
lookbook (2.3.8)
|
||||
lookbook (2.3.9)
|
||||
activemodel
|
||||
css_parser
|
||||
htmlbeautifier (~> 1.3)
|
||||
@@ -786,7 +786,7 @@ GEM
|
||||
mime-types (3.6.2)
|
||||
logger
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2025.0402)
|
||||
mime-types-data (3.2025.0415)
|
||||
mini_magick (5.2.0)
|
||||
benchmark
|
||||
logger
|
||||
@@ -813,24 +813,24 @@ GEM
|
||||
net-smtp (0.5.1)
|
||||
net-protocol
|
||||
nio4r (2.7.4)
|
||||
nokogiri (1.18.7)
|
||||
nokogiri (1.18.8)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.7-aarch64-linux-gnu)
|
||||
nokogiri (1.18.8-aarch64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.7-aarch64-linux-musl)
|
||||
nokogiri (1.18.8-aarch64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.7-arm-linux-gnu)
|
||||
nokogiri (1.18.8-arm-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.7-arm-linux-musl)
|
||||
nokogiri (1.18.8-arm-linux-musl)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.7-arm64-darwin)
|
||||
nokogiri (1.18.8-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.7-x86_64-darwin)
|
||||
nokogiri (1.18.8-x86_64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.7-x86_64-linux-gnu)
|
||||
nokogiri (1.18.8-x86_64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.7-x86_64-linux-musl)
|
||||
nokogiri (1.18.8-x86_64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
oj (3.16.10)
|
||||
bigdecimal (>= 3.0)
|
||||
@@ -853,17 +853,17 @@ GEM
|
||||
validate_email
|
||||
validate_url
|
||||
webfinger (~> 2.0)
|
||||
openproject-octicons (19.24.2)
|
||||
openproject-octicons_helper (19.24.2)
|
||||
openproject-octicons (19.25.0)
|
||||
openproject-octicons_helper (19.25.0)
|
||||
actionview
|
||||
openproject-octicons (= 19.24.2)
|
||||
openproject-octicons (= 19.25.0)
|
||||
railties
|
||||
openproject-primer_view_components (0.61.1)
|
||||
actionview (>= 5.0.0)
|
||||
activesupport (>= 5.0.0)
|
||||
openproject-primer_view_components (0.63.0)
|
||||
actionview (>= 7.1.0)
|
||||
activesupport (>= 7.1.0)
|
||||
openproject-octicons (>= 19.23.0)
|
||||
view_component (>= 3.1, < 4.0)
|
||||
openproject-token (5.2.0)
|
||||
openproject-token (5.3.0)
|
||||
activemodel
|
||||
openssl (3.3.0)
|
||||
openssl-signature_algorithm (1.3.0)
|
||||
@@ -876,10 +876,10 @@ GEM
|
||||
paper_trail (16.0.0)
|
||||
activerecord (>= 6.1)
|
||||
request_store (~> 1.4)
|
||||
parallel (1.26.3)
|
||||
parallel (1.27.0)
|
||||
parallel_tests (4.10.1)
|
||||
parallel
|
||||
parser (3.3.7.4)
|
||||
parser (3.3.8.0)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
pdf-core (0.9.0)
|
||||
@@ -1063,7 +1063,7 @@ GEM
|
||||
rspec-support (3.13.2)
|
||||
rspec-wait (1.0.1)
|
||||
rspec (>= 3.4)
|
||||
rubocop (1.75.2)
|
||||
rubocop (1.75.3)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (~> 3.17.0.2)
|
||||
lint_roller (~> 1.1.0)
|
||||
@@ -1074,7 +1074,7 @@ GEM
|
||||
rubocop-ast (>= 1.44.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 4.0)
|
||||
rubocop-ast (1.44.0)
|
||||
rubocop-ast (1.44.1)
|
||||
parser (>= 3.3.7.2)
|
||||
prism (~> 1.4)
|
||||
rubocop-capybara (2.22.1)
|
||||
@@ -1095,7 +1095,7 @@ GEM
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 1.75.0, < 2.0)
|
||||
rubocop-ast (>= 1.38.0, < 2.0)
|
||||
rubocop-rspec (3.5.0)
|
||||
rubocop-rspec (3.6.0)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (~> 1.72, >= 1.72.1)
|
||||
rubocop-rspec_rails (2.31.0)
|
||||
@@ -1166,7 +1166,7 @@ GEM
|
||||
activerecord (>= 6.1)
|
||||
stringex (2.8.6)
|
||||
stringio (3.1.6)
|
||||
structured_warnings (0.4.0)
|
||||
structured_warnings (0.5.0)
|
||||
svg-graph (2.2.2)
|
||||
swd (2.0.3)
|
||||
activesupport (>= 3)
|
||||
@@ -1359,7 +1359,7 @@ DEPENDENCIES
|
||||
lograge (~> 0.14.0)
|
||||
lookbook (~> 2.3.4)
|
||||
mail (= 2.8.1)
|
||||
markly (~> 0.10)
|
||||
markly (~> 0.13)
|
||||
matrix (~> 0.4.2)
|
||||
md_to_pdf!
|
||||
meta-tags (~> 2.22.0)
|
||||
@@ -1367,7 +1367,7 @@ DEPENDENCIES
|
||||
multi_json (~> 1.15.0)
|
||||
my_page!
|
||||
net-ldap (~> 0.19.0)
|
||||
nokogiri (~> 1.18.1)
|
||||
nokogiri (~> 1.18.8)
|
||||
oj (~> 3.16.0)
|
||||
okcomputer (~> 1.19.0)
|
||||
omniauth!
|
||||
@@ -1388,15 +1388,15 @@ DEPENDENCIES
|
||||
openproject-job_status!
|
||||
openproject-ldap_groups!
|
||||
openproject-meeting!
|
||||
openproject-octicons (~> 19.24.2)
|
||||
openproject-octicons_helper (~> 19.24.2)
|
||||
openproject-octicons (~> 19.25.0)
|
||||
openproject-octicons_helper (~> 19.25.0)
|
||||
openproject-openid_connect!
|
||||
openproject-primer_view_components (~> 0.61.1)
|
||||
openproject-primer_view_components (~> 0.63.0)
|
||||
openproject-recaptcha!
|
||||
openproject-reporting!
|
||||
openproject-storages!
|
||||
openproject-team_planner!
|
||||
openproject-token (~> 5.2.0)
|
||||
openproject-token (~> 5.3.0)
|
||||
openproject-two_factor_authentication!
|
||||
openproject-webhooks!
|
||||
openproject-xls_export!
|
||||
@@ -1464,7 +1464,7 @@ DEPENDENCIES
|
||||
stackprof
|
||||
store_attribute (~> 2.0)
|
||||
stringex (~> 2.8.5)
|
||||
structured_warnings (~> 0.4.0)
|
||||
structured_warnings (~> 0.5.0)
|
||||
svg-graph (~> 2.2.0)
|
||||
sys-filesystem (~> 1.5.0)
|
||||
table_print (~> 1.5.6)
|
||||
@@ -1519,14 +1519,14 @@ CHECKSUMS
|
||||
auto_strip_attributes (2.6.0) sha256=a7e2e0cf744de2bcd947fd68014220702bcc88c81274c1cd9ce6f7316aae39b0
|
||||
awesome_nested_set (3.8.0) sha256=469daff411d80291dbb80d1973133e498048a7afc2519c545f62d2cdebc60eda
|
||||
aws-eventstream (1.3.2) sha256=7e2c3a55ca70d7861d5d3c98e47daa463ed539c349caba22b48305b8919572d7
|
||||
aws-partitions (1.1082.0) sha256=b77347af71e71cd457e227997e53635078b4fc8b14961ab606685a385dd7fa8c
|
||||
aws-sdk-core (3.222.1) sha256=349f39840fca4300384bd6dc85a546a0ad9def81d48d1f99607a6b3ec27bcbb2
|
||||
aws-partitions (1.1088.0) sha256=8535142a11dadebed7b87e68bb607031b972171472d7a4b55bfb409d2a22998a
|
||||
aws-sdk-core (3.222.2) sha256=0639070595c6d123fc371d773a2a86f9fc208466ec88e9763d7af924a757c8d1
|
||||
aws-sdk-kms (1.99.0) sha256=ba292fc3ffd672532aae2601fe55ff424eee78da8e23c23ba6ce4037138275a8
|
||||
aws-sdk-s3 (1.183.0) sha256=8c06b0330c76fc57b4a04a94aec25a8474160b05b2436334d9f8e57ea2799f4c
|
||||
aws-sdk-sns (1.97.0) sha256=e0489eb11cc107496738c2b39628842df970f4534bdc81a2254d2b49f7d0b7f6
|
||||
aws-sigv4 (1.11.0) sha256=50a8796991a862324442036ad7a395920572b84bb6cd29b945e5e1800e8da1db
|
||||
axe-core-api (4.10.2) sha256=c84ea0a041380a329f38fcfbc32b3759d35b481a27a368471e93afba5aafb289
|
||||
axe-core-rspec (4.10.2) sha256=e302be5807e40eaa170f45559b2067791dfc42a58cb4400d2d6217321f12f775
|
||||
axe-core-api (4.10.3) sha256=6e10f3ed1c031804f16e8154d9d5dc658564d10850cee860e125fe665c3f0148
|
||||
axe-core-rspec (4.10.3) sha256=ca21d0111e2d0fcd0f1da922c9071337336732aa6a3a8dc21bed94c9a701527e
|
||||
axiom-types (0.1.1) sha256=c1ff113f3de516fa195b2db7e0a9a95fd1b08475a502ff660d04507a09980383
|
||||
base64 (0.2.0) sha256=0f25e9b21a02a0cc0cea8ef92b2041035d39350946e8789c562b2d1a3da01507
|
||||
bcrypt (3.1.20) sha256=8410f8c7b3ed54a3c00cd2456bf13917d695117f033218e2483b2e40b0784099
|
||||
@@ -1563,7 +1563,7 @@ CHECKSUMS
|
||||
commonmarker (2.3.0-x86_64-linux-musl) sha256=af895be1dfe723a1ae2671fd147d082e1be107be732b7a52e2e0d5daa1ae687a
|
||||
compare-xml (0.66) sha256=e21aa5c0f69ef1177eced997c688fd4df989084e74a1b612257af32e1dd05319
|
||||
concurrent-ruby (1.3.4) sha256=d4aa926339b0a86b5b5054a0a8c580163e6f5dcbdfd0f4bb916b1a2570731c32
|
||||
connection_pool (2.5.0) sha256=233b92f8d38e038c1349ccea65dd3772727d669d6d2e71f9897c8bf5cd53ebfc
|
||||
connection_pool (2.5.1) sha256=ae802a90a4b5a081101b39d618e69921a9a50bea9ac3420a5b8c71f1befa3e9c
|
||||
cookiejar (0.3.4) sha256=11b16acfc4baf7a0f463c21a6212005e04e25f5554d4d9f24d97f3492dfda0df
|
||||
cose (1.3.1) sha256=d5d4dbcd6b035d513edc4e1ab9bc10e9ce13b4011c96e3d1b8fe5e6413fd6de5
|
||||
costs (1.0.0)
|
||||
@@ -1613,24 +1613,24 @@ CHECKSUMS
|
||||
excon (1.2.5) sha256=ca040bb61bc0059968f34a17115a00d2db8562e3c0c5c5c7432072b551c85a9d
|
||||
factory_bot (6.5.1) sha256=40581ea7bec0aee05514b8f4f99ed477274bdf1884c1372de5209e60322d6ca9
|
||||
factory_bot_rails (6.4.4) sha256=139e17caa2c50f098fddf5e5e1f29e8067352024e91ca1186d018b36589e5c88
|
||||
faraday (2.12.2) sha256=157339c25c7b8bcb739f5cf1207cb0cefe8fa1c65027266bcbc34c90c84b9ad6
|
||||
faraday (2.13.0) sha256=f2697cd61a434dc446ee035f0370de654c2ad64707c4fc2541eb2338702e9614
|
||||
faraday-follow_redirects (0.3.0) sha256=d92d975635e2c7fe525dd494fcd4b9bb7f0a4a0ec0d5f4c15c729530fdb807f9
|
||||
faraday-net_http (3.4.0) sha256=a1f1e4cd6a2cf21599c8221595e27582d9936819977bbd4089a601f24c64e54a
|
||||
fastimage (2.3.1) sha256=23c629f1f3e7d61bcfcc06c25b3d2418bc6bf41d2e615dbf5132c0e3b63ecce9
|
||||
ferrum (0.15)
|
||||
ffi (1.17.1) sha256=26f6b0dbd1101e6ffc09d3ca640b2a21840cc52731ad8a7ded9fb89e5fb0fc39
|
||||
ffi (1.17.1-aarch64-linux-gnu) sha256=c5d22cb545a3a691d46060f1343c461d1a8d38c3fd71b96b4cbbe6906bf1fd38
|
||||
ffi (1.17.1-aarch64-linux-musl) sha256=88b9d6ae905d21142df27c94bb300042c1aae41b67291885f600eaad16326b1d
|
||||
ffi (1.17.1-arm-linux-gnu) sha256=fe14f5ece94082f3b0e651a09008113281f2764e7ea95f522b64e2fe32e11504
|
||||
ffi (1.17.1-arm-linux-musl) sha256=df14927ca7bd9095148a7d1938bb762bbf189d190cf25d9547395ec7acc198a0
|
||||
ffi (1.17.1-arm64-darwin) sha256=a8e04f79d375742c54ee7f9fff4b4022b87200a4ec0eb082128d3b6559e67b4d
|
||||
ffi (1.17.1-x86-linux-gnu) sha256=01411c78cb3cff3c88cf67b2a7b24534e9b1638253d88581fef44c2083f6a174
|
||||
ffi (1.17.1-x86-linux-musl) sha256=02bcc7bbcff71e021ef05f43469f7c5074ab3422e415b287001bd890c9cbb1c6
|
||||
ffi (1.17.1-x86_64-darwin) sha256=0036199c290462dd7f03bc22933644c1685b7834a21788062bd5df48c72aa7a6
|
||||
ffi (1.17.1-x86_64-linux-gnu) sha256=8c0ade2a5d19f3672bccfe3b58e016ae5f159e3e2e741c856db87fcf07c903d0
|
||||
ffi (1.17.1-x86_64-linux-musl) sha256=3a343086820c96d6fbea4a5ef807fb69105b2b8174678f103b3db210c3f78401
|
||||
ffi (1.17.2) sha256=297235842e5947cc3036ebe64077584bff583cd7a4e94e9a02fdec399ef46da6
|
||||
ffi (1.17.2-aarch64-linux-gnu) sha256=c910bd3cae70b76690418cce4572b7f6c208d271f323d692a067d59116211a1a
|
||||
ffi (1.17.2-aarch64-linux-musl) sha256=69e6556b091d45df83e6c3b19d3c54177c206910965155a6ec98de5e893c7b7c
|
||||
ffi (1.17.2-arm-linux-gnu) sha256=d4a438f2b40224ae42ec72f293b3ebe0ba2159f7d1bd47f8417e6af2f68dbaa5
|
||||
ffi (1.17.2-arm-linux-musl) sha256=977dfb7f3a6381206dbda9bc441d9e1f9366bf189a634559c3b7c182c497aaa3
|
||||
ffi (1.17.2-arm64-darwin) sha256=54dd9789be1d30157782b8de42d8f887a3c3c345293b57ffb6b45b4d1165f813
|
||||
ffi (1.17.2-x86-linux-gnu) sha256=95d8f9ebea23c39888e2ab85a02c98f54acb2f4e79b829250d7267ce741dc7b0
|
||||
ffi (1.17.2-x86-linux-musl) sha256=41741449bab2b9530f42a47baa5c26263925306fad0ac2d60887f51af2e3b24c
|
||||
ffi (1.17.2-x86_64-darwin) sha256=981f2d4e32ea03712beb26e55e972797c2c5a7b0257955d8667ba58f2da6440e
|
||||
ffi (1.17.2-x86_64-linux-gnu) sha256=05d2026fc9dbb7cfd21a5934559f16293815b7ce0314846fee2ac8efbdb823ea
|
||||
ffi (1.17.2-x86_64-linux-musl) sha256=97c0eb3981414309285a64dc4d466bd149e981c279a56371ef811395d68cb95c
|
||||
flamegraph (0.9.5) sha256=a683020637ffa0e14a72640fa41babf14d926bfeaed87e31907cfd06ab2de8dc
|
||||
fog-aws (3.30.0) sha256=f70b811b655fbfa2e7c59da9c3c0672af43436128cbee4bbf46ee6d78d9a5004
|
||||
fog-aws (3.31.0) sha256=31f7854f102a5133562828c32c8587bdbf276576cadd2cd3aec1292eaf3f0405
|
||||
fog-core (2.6.0) sha256=3fe08aa83a23cddce42f4ba412040c08f890d7ff04c175c0ee59119371245be6
|
||||
fog-json (1.2.0) sha256=dd4f5ab362dbc72b687240bba9d2dd841d5dfe888a285797533f85c03ea548fe
|
||||
fog-xml (0.1.5) sha256=52b9fea10701461dd3eaf9d9839702169b418dbbf50426786b9b74fade373bd6
|
||||
@@ -1644,7 +1644,7 @@ CHECKSUMS
|
||||
gon (6.4.0) sha256=e3a618d659392890f1aa7db420f17c75fd7d35aeb5f8fe003697d02c4b88d2f0
|
||||
good_job (3.99.1) sha256=7d3869d8a8ee8ef7048fee5d746f41c21987b7822c20038a2f773036bef0830a
|
||||
google-apis-core (0.16.0) sha256=046a2c30a5ec123b2a6bc5e64348be781ce5fcd18dd4e85982e7a6a8da9d0dcc
|
||||
google-apis-gmail_v1 (0.41.0) sha256=d09102619a571213e9637efb9ca4504c54d7779b524344045e24088eb2d0f8d0
|
||||
google-apis-gmail_v1 (0.42.0) sha256=74b26b486fdb5f3212f54d1c24e98043be912079572729135473500c2f478cd5
|
||||
google-cloud-env (2.2.2) sha256=94bed40e05a67e9468ce1cb38389fba9a90aa8fc62fc9e173204c1dca59e21e7
|
||||
google-logging-utils (0.1.0) sha256=70950b1e49314273cf2e167adb47b62af7917a4691b580da7e9be67b9205fcd5
|
||||
googleauth (1.14.0) sha256=62e7de11791890c3d3dc70582dfd9ab5516530e4e4f56d96451fd62c76475149
|
||||
@@ -1685,7 +1685,7 @@ CHECKSUMS
|
||||
ladle (1.0.1) sha256=e8586964108c798d48bf57d2a65bd5602e8e5223a176b6602a0fb36c0bda90dc
|
||||
language_server-protocol (3.17.0.4) sha256=c484626478664fd13482d8180947c50a8590484b1258b99b7aedb3b69df89669
|
||||
launchy (3.1.1) sha256=72b847b5cc961589dde2c395af0108c86ff0119f42d4648d25b5440ebb10059e
|
||||
lefthook (1.11.10) sha256=bd99932e48e46469049a58734d46d6d94fceeb78d92edb3f0e0e29f5b269ad58
|
||||
lefthook (1.11.11) sha256=8603255f118484e049979cd1ad549c4f151108083437f03004aa538d18362f16
|
||||
letter_opener (1.10.0) sha256=2ff33f2e3b5c3c26d1959be54b395c086ca6d44826e8bf41a14ff96fdf1bdbb2
|
||||
letter_opener_web (3.0.0) sha256=3f391efe0e8b9b24becfab5537dfb17a5cf5eb532038f947daab58cb4b749860
|
||||
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
|
||||
@@ -1694,17 +1694,17 @@ CHECKSUMS
|
||||
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
|
||||
lograge (0.14.0) sha256=42371a75823775f166f727639f5ddce73dd149452a55fc94b90c303213dc9ae1
|
||||
loofah (2.24.0) sha256=61e6a710883abb8210887f3dc868cf3ed66594c509d9ff6987621efa6651ee1e
|
||||
lookbook (2.3.8) sha256=8fe719b5f80a1f245074d94474fa16f2c102a388d93576f43ed0ba2244f1dc41
|
||||
lookbook (2.3.9) sha256=b91fd92bd20281b87fbd23d7b22ffacb83ceb086c30986ab90275d220eaf0f39
|
||||
mail (2.8.1) sha256=ec3b9fadcf2b3755c78785cb17bc9a0ca9ee9857108a64b6f5cfc9c0b5bfc9ad
|
||||
marcel (1.0.4) sha256=0d5649feb64b8f19f3d3468b96c680bae9746335d02194270287868a661516a4
|
||||
markly (0.13.0) sha256=326a232c876cd95093d110b8d184be8effeadd776c3ffcefd59bdc8de7f59e0d
|
||||
matrix (0.4.2) sha256=71083ccbd67a14a43bfa78d3e4dc0f4b503b9cc18e5b4b1d686dc0f9ef7c4cc0
|
||||
md_to_pdf (0.2.0)
|
||||
md_to_pdf (0.2.1)
|
||||
messagebird-rest (1.4.2) sha256=1501e16ad76c87558f20f3b26cb39d05f587b349b44b8bf8056b040e9b495301
|
||||
meta-tags (2.22.1) sha256=e5ae1febbd320d396c7226d7edb868e5d63466c14b9c8b06622a1a74e6dce354
|
||||
method_source (1.1.0) sha256=181301c9c45b731b4769bc81e8860e72f9161ad7d66dd99103c9ab84f560f5c5
|
||||
mime-types (3.6.2) sha256=6109148e6a6e656607510b74571deff8ecd9a97ab0dcec9b7431bdd0b74460af
|
||||
mime-types-data (3.2025.0402) sha256=455cc502e76bf4260d9f4fc8484c6324a9d765c4917a6672cb5a13f7818719c0
|
||||
mime-types-data (3.2025.0415) sha256=82267683886373a95aa8b38d5de6ad3a4c2cca70d2ffa2aed07ec176eabb69bc
|
||||
mini_magick (5.2.0) sha256=2757ffbfdb1d38242d1da9ff1505360ab75d59dc02eb7ab79ff6d5acb1243f4a
|
||||
mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
|
||||
mini_portile2 (2.8.8) sha256=8e47136cdac04ce81750bb6c09733b37895bf06962554e4b4056d78168d70a75
|
||||
@@ -1722,15 +1722,15 @@ CHECKSUMS
|
||||
net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8
|
||||
net-smtp (0.5.1) sha256=ed96a0af63c524fceb4b29b0d352195c30d82dd916a42f03c62a3a70e5b70736
|
||||
nio4r (2.7.4) sha256=d95dee68e0bb251b8ff90ac3423a511e3b784124e5db7ff5f4813a220ae73ca9
|
||||
nokogiri (1.18.7) sha256=6b63ff5defe48f30d1d3b3122f65255ca91df2caf5378c6e0482ce73ff46fb31
|
||||
nokogiri (1.18.7-aarch64-linux-gnu) sha256=57a064ab5440814a69a0e040817bd8154adea68a30d2ff2b3aa515a6a06dbb5f
|
||||
nokogiri (1.18.7-aarch64-linux-musl) sha256=3e442dc5b69376e84288295fe37cbb890a21ad816a7e571e5e9967b3c1e30cd3
|
||||
nokogiri (1.18.7-arm-linux-gnu) sha256=337d9149deb5ae01022dff7c90f97bed81715fd586aacab0c5809ef933994c5e
|
||||
nokogiri (1.18.7-arm-linux-musl) sha256=97a26edcc975f780a0822aaf7f7d7427c561067c1c9ee56bd3542960f0c28a6e
|
||||
nokogiri (1.18.7-arm64-darwin) sha256=083abb2e9ed2646860f6b481a981485a658c6064caafaa81bf1cda1bada2e9d5
|
||||
nokogiri (1.18.7-x86_64-darwin) sha256=081d1aa517454ba3415304e2ea51fe411d6a3a809490d0c4aa42799cada417b7
|
||||
nokogiri (1.18.7-x86_64-linux-gnu) sha256=3a0bf946eb2defde13d760f869b61bc8b0c18875afdd3cffa96543cfa3a18005
|
||||
nokogiri (1.18.7-x86_64-linux-musl) sha256=9d83f8ec1fc37a305fa835d7ee61a4f37899e6ccc6dcb05be6645fa9797605af
|
||||
nokogiri (1.18.8) sha256=8c7464875d9ca7f71080c24c0db7bcaa3940e8be3c6fc4bcebccf8b9a0016365
|
||||
nokogiri (1.18.8-aarch64-linux-gnu) sha256=36badd2eb281fca6214a5188e24a34399b15d89730639a068d12931e2adc210e
|
||||
nokogiri (1.18.8-aarch64-linux-musl) sha256=664e0f9a77a7122a66d6c03abba7641ca610769a4728db55ee1706a0838b78a2
|
||||
nokogiri (1.18.8-arm-linux-gnu) sha256=17de01ca3adf9f8e187883ed73c672344d3dbb3c260f88ffa1008e8dc255a28e
|
||||
nokogiri (1.18.8-arm-linux-musl) sha256=6e6d7e71fc39572bd613a82d528cf54392c3de1ba5ce974f05c832b8187a040b
|
||||
nokogiri (1.18.8-arm64-darwin) sha256=483b5b9fb33653f6f05cbe00d09ea315f268f0e707cfc809aa39b62993008212
|
||||
nokogiri (1.18.8-x86_64-darwin) sha256=024cdfe7d9ae3466bba6c06f348fb2a8395d9426b66a3c82f1961b907945cc0c
|
||||
nokogiri (1.18.8-x86_64-linux-gnu) sha256=4a747875db873d18a2985ee2c320a6070c4a414ad629da625fbc58d1a20e5ecc
|
||||
nokogiri (1.18.8-x86_64-linux-musl) sha256=ddd735fba49475a395b9ea793bb6474e3a3125b89960339604d08a5397de1165
|
||||
oj (3.16.10) sha256=7f26bed974e331e16d579b470b0865010757f6fe6ee30ea9b67df653fbe13d7c
|
||||
okcomputer (1.19.0) sha256=8548935a82f725bdd8f2c329925a9f1a1bb2ce19ce26b47d7515665ee363b458
|
||||
omniauth (1.9.2)
|
||||
@@ -1754,15 +1754,15 @@ CHECKSUMS
|
||||
openproject-job_status (1.0.0)
|
||||
openproject-ldap_groups (1.0.0)
|
||||
openproject-meeting (1.0.0)
|
||||
openproject-octicons (19.24.2) sha256=c4d63f4c16796c51e2a43cefae29e6042aa5cd418ea1bc11015add12e25c841e
|
||||
openproject-octicons_helper (19.24.2) sha256=b7c8e7f4a22f60c8ac9ae70535e92a90121f28d58391aa21053bf751efc1c576
|
||||
openproject-octicons (19.25.0) sha256=16fc221375e693f0e893b1c208286f2d7719ae4dfe080c5415642b221f51f550
|
||||
openproject-octicons_helper (19.25.0) sha256=9b1778a67b0015ebe84ca0471f74e31004b985a8dcaaa443f7a2ac365b0a4e2d
|
||||
openproject-openid_connect (1.0.0)
|
||||
openproject-primer_view_components (0.61.1) sha256=c7a7c264fd125a32f6e8d8fadaf97f4ed1eee30d809f8caf7150f67ad4ed72b2
|
||||
openproject-primer_view_components (0.63.0) sha256=fa9d8b4e2ffc968d908c487c687f0eb380c7d02de50876aae92ba10ce42c0c57
|
||||
openproject-recaptcha (1.0.0)
|
||||
openproject-reporting (1.0.0)
|
||||
openproject-storages (1.0.0)
|
||||
openproject-team_planner (1.0.0)
|
||||
openproject-token (5.2.0) sha256=dd30482ea94897f24b7dc19d1f1cdd5a149e76635e29bf2ca34d31cf82f9d394
|
||||
openproject-token (5.3.0) sha256=5fe2f33992e6bd8926ef56c44afd098ac868bd91b07f4abff5577c171663901a
|
||||
openproject-two_factor_authentication (1.0.0)
|
||||
openproject-webhooks (1.0.0)
|
||||
openproject-xls_export (1.0.0)
|
||||
@@ -1774,9 +1774,9 @@ CHECKSUMS
|
||||
overviews (1.0.0)
|
||||
ox (2.14.22) sha256=0bbc4a40d109e4b76295c4927b5f2d453070eb5af221e5b05ec0ff58e291c6a9
|
||||
paper_trail (16.0.0) sha256=e9b9f0fb1b8b590c8231cfa931b282ba92f90e066e393930a5e1c61ae4c5019d
|
||||
parallel (1.26.3) sha256=d86babb7a2b814be9f4b81587bf0b6ce2da7d45969fab24d8ae4bf2bb4d4c7ef
|
||||
parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130
|
||||
parallel_tests (4.10.1) sha256=df05458c691462b210f7a41fc2651d4e4e8a881e8190e6d1e122c92c07735d70
|
||||
parser (3.3.7.4) sha256=2b26282274280e13f891080dc4ef3f65ce658d62e13255b246b28ec6754e98ab
|
||||
parser (3.3.8.0) sha256=2476364142b307fa5a1b1ece44f260728be23858a9c71078e956131a75453c45
|
||||
pdf-core (0.9.0) sha256=4f368b2f12b57ec979872d4bf4bd1a67e8648e0c81ab89801431d2fc89f4e0bb
|
||||
pdf-inspector (1.3.0) sha256=fc107579d6f29b636e2da3d6743479b2624d9e390bf2d84beef8fd4ebe1a05bd
|
||||
pdf-reader (2.14.1) sha256=b45a4521c249a394ad7ad9e691bfd46d4d00998cfc4f019e4525afb4963b411b
|
||||
@@ -1847,14 +1847,14 @@ CHECKSUMS
|
||||
rspec-retry (0.6.2) sha256=6101ba23a38809811ae3484acde4ab481c54d846ac66d5037ccb40131a60d858
|
||||
rspec-support (3.13.2) sha256=cea3a2463fd9b84b9dcc9685efd80ea701aa8f7b3decb3b3ce795ed67737dbec
|
||||
rspec-wait (1.0.1) sha256=50ce9cc7cd20b8bb67b95a4ed56b59a16d4af7e86ae91b28dbf20eeaa2481d1f
|
||||
rubocop (1.75.2) sha256=8efde647e278417e8074421b007e0d7d7c591482ef99d980528b18fea015a7c8
|
||||
rubocop-ast (1.44.0) sha256=77fdaf4aacf0c7d1ef887b075686124fb4ae5743d42ff7e73bb1c0d6b61b5d0a
|
||||
rubocop (1.75.3) sha256=1860a1b571fe4a15dff79d6683642ebf3ea4b3f7ae7903743ab0885b4686a1d5
|
||||
rubocop-ast (1.44.1) sha256=e3cc04203b2ef04f6d6cf5f85fe6d643f442b18cc3b23e3ada0ce5b6521b8e92
|
||||
rubocop-capybara (2.22.1) sha256=ced88caef23efea53f46e098ff352f8fc1068c649606ca75cb74650970f51c0c
|
||||
rubocop-factory_bot (2.27.1) sha256=9d744b5916778c1848e5fe6777cc69855bd96548853554ec239ba9961b8573fe
|
||||
rubocop-openproject (0.2.0) sha256=2300ed6b638a34a9368b458dc5fb618ae396da6a27670b50519ad1e3cea65ccb
|
||||
rubocop-performance (1.25.0) sha256=6f7d03568a770054117a78d0a8e191cefeffb703b382871ca7743831b1a52ec1
|
||||
rubocop-rails (2.31.0) sha256=79476e1075299c3e60fc50549c7c32614f9ebaae719b899ed75785c6786c52bd
|
||||
rubocop-rspec (3.5.0) sha256=710c942fe1af884ba8eea75cbb8bdbb051929a2208880a6fc2e2dce1eed5304c
|
||||
rubocop-rspec (3.6.0) sha256=c0e4205871776727e54dee9cc91af5fd74578001551ba40e1fe1a1ab4b404479
|
||||
rubocop-rspec_rails (2.31.0) sha256=775375e18a26a1184a812ef3054b79d218e85601b9ae897f38f8be24dddf1f45
|
||||
ruby-duration (3.2.3) sha256=eb3d13b1df85067a015a8fb2ed8f1eec842a3b721e47c9b6fd74d2f356069784
|
||||
ruby-ole (1.2.13.1) sha256=578d10dd2a797a2b35a1286c6fb2c9525f67c24791346fc8015d39f0ffa3cb72
|
||||
@@ -1888,7 +1888,7 @@ CHECKSUMS
|
||||
store_attribute (2.0.0) sha256=3399d9baa20776ec9dcb266cff407aac82bf99791678d6266ea617703e4b0e28
|
||||
stringex (2.8.6) sha256=c7b382d2b2a47a1e1646f256df201c48d487d6296fbb289d76802f67f5e929c4
|
||||
stringio (3.1.6) sha256=292c495d1657adfcdf0a32eecf12a60e6691317a500c3112ad3b2e31068274f5
|
||||
structured_warnings (0.4.0) sha256=ff9a59278e3ad8d18f742e677b764ce374f16e52c7993c0ff7d76900e93383ad
|
||||
structured_warnings (0.5.0) sha256=864aca5a4d6b63cd3a948f9dcd7f5498af1cf42bcf4fd75e2c315b7d77a30583
|
||||
svg-graph (2.2.2) sha256=f928866403055e6539afdfdab5f6268d108b2abc9f002e0fc51b16511809513a
|
||||
swd (2.0.3) sha256=4cdbe2a4246c19f093fce22e967ec3ebdd4657d37673672e621bf0c7eb770655
|
||||
sys-filesystem (1.5.3) sha256=17b561d1be683c34bc53946461ea9d67012d8f395e7297db8c63b9018cb30ece
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 259 KiB |
@@ -28,5 +28,5 @@
|
||||
@import "work_package_relations_tab/relation_component"
|
||||
@import "users/hover_card_component"
|
||||
@import "enterprise_edition/banner_component"
|
||||
@import "enterprise_edition/upsale_page_component"
|
||||
@import "enterprise_edition/upsell_page_component"
|
||||
@import "work_packages/types/pattern_input"
|
||||
|
||||
@@ -32,7 +32,7 @@ module WorkPackages
|
||||
module ActivitiesTab
|
||||
module StimulusControllers
|
||||
def index_stimulus_controller(suffix = nil) = "work-packages--activities-tab--index#{suffix}"
|
||||
def restricted_comment_stimulus_controller(suffix = nil) = "work-packages--activities-tab--restricted-comment#{suffix}"
|
||||
def internal_comment_stimulus_controller(suffix = nil) = "work-packages--activities-tab--internal-comment#{suffix}"
|
||||
def quote_comments_stimulus_controller(suffix = nil) = "work-packages--activities-tab--quote-comment#{suffix}"
|
||||
|
||||
def items_index_selector = "##{WorkPackages::ActivitiesTab::IndexComponent.wrapper_key}"
|
||||
|
||||
@@ -2,20 +2,36 @@
|
||||
component_wrapper(tag: "turbo-frame") do
|
||||
grid_layout("op-enterprise-banner",
|
||||
**@system_arguments) do |grid|
|
||||
grid.with_area(:visual) do
|
||||
render(Primer::Beta::Octicon.new(icon: :"op-enterprise-addons", classes: "op-enterprise-banner--icon"))
|
||||
|
||||
if inline?
|
||||
grid.with_area(:visual) do
|
||||
render(Primer::Beta::Octicon.new(icon: :"op-enterprise-addons", classes: "op-enterprise-banner--icon"))
|
||||
end
|
||||
end
|
||||
|
||||
grid.with_area(:content) do
|
||||
flex_layout do |flex|
|
||||
flex.with_row do
|
||||
render(Primer::Beta::Text.new(font_weight: :bold)) { title }
|
||||
if medium?
|
||||
flex_layout(align_items: :center, mb: 3) do |title_flex|
|
||||
title_flex.with_column do
|
||||
render(Primer::Beta::Octicon.new(icon: :"op-enterprise-addons",
|
||||
size: :medium,
|
||||
classes: "upsell-colored"))
|
||||
end
|
||||
|
||||
title_flex.with_column(ml: 2) do
|
||||
render(Primer::Beta::Heading.new(tag: :h2)) { title }
|
||||
end
|
||||
end
|
||||
else
|
||||
render(Primer::Beta::Text.new(font_weight: :bold)) { title }
|
||||
end
|
||||
end
|
||||
|
||||
flex.with_row(mt: 1) do
|
||||
concat render(Primer::Beta::Text.new) { description }
|
||||
concat " "
|
||||
concat render(Primer::Beta::Text.new) { plan_text }
|
||||
flex.with_row(classes: "op-enterprise-banner--description") do
|
||||
concat render(Primer::Beta::Text.new(tag: :p)) { description }
|
||||
concat render(Primer::Beta::Text.new(tag: :p)) { plan_text }
|
||||
end
|
||||
|
||||
if features.present?
|
||||
@@ -24,23 +40,31 @@
|
||||
end
|
||||
end
|
||||
|
||||
flex.with_row(mt: 2) do
|
||||
render EnterpriseEdition::UpsaleButtonsComponent.new(feature_key)
|
||||
flex.with_row(mt: 1) do
|
||||
render EnterpriseEdition::UpsellButtonsComponent.new(feature_key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if medium?
|
||||
grid.with_area(:graphic) do
|
||||
image_tag @image,
|
||||
class: "op-enterprise-banner--image"
|
||||
end
|
||||
end
|
||||
|
||||
if @dismissable
|
||||
grid.with_area(:dismiss) do
|
||||
classes = medium? ? "op-enterprise-banner--close-icon_hovering" : ""
|
||||
render(
|
||||
Primer::Beta::IconButton.new(
|
||||
classes: "op-enterprise-banner--close-icon",
|
||||
classes: class_names("op-enterprise-banner--close-icon", classes),
|
||||
scheme: :invisible,
|
||||
tag: :a,
|
||||
href: dismiss_enterprise_banner_path(feature_key: @dismiss_key),
|
||||
data: { turbo_stream: true, turbo_method: :post },
|
||||
icon: :x,
|
||||
aria: { label: t("ee.upsale.hide_banner") }
|
||||
aria: { label: t("ee.upsell.hide_banner") }
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
@@ -33,32 +33,66 @@ module EnterpriseEdition
|
||||
# This component uses conventional names for translation keys or URL look-ups based on the feature_key passed in.
|
||||
# It will only be rendered if necessary.
|
||||
class BannerComponent < ApplicationComponent
|
||||
include Primer::FetchOrFallbackHelper
|
||||
include Primer::ClassNameHelper
|
||||
include OpPrimer::ComponentHelpers
|
||||
include OpTurbo::Streamable
|
||||
include PlanForFeature
|
||||
|
||||
DEFAULT_VARIANT = :inline
|
||||
VARIANT_OPTIONS = %i[inline medium].freeze
|
||||
|
||||
# @param feature_key [Symbol, NilClass] The key of the feature to show the banner for.
|
||||
# @param variant [Symbol, NilClass] The variant of the banner comopnent.
|
||||
# @param image [String, NilClass] Path to the image to show on the banner, or nil.
|
||||
# Required when variant is :medium.
|
||||
# @param i18n_scope [String] Provide the i18n scope to look for title, description, and features.
|
||||
# Defaults to "ee.upsale.{feature_key}"
|
||||
# Defaults to "ee.upsell.{feature_key}"
|
||||
# @param dismissable [boolean] Allow this banner to be dismissed.
|
||||
# @param show_always [boolean] Always show the banner, regardless of the dismissed or feature state.
|
||||
# @param dismiss_key [String] Provide a string to identify this banner when being dismissed. Defaults to feature_key
|
||||
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
|
||||
def initialize(feature_key,
|
||||
i18n_scope: "ee.upsale.#{feature_key}",
|
||||
def initialize(feature_key, # rubocop:disable Metrics/AbcSize
|
||||
variant: DEFAULT_VARIANT,
|
||||
image: nil,
|
||||
i18n_scope: "ee.upsell.#{feature_key}",
|
||||
dismissable: false,
|
||||
show_always: false,
|
||||
dismiss_key: feature_key,
|
||||
**system_arguments)
|
||||
@variant = fetch_or_fallback(VARIANT_OPTIONS, variant, DEFAULT_VARIANT)
|
||||
@image = image
|
||||
@dismissable = dismissable
|
||||
@dismiss_key = dismiss_key
|
||||
@show_always = show_always
|
||||
|
||||
self.feature_key = feature_key
|
||||
self.i18n_scope = i18n_scope
|
||||
|
||||
if @variant == :medium && @image.nil?
|
||||
raise ArgumentError, "The 'image' parameter is required when the variant is :medium."
|
||||
end
|
||||
|
||||
@system_arguments = system_arguments
|
||||
@system_arguments[:tag] = :div
|
||||
@system_arguments[:mb] ||= 2
|
||||
@system_arguments[:id] = "op-enterprise-banner-#{feature_key.to_s.tr('_', '-')}"
|
||||
@system_arguments[:test_selector] = "op-enterprise-banner"
|
||||
super
|
||||
@system_arguments[:classes] = class_names(
|
||||
@system_arguments[:classes],
|
||||
"op-enterprise-banner",
|
||||
@variant == :medium ? "op-enterprise-banner_medium" : nil
|
||||
)
|
||||
|
||||
self.feature_key = feature_key
|
||||
self.i18n_scope = i18n_scope
|
||||
@dismissable = dismissable
|
||||
@dismiss_key = dismiss_key
|
||||
super
|
||||
end
|
||||
|
||||
def medium?
|
||||
@variant == :medium
|
||||
end
|
||||
|
||||
def inline?
|
||||
@variant == :inline
|
||||
end
|
||||
|
||||
def wrapper_key
|
||||
@@ -68,6 +102,8 @@ module EnterpriseEdition
|
||||
private
|
||||
|
||||
def render?
|
||||
return true if @show_always
|
||||
|
||||
!(EnterpriseToken.hide_banners? || feature_available? || dismissed?)
|
||||
end
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
/ ++
|
||||
/
|
||||
|
||||
$op-enterprise-banner-fixed-height: 220px
|
||||
|
||||
.op-enterprise-banner
|
||||
display: grid
|
||||
@@ -40,17 +41,47 @@
|
||||
background: var(--body-background)
|
||||
padding: var(--base-size-8)
|
||||
|
||||
&_medium
|
||||
padding: 0
|
||||
grid-template-areas: "content graphic"
|
||||
grid-template-columns: 1fr 1fr
|
||||
height: $op-enterprise-banner-fixed-height
|
||||
|
||||
&_medium &
|
||||
padding: 0
|
||||
grid-template-areas: "content graphic"
|
||||
grid-template-columns: 1fr 1fr
|
||||
|
||||
&--content
|
||||
align-self: center
|
||||
padding: var(--base-size-16)
|
||||
|
||||
&--graphic
|
||||
height: $op-enterprise-banner-fixed-height
|
||||
|
||||
&--image
|
||||
width: 100%
|
||||
height: 100%
|
||||
object-fit: cover
|
||||
|
||||
&--visual
|
||||
display: grid
|
||||
align-self: start
|
||||
padding: var(--base-size-6) var(--base-size-8)
|
||||
|
||||
&--icon
|
||||
color: var(--enterprise-upsale-color)
|
||||
color: var(--enterprise-upsell-color)
|
||||
margin-block: var(--base-size-2)
|
||||
|
||||
&--close-icon .Button-visual
|
||||
color: var(--enterprise-upsale-color)
|
||||
&--close-icon
|
||||
.Button-visual
|
||||
color: var(--enterprise-upsell-color)
|
||||
|
||||
&_hovering
|
||||
position: absolute
|
||||
top: var(--base-size-24)
|
||||
right: var(--base-size-24)
|
||||
|
||||
|
||||
&--content
|
||||
padding: var(--base-size-6) var(--base-size-8)
|
||||
|
||||
@@ -74,11 +74,11 @@ module EnterpriseEdition
|
||||
end
|
||||
|
||||
def plan_text
|
||||
plan_name = render(Primer::Beta::Text.new(font_weight: :bold, classes: "upsale-colored")) do
|
||||
I18n.t("ee.upsale.plan_name", plan:)
|
||||
plan_name = render(Primer::Beta::Text.new(font_weight: :bold, classes: "upsell-colored")) do
|
||||
I18n.t("ee.upsell.plan_name", plan:)
|
||||
end
|
||||
|
||||
I18n.t("ee.upsale.plan_text_html", plan_name:).html_safe
|
||||
I18n.t("ee.upsell.plan_text_html", plan_name:).html_safe
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+3
-2
@@ -29,7 +29,7 @@
|
||||
# ++
|
||||
|
||||
module EnterpriseEdition
|
||||
class UpsaleButtonsComponent < ApplicationComponent
|
||||
class UpsellButtonsComponent < ApplicationComponent
|
||||
include OpPrimer::ComponentHelpers
|
||||
|
||||
# @param feature_key [Symbol, NilClass] The key of the feature to show the banner for.
|
||||
@@ -66,6 +66,7 @@ module EnterpriseEdition
|
||||
|
||||
def free_trial_button
|
||||
return if EnterpriseToken.active?
|
||||
return unless User.current.admin?
|
||||
|
||||
helpers.angular_component_tag("opce-free-trial-button")
|
||||
end
|
||||
@@ -83,7 +84,7 @@ module EnterpriseEdition
|
||||
end
|
||||
|
||||
def link_title
|
||||
I18n.t("ee.upsale.#{feature_key}.link_title", default: I18n.t("ee.upsale.link_title"))
|
||||
I18n.t("ee.upsell.#{feature_key}.link_title", default: I18n.t(:label_more_information))
|
||||
end
|
||||
|
||||
def enterprise_link
|
||||
+4
-4
@@ -3,13 +3,13 @@
|
||||
<% helpers.write_trial_key_to_gon if !EnterpriseToken.current.present? %>
|
||||
<% end %>
|
||||
|
||||
<div class="op-enterprise-upsale-page" data-test-selector="op-enterprise-upsale-page">
|
||||
<div class="op-enterprise-upsell-page" data-test-selector="op-enterprise-upsell-page">
|
||||
<%=
|
||||
render(Primer::OpenProject::FlexLayout.new(justify_content: :center, align_items: :center)) do |flex|
|
||||
flex.with_column do
|
||||
render(Primer::Beta::Octicon.new(icon: :"op-enterprise-addons",
|
||||
size: :medium,
|
||||
classes: "upsale-colored"))
|
||||
classes: "upsell-colored"))
|
||||
end
|
||||
|
||||
flex.with_column(ml: 2) do
|
||||
@@ -18,7 +18,7 @@
|
||||
end
|
||||
%>
|
||||
|
||||
<p class="upsale--feature-reference">
|
||||
<p class="upsell--feature-reference">
|
||||
<%= render(Primer::Beta::Text.new) { description } %>
|
||||
<%= render(Primer::Beta::Text.new) { plan_text } %>
|
||||
</p>
|
||||
@@ -42,5 +42,5 @@
|
||||
<%= image_tag "enterprise-add-on.svg", class: "widget-box--teaser-image widget-box--teaser-image_default" %>
|
||||
<% end %>
|
||||
|
||||
<%= render EnterpriseEdition::UpsaleButtonsComponent.new(feature_key, justify_content: :center) %>
|
||||
<%= render EnterpriseEdition::UpsellButtonsComponent.new(feature_key, justify_content: :center) %>
|
||||
</div>
|
||||
+6
-6
@@ -30,16 +30,16 @@
|
||||
|
||||
module EnterpriseEdition
|
||||
# A full page banner for the enterprise edition
|
||||
class UpsalePageComponent < ApplicationComponent
|
||||
class UpsellPageComponent < ApplicationComponent
|
||||
include OpPrimer::ComponentHelpers
|
||||
include PlanForFeature
|
||||
|
||||
# @param feature_key [Symbol, NilClass] The key of the feature to show the upsale page for.
|
||||
# @param feature_key [Symbol, NilClass] The key of the feature to show the upsell page for.
|
||||
# @param i18n_scope [String] Provide the i18n scope to look for title, description, and features.
|
||||
# Defaults to "ee.upsale.{feature_key}"
|
||||
# @param image [String, NilClass] Path to the image to show on the upsale page, or nil.
|
||||
# @param video [String, NilClass] Path to the video to show on the upsale page, or nil.
|
||||
def initialize(feature_key, i18n_scope: "ee.upsale.#{feature_key}", image: nil, video: nil)
|
||||
# Defaults to "ee.upsell.{feature_key}"
|
||||
# @param image [String, NilClass] Path to the image to show on the upsell page, or nil.
|
||||
# @param video [String, NilClass] Path to the video to show on the upsell page, or nil.
|
||||
def initialize(feature_key, i18n_scope: "ee.upsell.#{feature_key}", image: nil, video: nil)
|
||||
super
|
||||
|
||||
self.feature_key = feature_key
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
/ See COPYRIGHT and LICENSE files for more details.
|
||||
/ ++
|
||||
/
|
||||
.op-enterprise-upsale-page
|
||||
.op-enterprise-upsell-page
|
||||
max-width: 50vw
|
||||
margin: auto
|
||||
padding-top: 20px
|
||||
@@ -30,6 +30,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
<%=
|
||||
render(
|
||||
border_box_container(
|
||||
id: container_id,
|
||||
classes: container_class,
|
||||
test_selector:
|
||||
)
|
||||
@@ -55,10 +56,10 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
if has_actions?
|
||||
concat render(
|
||||
Primer::BaseComponent.new(
|
||||
classes: heading_class,
|
||||
tag: :div
|
||||
classes: header_classes(:actions),
|
||||
tag: :span
|
||||
)
|
||||
)
|
||||
) { action_row_header_content }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -138,6 +138,10 @@ module OpPrimer
|
||||
nil
|
||||
end
|
||||
|
||||
def action_row_header_content
|
||||
nil
|
||||
end
|
||||
|
||||
def footer
|
||||
raise ArgumentError, "Need to provide footer content"
|
||||
end
|
||||
|
||||
@@ -23,6 +23,11 @@
|
||||
&:not(:last-child)
|
||||
padding-right: 6px
|
||||
|
||||
&:last-child
|
||||
justify-content: flex-end !important
|
||||
display: flex !important
|
||||
flex-direction: row !important
|
||||
|
||||
&--mobile-heading,
|
||||
&--row-label
|
||||
display: none
|
||||
|
||||
@@ -55,6 +55,15 @@ module OpPrimer
|
||||
# @param blk [Proc] A block that defines the form structure.
|
||||
def render_inline_form(form_builder, &blk)
|
||||
form_class = Class.new(ApplicationForm) do
|
||||
# This is a workaround to make the form class aware of the template path
|
||||
# of the block that is passed to it.
|
||||
#
|
||||
# This avoids an annoying warning "Could not identify the template" from
|
||||
# original `base_template_path` method.
|
||||
define_singleton_method(:base_template_path) do
|
||||
blk.source_location.first
|
||||
end
|
||||
|
||||
form(&blk)
|
||||
end
|
||||
render(form_class.new(form_builder))
|
||||
@@ -89,6 +98,15 @@ module OpPrimer
|
||||
# @param blk [Proc] A block that defines the form structure.
|
||||
def render_inline_settings_form(form_builder, &blk)
|
||||
form_class = Class.new(ApplicationForm) do
|
||||
# This is a workaround to make the form class aware of the template path
|
||||
# of the block that is passed to it.
|
||||
#
|
||||
# This avoids an annoying warning "Could not identify the template" from
|
||||
# original `base_template_path` method.
|
||||
define_singleton_method(:base_template_path) do
|
||||
blk.source_location.first
|
||||
end
|
||||
|
||||
settings_form(&blk)
|
||||
end
|
||||
render(form_class.new(form_builder))
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<%= menu_item.title %>
|
||||
|
||||
<% if menu_item.show_enterprise_icon %>
|
||||
<%= render Primer::Beta::Octicon.new(icon: "op-enterprise-addons", "aria-label": I18n.t(:label_enterprise_edition), classes: "upsale-colored", ml: 2) %>
|
||||
<%= render Primer::Beta::Octicon.new(icon: "op-enterprise-addons", "aria-label": I18n.t(:label_enterprise_edition), classes: "upsell-colored", ml: 2) %>
|
||||
<% end %>
|
||||
</span>
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
<%= child_item.title %>
|
||||
|
||||
<% if child_item.show_enterprise_icon %>
|
||||
<%= render Primer::Beta::Octicon.new(icon: "op-enterprise-addons", "aria-label": I18n.t(:label_enterprise_edition), classes: "upsale-colored", ml: 2) %>
|
||||
<%= render Primer::Beta::Octicon.new(icon: "op-enterprise-addons", "aria-label": I18n.t(:label_enterprise_edition), classes: "upsell-colored", ml: 2) %>
|
||||
<% end %>
|
||||
|
||||
<% if child_item.favored %>
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
anchor_align: :end
|
||||
},
|
||||
button_arguments: {
|
||||
icon: "op-kebab-vertical",
|
||||
icon: "kebab-horizontal",
|
||||
"aria-label": t(:label_more),
|
||||
data: { "test-selector": "project-more-dropdown-menu" }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
<%#-- copyright
|
||||
OpenProject is an open source project management software.
|
||||
Copyright (C) the OpenProject GmbH
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License version 3.
|
||||
|
||||
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
Copyright (C) 2010-2013 the ChiliProject Team
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
See COPYRIGHT and LICENSE files for more details.
|
||||
|
||||
++#%>
|
||||
|
||||
<%=
|
||||
flex_layout do |container|
|
||||
container.with_row(mb: 3) do
|
||||
admin_settings_primer_form_with(model: project, url: project_settings_general_path(project)) do |f|
|
||||
concat(
|
||||
render(Primer::Beta::Subhead.new) do |component|
|
||||
component.with_heading(tag: :h2, size: :medium) { I18n.t("projects.settings.header_details") }
|
||||
end
|
||||
)
|
||||
concat(
|
||||
render(
|
||||
Primer::Forms::FormList.new(
|
||||
Projects::Settings::NameForm.new(f),
|
||||
Projects::Settings::DescriptionForm.new(f),
|
||||
Projects::Settings::Submit.new(f, label: I18n.t("projects.settings.button_update_details"))
|
||||
)
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
container.with_row(mb: 3) do
|
||||
admin_settings_primer_form_with(model: project, url: project_settings_general_path(project)) do |f|
|
||||
concat(
|
||||
render(Primer::Beta::Subhead.new) do |component|
|
||||
component.with_heading(tag: :h2, size: :medium) { I18n.t("projects.settings.header_status") }
|
||||
end
|
||||
)
|
||||
concat(
|
||||
render(
|
||||
Primer::Forms::FormList.new(
|
||||
Projects::Settings::StatusForm.new(f),
|
||||
Projects::Settings::Submit.new(f, label: I18n.t("projects.settings.button_update_status"))
|
||||
)
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
container.with_row(mb: 3) do
|
||||
admin_settings_primer_form_with(model: project, url: project_settings_general_path(project)) do |f|
|
||||
concat(
|
||||
render(Primer::Beta::Subhead.new) do |component|
|
||||
component.with_heading(tag: :h2, size: :medium) { I18n.t("projects.settings.header_relations") }
|
||||
end
|
||||
)
|
||||
concat(
|
||||
render(
|
||||
Primer::Forms::FormList.new(
|
||||
Projects::Settings::RelationsForm.new(f),
|
||||
Projects::Settings::Submit.new(f, label: I18n.t("projects.settings.button_update_parent_project"))
|
||||
)
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
%>
|
||||
@@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# -- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 2010-2024 the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
# ++
|
||||
|
||||
module Projects
|
||||
module Settings
|
||||
module General
|
||||
class ShowComponent < ApplicationComponent
|
||||
include ApplicationHelper
|
||||
include OpPrimer::ComponentHelpers
|
||||
include OpTurbo::Streamable
|
||||
|
||||
options :project
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -42,7 +42,7 @@
|
||||
anchor_align: :end
|
||||
},
|
||||
button_arguments: {
|
||||
icon: "op-kebab-vertical",
|
||||
icon: "kebab-horizontal",
|
||||
"aria-label": t(:label_more),
|
||||
test_selector: "project-settings-more-menu"
|
||||
}
|
||||
@@ -61,6 +61,20 @@
|
||||
end
|
||||
end
|
||||
|
||||
if User.current.allowed_in_project?(:edit_project, @project)
|
||||
menu.with_item(
|
||||
tag: :a,
|
||||
label: public? ? t(:button_unpublish) : t(:button_publish),
|
||||
href: toggle_public_dialog_project_settings_general_path(@project),
|
||||
content_arguments: {
|
||||
data: { turbo_stream: true },
|
||||
test_selector: "project-settings--toggle-public"
|
||||
}
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: public? ? :lock : :unlock)
|
||||
end
|
||||
end
|
||||
|
||||
if User.current.allowed_in_project?(:archive_project, @project)
|
||||
menu.with_item(
|
||||
tag: :a,
|
||||
@@ -74,6 +88,7 @@
|
||||
item.with_leading_visual_icon(icon: "lock")
|
||||
end
|
||||
end
|
||||
|
||||
if User.current.admin?
|
||||
label = @project.templated ? "remove_from_templates" : "make_template"
|
||||
menu.with_item(
|
||||
|
||||
@@ -36,4 +36,8 @@ class Projects::Settings::IndexPageHeaderComponent < ApplicationComponent
|
||||
|
||||
@project = project
|
||||
end
|
||||
|
||||
attr_reader :project
|
||||
|
||||
delegate :public?, to: :project
|
||||
end
|
||||
|
||||
+20
-7
@@ -27,10 +27,23 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
|
||||
++#%>
|
||||
|
||||
<%= toolbar title: t(:label_version) %>
|
||||
<% html_title t(:label_version) %>
|
||||
|
||||
<%= labelled_tabular_form_with model: @version, url: project_version_path(@project, @version), html: { method: :put } do |f| %>
|
||||
<%= render partial: "form", locals: { f: } %>
|
||||
<%= styled_button_tag t(:button_save), class: "-primary -with-icon icon-checkmark" %>
|
||||
<% end %>
|
||||
<%=
|
||||
render(
|
||||
Primer::OpenProject::DangerDialog.new(
|
||||
id: "projects_toggle_public_confirmation",
|
||||
title: t(:title, scope: i18n_scope),
|
||||
confirm_button_text: t(:'js.button_confirm'),
|
||||
form_arguments: {
|
||||
action: toggle_public_project_settings_general_path(@project),
|
||||
method: :post,
|
||||
data: { turbo: true }
|
||||
}
|
||||
)
|
||||
) do |dialog|
|
||||
dialog.with_confirmation_message do |message|
|
||||
message.with_heading(tag: :h2) { t(:title, scope: i18n_scope) }
|
||||
message.with_description_content(t(:description, scope: i18n_scope))
|
||||
end
|
||||
dialog.with_confirmation_check_box_content(t(:checkbox, scope: i18n_scope))
|
||||
end
|
||||
%>
|
||||
@@ -0,0 +1,51 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module Projects::Settings
|
||||
class TogglePublicDialogComponent < ApplicationComponent
|
||||
include ApplicationHelper
|
||||
include OpTurbo::Streamable
|
||||
|
||||
def initialize(project, back_url: nil)
|
||||
super
|
||||
|
||||
@project = project
|
||||
@back_url = back_url
|
||||
end
|
||||
|
||||
def i18n_scope
|
||||
if @project.public?
|
||||
"projects.settings.private_confirmation"
|
||||
else
|
||||
"projects.settings.public_confirmation"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -47,7 +47,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
end
|
||||
end
|
||||
tab_nav.with_tab(selected: selected?(:activities), href: project_settings_work_packages_activities_path) do |tab|
|
||||
tab.with_text { t("label_activity") }
|
||||
tab.with_text { activity_title }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -58,6 +58,14 @@ module Settings
|
||||
def show_custom_fields?
|
||||
User.current.allowed_in_project?(:select_custom_fields, @project)
|
||||
end
|
||||
|
||||
def activity_title
|
||||
label = t("label_activity").html_safe
|
||||
unless EnterpriseToken.allows_to?(:internal_comments)
|
||||
label << render(Primer::Beta::Octicon.new(icon: "op-enterprise-addons", classes: "upsell-colored", ml: 2))
|
||||
end
|
||||
label
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+5
-5
@@ -1,10 +1,10 @@
|
||||
<%=
|
||||
modal_content.with_row(data: { "test-selector": "op-share-dialog-upsale-block" }) do
|
||||
render Primer::OpenProject::FeedbackMessage.new(icon_arguments: { icon: :"op-enterprise-addons", classes: "upsale-colored" }, border: true) do |component|
|
||||
component.with_heading(tag: :h2, classes: "upsale-colored").with_content(I18n.t(:"ee.upsale.title"))
|
||||
component.with_description { I18n.t("sharing.project_queries.upsale.message") }
|
||||
modal_content.with_row(data: { "test-selector": "op-share-dialog-upsell-block" }) do
|
||||
render Primer::OpenProject::FeedbackMessage.new(icon_arguments: { icon: :"op-enterprise-addons", classes: "upsell-colored" }, border: true) do |component|
|
||||
component.with_heading(tag: :h2, classes: "upsell-colored").with_content(I18n.t(:"ee.upsell.title"))
|
||||
component.with_description { I18n.t("sharing.project_queries.upsell.message") }
|
||||
|
||||
href = "#{OpenProject::Static::Links.links[:upsale][:href]}/?utm_source=unknown&utm_medium=community-edition&utm_campaign=project-list-sharing-modal"
|
||||
href = "#{OpenProject::Static::Links.links[:upsell][:href]}/?utm_source=unknown&utm_medium=community-edition&utm_campaign=project-list-sharing-modal"
|
||||
component.with_secondary_action(href:) do |link|
|
||||
link.with_trailing_visual_icon(icon: "link-external")
|
||||
I18n.t("admin.enterprise.enterprise_link")
|
||||
+1
-1
@@ -28,7 +28,7 @@
|
||||
|
||||
module Shares
|
||||
module ProjectQueries
|
||||
class UpsaleComponent < ApplicationComponent # rubocop:disable OpenProject/AddPreviewForViewComponent
|
||||
class UpsellComponent < ApplicationComponent # rubocop:disable OpenProject/AddPreviewForViewComponent
|
||||
include OpPrimer::ComponentHelpers
|
||||
|
||||
def initialize(modal_content:)
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
<%=
|
||||
component_wrapper(tag: "turbo-frame") do
|
||||
render Primer::OpenProject::FeedbackMessage.new(icon_arguments: { icon: :"op-enterprise-addons", classes: "upsale-colored" }, border: true) do |component|
|
||||
component.with_heading(tag: :h2, classes: "upsale-colored").with_content(I18n.t(:"ee.upsale.title"))
|
||||
render Primer::OpenProject::FeedbackMessage.new(icon_arguments: { icon: :"op-enterprise-addons", classes: "upsell-colored" }, border: true) do |component|
|
||||
component.with_heading(tag: :h2, classes: "upsell-colored").with_content(I18n.t(:"ee.upsell.title"))
|
||||
component.with_description { I18n.t("mail.sharing.work_packages.enterprise_text") }
|
||||
|
||||
href = "#{OpenProject::Static::Links.links[:upsale][:href]}/?utm_source=unknown&utm_medium=community-edition&utm_campaign=work-package-sharing-modal"
|
||||
href = "#{OpenProject::Static::Links.links[:upsell][:href]}/?utm_source=unknown&utm_medium=community-edition&utm_campaign=work-package-sharing-modal"
|
||||
component.with_secondary_action(href:) do |link|
|
||||
link.with_trailing_visual_icon(icon: "link-external")
|
||||
I18n.t("admin.enterprise.enterprise_link")
|
||||
+1
-1
@@ -28,7 +28,7 @@
|
||||
|
||||
module Shares
|
||||
module WorkPackages
|
||||
class ModalUpsaleComponent < ApplicationComponent # rubocop:disable OpenProject/AddPreviewForViewComponent
|
||||
class ModalUpsellComponent < ApplicationComponent # rubocop:disable OpenProject/AddPreviewForViewComponent
|
||||
include ApplicationHelper
|
||||
include OpTurbo::Streamable
|
||||
include OpPrimer::ComponentHelpers
|
||||
@@ -27,7 +27,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
|
||||
++#%>
|
||||
|
||||
<div class="generic-table--container <%= container_class %>" data-test-selector="<%= test_selector %>">
|
||||
<div class="generic-table--container <%= container_class %>" data-test-selector="<%= test_selector %>" id="<%= container_id %>">
|
||||
<div class="generic-table--results-container">
|
||||
<table class="generic-table" data-controller="table-highlighting">
|
||||
<colgroup>
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
##
|
||||
# Abstract view component. Subclass this for a concrete table.
|
||||
class TableComponent < ApplicationComponent
|
||||
def initialize(rows: [], **options)
|
||||
super(rows, **options)
|
||||
def initialize(rows: [], **)
|
||||
super(rows, **)
|
||||
end
|
||||
|
||||
class << self
|
||||
@@ -107,7 +107,7 @@ class TableComponent < ApplicationComponent
|
||||
end
|
||||
|
||||
def sortable_columns_correlation
|
||||
sortable_columns.to_h { [_1.to_s, _1.to_s] }
|
||||
sortable_columns.to_h { [it.to_s, it.to_s] }
|
||||
.with_indifferent_access
|
||||
end
|
||||
|
||||
@@ -153,20 +153,22 @@ class TableComponent < ApplicationComponent
|
||||
model
|
||||
end
|
||||
|
||||
def row_class
|
||||
self.class.row_class
|
||||
end
|
||||
delegate :row_class, to: :class
|
||||
|
||||
def container_class
|
||||
nil
|
||||
end
|
||||
|
||||
def container_id
|
||||
nil
|
||||
end
|
||||
|
||||
def columns
|
||||
self.class.columns
|
||||
self.class.columns.reject { skip_column?(it) }
|
||||
end
|
||||
|
||||
def sortable_columns
|
||||
self.class.sortable_columns
|
||||
self.class.sortable_columns.reject { skip_column?(it) }
|
||||
end
|
||||
|
||||
def render_collection(rows)
|
||||
@@ -193,6 +195,10 @@ class TableComponent < ApplicationComponent
|
||||
true
|
||||
end
|
||||
|
||||
def skip_column?(_column)
|
||||
false
|
||||
end
|
||||
|
||||
def sortable_column?(column)
|
||||
sortable? && sortable_columns.include?(column.to_sym)
|
||||
end
|
||||
|
||||
@@ -39,7 +39,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
feature = tab[:enterprise_feature]
|
||||
|
||||
if feature && !EnterpriseToken.allows_to?(feature)
|
||||
t.with_icon(icon: :"op-enterprise-addons", classes: "upsale-colored")
|
||||
t.with_icon(icon: :"op-enterprise-addons", classes: "upsell-colored")
|
||||
end
|
||||
t.with_text { I18n.t(tab[:label]) }
|
||||
end
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
<%#-- copyright
|
||||
OpenProject is an open source project management software.
|
||||
Copyright (C) the OpenProject GmbH
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License version 3.
|
||||
|
||||
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
Copyright (C) 2010-2013 the ChiliProject Team
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
See COPYRIGHT and LICENSE files for more details.
|
||||
|
||||
++#%>
|
||||
|
||||
<%=
|
||||
render Primer::OpenProject::PageHeader.new do |header|
|
||||
header.with_title(test_selector: "wiki-page-header-title") { h(@page.title) }
|
||||
header.with_breadcrumbs(
|
||||
helpers.breadcrumb_for_page(@project, @page),
|
||||
data: { "test-selector": "wiki-page-header-breadcrumbs" }
|
||||
)
|
||||
if @editable
|
||||
if show_edit?
|
||||
header.with_action_button(
|
||||
tag: :a,
|
||||
mobile_icon: :pencil,
|
||||
mobile_label: t(:button_edit),
|
||||
size: :medium,
|
||||
href: url_for(controller: "wiki", action: "edit", id: @page),
|
||||
aria: { label: I18n.t(:button_edit) },
|
||||
data: { "test-selector": "wiki-edit-action-button" },
|
||||
title: I18n.t(:button_edit)
|
||||
) do |button|
|
||||
button.with_leading_visual_icon(icon: :pencil)
|
||||
t(:button_edit)
|
||||
end
|
||||
end
|
||||
helpers.watcher_action_button(header, @page.object)
|
||||
unless @page.current_version?
|
||||
header.with_action_button(
|
||||
tag: :a,
|
||||
mobile_icon: :book,
|
||||
mobile_label: t(:label_history),
|
||||
size: :medium,
|
||||
href: url_for(controller: "wiki", action: "history", id: @page),
|
||||
aria: { label: t(:label_history) },
|
||||
title: t(:label_history)
|
||||
) do |button|
|
||||
button.with_leading_visual_icon(icon: "book")
|
||||
t(:label_history)
|
||||
end
|
||||
end
|
||||
end
|
||||
header.with_action_menu(
|
||||
menu_arguments: { anchor_align: :end },
|
||||
button_arguments: {
|
||||
icon: "kebab-horizontal",
|
||||
"aria-label": t(:label_more),
|
||||
data: { "test-selector": "wiki-more-dropdown-menu" }
|
||||
}
|
||||
) do |menu|
|
||||
if @editable
|
||||
if User.current.allowed_in_project?(:protect_wiki_pages, @project)
|
||||
menu.with_item(
|
||||
label: lock_data[:label],
|
||||
tag: :a,
|
||||
size: :medium,
|
||||
content_arguments: { data: { method: :post } },
|
||||
href: url_for(controller: "wiki", action: "protect", id: @page, protected: lock_data[:protected])
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: lock_data[:icon])
|
||||
end
|
||||
end
|
||||
if @page.current_version?
|
||||
if User.current.allowed_in_project?(:rename_wiki_pages, @project)
|
||||
menu.with_item(
|
||||
label: t(:button_rename),
|
||||
tag: :a,
|
||||
size: :medium,
|
||||
data: { "test-selector": "wiki-rename-action-menu-item" },
|
||||
href: url_for(controller: "wiki", action: "rename", id: @page)
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: "arrow-switch")
|
||||
end
|
||||
end
|
||||
if User.current.allowed_in_project?(:change_wiki_parent_page, @project)
|
||||
menu.with_item(
|
||||
label: t(:button_change_parent_page),
|
||||
tag: :a,
|
||||
size: :medium,
|
||||
href: url_for(controller: "wiki", action: "edit_parent_page", id: @page)
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: :link)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if User.current.allowed_in_project?(:delete_wiki_pages, @project)
|
||||
menu.with_item(
|
||||
label: t(:button_delete),
|
||||
scheme: :danger,
|
||||
tag: :a,
|
||||
size: :medium,
|
||||
content_arguments: { data: { confirm: t(:text_are_you_sure), method: :delete } },
|
||||
href: url_for(controller: "wiki", action: "destroy", id: @page),
|
||||
data: { "test-selector": "wiki-delete-action-menu-item" }
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: :trash)
|
||||
end
|
||||
end
|
||||
if show_rollback?
|
||||
menu.with_item(
|
||||
label: t(:button_rollback),
|
||||
href: url_for(controller: "wiki", action: "edit", id: @page, version: @page.version)
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: :x)
|
||||
end
|
||||
end
|
||||
if User.current.allowed_in_project?(:view_wiki_edits, @project)
|
||||
menu.with_item(
|
||||
label: t(:label_history),
|
||||
href: url_for(controller: "wiki", action: "history", id: @page),
|
||||
data: { "test-selector": "wiki-history-action-menu-item" }
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: :history)
|
||||
end
|
||||
end
|
||||
if User.current.allowed_in_project?(:manage_wiki_menu, @project)
|
||||
menu.with_item(
|
||||
label: t(:button_manage_menu_entry),
|
||||
href: url_for(controller: "/wiki_menu_items", action: "edit", project_id: @project.identifier, id: @page),
|
||||
data: { "test-selector": "wiki-configure-menu-action-menu-item" }
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: :gear)
|
||||
end
|
||||
end
|
||||
menu.with_item(
|
||||
tag: :a,
|
||||
label: t(:button_print),
|
||||
content_arguments: { data: {
|
||||
controller: "print",
|
||||
action: "print#triggerPrint"
|
||||
} },
|
||||
title: t("wiki.print_hint"),
|
||||
href: ""
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: "op-printer")
|
||||
end
|
||||
if User.current.allowed_in_project?(:export_wiki_pages, @project)
|
||||
menu.with_item(
|
||||
tag: :a,
|
||||
label: t("js.label_export"),
|
||||
size: :medium,
|
||||
href: "",
|
||||
title: t("js.label_export"),
|
||||
classes: "modal-delivery-element--activation-link"
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: :download)
|
||||
end
|
||||
end
|
||||
menu.with_item(
|
||||
tag: :a,
|
||||
label: t(:label_table_of_contents),
|
||||
title: t("wiki.print_hint"),
|
||||
href: url_for({ controller: "/wiki", action: "index", project_id: @project.identifier, id: @page })
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: "op-view-list")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
%>
|
||||
|
||||
<section data-augmented-model-wrapper
|
||||
data-modal-class-name="wiki-export---modal">
|
||||
<%= link_to t("js.label_export"),
|
||||
"",
|
||||
title: t("js.label_export"),
|
||||
style: "display: none;" %>
|
||||
<%= render partial: "wiki/wiki_export_modal" %>
|
||||
</section>
|
||||
@@ -0,0 +1,59 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module WikiPages
|
||||
class ShowPageHeaderComponent < ApplicationComponent
|
||||
include OpPrimer::ComponentHelpers
|
||||
include ApplicationHelper
|
||||
|
||||
def initialize(page:, project:, editable:)
|
||||
super
|
||||
@page = page
|
||||
@project = project
|
||||
@editable = editable
|
||||
end
|
||||
|
||||
def lock_data
|
||||
if @page.protected?
|
||||
{ label: t(:button_unlock), icon: :unlock, protected: 0 }
|
||||
else
|
||||
{ label: t(:button_lock), icon: :lock, protected: 1 }
|
||||
end
|
||||
end
|
||||
|
||||
def show_edit?
|
||||
User.current.allowed_in_project?(:edit_wiki_pages, @project) && @page.current_version?
|
||||
end
|
||||
|
||||
def show_rollback?
|
||||
User.current.allowed_in_project?(:edit_wiki_pages, @project) && !@page.current_version?
|
||||
end
|
||||
end
|
||||
end
|
||||
+15
-5
@@ -27,8 +27,18 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
|
||||
++#%>
|
||||
|
||||
<%= error_messages_for "version" %>
|
||||
|
||||
<% if @project.enabled_modules.map(&:name).include?("backlogs") %>
|
||||
<%= version_settings_fields @version, @project %>
|
||||
<% end %>
|
||||
<%=
|
||||
render(Primer::OpenProject::SubHeader.new) do |subheader|
|
||||
subheader.with_action_button(
|
||||
scheme: :primary,
|
||||
aria: { label: t("wiki.index.no_results_content_text") },
|
||||
title: t("wiki.index.no_results_content_text"),
|
||||
tag: :a,
|
||||
test_selector: "wiki-new-child-button",
|
||||
href: url_for({ controller: "/wiki", action: "new_child", project_id: @project.identifier, id: @page })
|
||||
) do |button|
|
||||
button.with_leading_visual_icon(icon: :plus)
|
||||
t(:create_wiki_page_button)
|
||||
end
|
||||
end
|
||||
%>
|
||||
@@ -0,0 +1,42 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module WikiPages
|
||||
class ShowSubHeaderComponent < ApplicationComponent
|
||||
include OpPrimer::ComponentHelpers
|
||||
include ApplicationHelper
|
||||
|
||||
def initialize(page:, project:)
|
||||
super
|
||||
@page = page
|
||||
@project = project
|
||||
end
|
||||
end
|
||||
end
|
||||
+3
-2
@@ -9,8 +9,9 @@
|
||||
d.with_header(variant: :large)
|
||||
d.with_body(classes: body_classes) do
|
||||
render(
|
||||
WorkPackageRelationsTab::AddWorkPackageChildFormComponent.new(
|
||||
work_package: @work_package
|
||||
WorkPackageRelationsTab::AddWorkPackageHierarchyFormComponent.new(
|
||||
work_package:,
|
||||
relation_type:
|
||||
)
|
||||
)
|
||||
end
|
||||
+8
-7
@@ -28,28 +28,29 @@
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
class WorkPackageRelationsTab::AddWorkPackageChildDialogComponent < ApplicationComponent
|
||||
class WorkPackageRelationsTab::AddWorkPackageHierarchyDialogComponent < ApplicationComponent
|
||||
include ApplicationHelper
|
||||
include OpTurbo::Streamable
|
||||
include OpPrimer::ComponentHelpers
|
||||
|
||||
I18N_NAMESPACE = "work_package_relations_tab"
|
||||
DIALOG_ID = "add-work-package-child-dialog"
|
||||
FORM_ID = "add-work-package-child-form"
|
||||
DIALOG_ID = "add-work-package-hierarchy-dialog"
|
||||
FORM_ID = "add-work-package-hierarchy-form"
|
||||
|
||||
attr_reader :work_package
|
||||
attr_reader :work_package, :relation_type
|
||||
|
||||
def initialize(work_package:)
|
||||
def initialize(work_package:, relation_type:)
|
||||
super()
|
||||
|
||||
@work_package = work_package
|
||||
@relation_type = relation_type
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def dialog_title
|
||||
child_label = t("#{I18N_NAMESPACE}.relations.label_child_singular")
|
||||
t("#{I18N_NAMESPACE}.label_add_x", x: child_label)
|
||||
hierarchy_label = t("#{I18N_NAMESPACE}.relations.label_#{relation_type}_singular")
|
||||
t("#{I18N_NAMESPACE}.label_add_x", x: hierarchy_label)
|
||||
end
|
||||
|
||||
def body_classes
|
||||
+15
-11
@@ -1,24 +1,27 @@
|
||||
<%= component_wrapper do %>
|
||||
<%= primer_form_with(
|
||||
<%=
|
||||
primer_form_with(
|
||||
id: FORM_ID,
|
||||
model: @child,
|
||||
model: related,
|
||||
**submit_url_options,
|
||||
data: {
|
||||
turbo: true,
|
||||
update_work_package: true
|
||||
}
|
||||
) do |f| %>
|
||||
) do |f|
|
||||
%>
|
||||
<%# Form fields section %>
|
||||
<%= flex_layout(mb: 3) do |flex|
|
||||
<%=
|
||||
flex_layout(mb: 3) do |flex|
|
||||
flex.with_row do
|
||||
if @base_errors&.any?
|
||||
render(Primer::Alpha::Banner.new(mb: 3, icon: :stop, scheme: :danger)) { @base_errors.join("\n") }
|
||||
if base_errors.any?
|
||||
render(Primer::Alpha::Banner.new(mb: 3, icon: :stop, scheme: :danger)) { base_errors.join("\n") }
|
||||
end
|
||||
end
|
||||
flex.with_row do
|
||||
# @workpackage is not available inside the render_inline_form block
|
||||
# so we need to re-define them here. Figure out solution for this.
|
||||
url = ::API::V3::Utilities::PathHelper::ApiV3Path.work_package_available_relation_candidates(@work_package.id, type: Relation::TYPE_CHILD)
|
||||
url = ::API::V3::Utilities::PathHelper::ApiV3Path.work_package_available_relation_candidates(work_package.id, type: relation_type)
|
||||
render_inline_form(f) do |my_form|
|
||||
my_form.work_package_autocompleter(
|
||||
name: :id,
|
||||
@@ -26,18 +29,19 @@
|
||||
required: true,
|
||||
visually_hide_label: false,
|
||||
autocomplete_options: {
|
||||
resource: 'work_packages',
|
||||
searchKey: 'subjectOrId',
|
||||
resource: "work_packages",
|
||||
searchKey: "subjectOrId",
|
||||
url:,
|
||||
openDirectly: false,
|
||||
focusDirectly: true,
|
||||
dropdownPosition: 'bottom',
|
||||
dropdownPosition: "bottom",
|
||||
appendTo: "##{DIALOG_ID}",
|
||||
data: { test_selector: ID_FIELD_TEST_SELECTOR }
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end %>
|
||||
end
|
||||
%>
|
||||
<% end %>
|
||||
<% end %>
|
||||
+11
-8
@@ -28,26 +28,29 @@
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
class WorkPackageRelationsTab::AddWorkPackageChildFormComponent < ApplicationComponent
|
||||
class WorkPackageRelationsTab::AddWorkPackageHierarchyFormComponent < ApplicationComponent
|
||||
include ApplicationHelper
|
||||
include OpTurbo::Streamable
|
||||
include OpPrimer::ComponentHelpers
|
||||
|
||||
DIALOG_ID = "add-work-package-child-dialog"
|
||||
FORM_ID = "add-work-package-child-form"
|
||||
ID_FIELD_TEST_SELECTOR = "work-package-child-form-id"
|
||||
DIALOG_ID = "add-work-package-hierarchy-dialog"
|
||||
FORM_ID = "add-work-package-hierarchy-form"
|
||||
ID_FIELD_TEST_SELECTOR = "work-package-hierarchy-form-id"
|
||||
I18N_NAMESPACE = "work_package_relations_tab"
|
||||
|
||||
def initialize(work_package:, child: nil, base_errors: nil)
|
||||
attr_reader :work_package, :relation_type, :related, :base_errors
|
||||
|
||||
def initialize(work_package:, relation_type:, related: nil, base_errors: nil)
|
||||
super()
|
||||
|
||||
@work_package = work_package
|
||||
@child = child.presence || WorkPackage.new
|
||||
@base_errors = base_errors
|
||||
@relation_type = relation_type
|
||||
@related = related.presence || WorkPackage.new
|
||||
@base_errors = base_errors || []
|
||||
end
|
||||
|
||||
def submit_url_options
|
||||
{ method: :post,
|
||||
url: work_package_children_relations_path(@work_package) }
|
||||
url: work_package_hierarchy_relations_path(work_package, relation_type:) }
|
||||
end
|
||||
end
|
||||
@@ -12,8 +12,8 @@
|
||||
action_bar.with_column(flex_shrink: 0, ml: 2) do
|
||||
render(
|
||||
Primer::Alpha::ActionMenu.new(
|
||||
test_selector: NEW_RELATION_ACTION_MENU,
|
||||
menu_id: NEW_RELATION_ACTION_MENU
|
||||
test_selector: ADD_RELATION_ACTION_MENU,
|
||||
menu_id: ADD_RELATION_ACTION_MENU
|
||||
)
|
||||
) do |menu|
|
||||
menu.with_show_button do |button|
|
||||
@@ -22,23 +22,7 @@
|
||||
t(:label_relation)
|
||||
end
|
||||
|
||||
render_child_menu_items(menu)
|
||||
|
||||
if should_render_add_relations?
|
||||
Relation::TYPES.each do |relation_type, type_configuration_hash|
|
||||
label_key = "#{I18N_NAMESPACE}.relations.#{type_configuration_hash[:name]}_singular"
|
||||
menu.with_item(
|
||||
label: t(label_key).upcase_first,
|
||||
href: new_relation_path(relation_type:),
|
||||
test_selector: new_button_test_selector(relation_type:),
|
||||
content_arguments: {
|
||||
data: { turbo_stream: true }
|
||||
}
|
||||
) do |item|
|
||||
item.with_description.with_content(t("#{I18N_NAMESPACE}.relations.#{relation_type}_description"))
|
||||
end
|
||||
end
|
||||
end
|
||||
render_add_relations_menu_items(menu, ADD_RELATION_MENU_TYPES)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,9 +8,31 @@
|
||||
# - Child work packages
|
||||
class WorkPackageRelationsTab::IndexComponent < ApplicationComponent
|
||||
FRAME_ID = "work-package-relations-tab-content"
|
||||
NEW_RELATION_ACTION_MENU = "new-relation-action-menu"
|
||||
NEW_CHILD_ACTION_MENU = "new-child-action-menu"
|
||||
ADD_RELATION_ACTION_MENU = "add-relation-action-menu"
|
||||
ADD_CHILD_ACTION_MENU = "add-child-action-menu"
|
||||
I18N_NAMESPACE = "work_package_relations_tab"
|
||||
|
||||
ADD_CHILD_MENU_TYPES = [
|
||||
"new_child",
|
||||
Relation::TYPE_CHILD
|
||||
].freeze
|
||||
|
||||
ADD_RELATION_MENU_TYPES = [
|
||||
*ADD_CHILD_MENU_TYPES,
|
||||
Relation::TYPE_RELATES,
|
||||
Relation::TYPE_FOLLOWS,
|
||||
Relation::TYPE_PRECEDES,
|
||||
Relation::TYPE_PARENT,
|
||||
Relation::TYPE_DUPLICATES,
|
||||
Relation::TYPE_DUPLICATED,
|
||||
Relation::TYPE_BLOCKS,
|
||||
Relation::TYPE_BLOCKED,
|
||||
Relation::TYPE_INCLUDES,
|
||||
Relation::TYPE_PARTOF,
|
||||
Relation::TYPE_REQUIRES,
|
||||
Relation::TYPE_REQUIRED
|
||||
].freeze
|
||||
|
||||
include ApplicationHelper
|
||||
include OpPrimer::ComponentHelpers
|
||||
include Turbo::FramesHelper
|
||||
@@ -44,15 +66,23 @@ class WorkPackageRelationsTab::IndexComponent < ApplicationComponent
|
||||
def should_render_add_child?
|
||||
return false if work_package.milestone?
|
||||
|
||||
helpers.current_user.allowed_in_project?(:manage_subtasks, work_package.project)
|
||||
allowed_to?(:manage_subtasks)
|
||||
end
|
||||
|
||||
def should_render_add_parent?
|
||||
allowed_to?(:manage_subtasks)
|
||||
end
|
||||
|
||||
def should_render_add_relations?
|
||||
helpers.current_user.allowed_in_project?(:manage_work_package_relations, work_package.project)
|
||||
allowed_to?(:manage_work_package_relations)
|
||||
end
|
||||
|
||||
def allowed_to?(permission)
|
||||
helpers.current_user.allowed_in_project?(permission, work_package.project)
|
||||
end
|
||||
|
||||
def should_render_create_button?
|
||||
should_render_add_child? || should_render_add_relations?
|
||||
should_render_add_child? || should_render_add_parent? || should_render_add_relations?
|
||||
end
|
||||
|
||||
def render_relation_group(title:, relation_group:, &)
|
||||
@@ -85,46 +115,74 @@ class WorkPackageRelationsTab::IndexComponent < ApplicationComponent
|
||||
concat render(Primer::Beta::Counter.new(count:, round: true, scheme: :primary))
|
||||
end
|
||||
header.with_column do
|
||||
render(Primer::Alpha::ActionMenu.new(menu_id: NEW_CHILD_ACTION_MENU)) do |menu|
|
||||
render(Primer::Alpha::ActionMenu.new(menu_id: ADD_CHILD_ACTION_MENU)) do |menu|
|
||||
menu.with_show_button do |button|
|
||||
button.with_leading_visual_icon(icon: :plus)
|
||||
button.with_trailing_action_icon(icon: :"triangle-down")
|
||||
t("work_package_relations_tab.label_add_child_button")
|
||||
end
|
||||
|
||||
render_child_menu_items(menu)
|
||||
render_add_relations_menu_items(menu, ADD_CHILD_MENU_TYPES)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def render_child_menu_items(menu) # rubocop:disable Metrics/AbcSize
|
||||
return unless should_render_add_child?
|
||||
|
||||
if helpers.current_user.allowed_in_project?(:add_work_packages, work_package.project)
|
||||
menu.with_item(
|
||||
label: t("work_package_relations_tab.relations.new_child"),
|
||||
href: new_project_work_packages_dialog_path(work_package.project, parent_id: work_package.id),
|
||||
content_arguments: {
|
||||
data: { turbo_stream: true }
|
||||
}
|
||||
) do |item|
|
||||
item.with_description.with_content(t("work_package_relations_tab.relations.new_child_text"))
|
||||
end
|
||||
end
|
||||
# Renders the menu items for the add relations action menu
|
||||
#
|
||||
# @param menu [Primer::Alpha::ActionMenu] The action menu component to render the items in
|
||||
# @param relation_types [Array<String>] The relation types to render menu items for
|
||||
def render_add_relations_menu_items(menu, relation_types)
|
||||
relation_types
|
||||
.filter { |relation_type| can_add_relation?(relation_type) }
|
||||
.each { |relation_type| render_add_relation_menu_item(menu, relation_type) }
|
||||
end
|
||||
|
||||
def render_add_relation_menu_item(menu, relation_type)
|
||||
menu.with_item(
|
||||
label: t("work_package_relations_tab.relations.child"),
|
||||
href: new_work_package_children_relation_path(work_package),
|
||||
label: label(relation_type),
|
||||
href: new_relation_path(relation_type),
|
||||
test_selector: new_button_test_selector(relation_type),
|
||||
content_arguments: {
|
||||
data: { turbo_stream: true }
|
||||
}
|
||||
) do |item|
|
||||
item.with_description.with_content(t("work_package_relations_tab.relations.child_description"))
|
||||
item.with_description.with_content(description(relation_type))
|
||||
end
|
||||
end
|
||||
|
||||
def can_add_relation?(relation_type)
|
||||
case relation_type
|
||||
when "new_child"
|
||||
should_render_add_child? && allowed_to?(:add_work_packages)
|
||||
when Relation::TYPE_CHILD
|
||||
should_render_add_child?
|
||||
when Relation::TYPE_PARENT
|
||||
should_render_add_parent?
|
||||
when *Relation::TYPES.keys
|
||||
should_render_add_relations?
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def label(relation_type)
|
||||
label_key =
|
||||
if Relation::TYPES.key?(relation_type)
|
||||
"#{Relation::TYPES[relation_type][:name]}_singular"
|
||||
else
|
||||
relation_type
|
||||
end
|
||||
|
||||
label = t("#{I18N_NAMESPACE}.relations.#{label_key}")
|
||||
label.upcase_first
|
||||
end
|
||||
|
||||
def description(relation_type)
|
||||
I18n.t("#{I18N_NAMESPACE}.relations.#{relation_type}_description")
|
||||
end
|
||||
|
||||
def render_items(border_box, relation_items)
|
||||
relation_items.each do |relation_item|
|
||||
relation = relation_item.relation || relation_item.related
|
||||
@@ -154,17 +212,20 @@ class WorkPackageRelationsTab::IndexComponent < ApplicationComponent
|
||||
&& item.instance_of?(relation_to_scroll_to.class)
|
||||
end
|
||||
|
||||
def new_relation_path(relation_type:)
|
||||
raise ArgumentError, "Invalid relation type: #{relation_type}" unless Relation::TYPES.key?(relation_type)
|
||||
|
||||
if relation_type == Relation::TYPE_CHILD
|
||||
raise NotImplementedError, "Child relations are not supported yet"
|
||||
else
|
||||
def new_relation_path(relation_type)
|
||||
case relation_type
|
||||
when "new_child"
|
||||
new_project_work_packages_dialog_path(work_package.project, parent_id: work_package.id)
|
||||
when Relation::TYPE_CHILD, Relation::TYPE_PARENT
|
||||
new_work_package_hierarchy_relation_path(work_package, relation_type:)
|
||||
when *Relation::TYPES.keys
|
||||
new_work_package_relation_path(work_package, relation_type:)
|
||||
else
|
||||
raise ArgumentError, "Invalid relation type: #{relation_type}"
|
||||
end
|
||||
end
|
||||
|
||||
def new_button_test_selector(relation_type:)
|
||||
def new_button_test_selector(relation_type)
|
||||
"op-new-relation-button-#{relation_type}"
|
||||
end
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// We reference an ID as one is required to be specified for the action menu list.
|
||||
// It can't be nested inside the BEM model as it's placed as a #top-layer element.
|
||||
#new-relation-action-menu-list,
|
||||
#new-child-action-menu-list
|
||||
max-height: 470px
|
||||
#add-relation-action-menu-list,
|
||||
#add-child-action-menu-list
|
||||
max-height: 510px
|
||||
max-width: 280px
|
||||
|
||||
@@ -51,25 +51,21 @@ class WorkPackageRelationsTab::RelationComponent < ApplicationComponent
|
||||
relation_item.related
|
||||
end
|
||||
|
||||
def child
|
||||
relation_item.related if parent_child_relationship?
|
||||
end
|
||||
|
||||
def editable? = editable
|
||||
|
||||
private
|
||||
|
||||
def parent_child_relationship? = relation.nil?
|
||||
def hierarchy_relationship? = relation.nil?
|
||||
|
||||
def should_render_edit_option?
|
||||
# Children have nothing to edit as it's not a relation.
|
||||
!parent_child_relationship? && allowed_to_manage_relations?
|
||||
# Children and parent can not be edited as it's not a relation.
|
||||
!hierarchy_relationship? && allowed_to_manage_relations?
|
||||
end
|
||||
|
||||
def should_render_action_menu?
|
||||
return false unless editable?
|
||||
|
||||
if parent_child_relationship?
|
||||
if hierarchy_relationship?
|
||||
allowed_to_manage_subtasks?
|
||||
else
|
||||
allowed_to_manage_relations?
|
||||
@@ -78,7 +74,7 @@ class WorkPackageRelationsTab::RelationComponent < ApplicationComponent
|
||||
|
||||
def allowed_to_manage_subtasks?
|
||||
helpers.current_user.allowed_in_project?(:manage_subtasks, work_package.project) &&
|
||||
helpers.current_user.allowed_in_project?(:manage_subtasks, child.project)
|
||||
helpers.current_user.allowed_in_project?(:manage_subtasks, related_work_package.project)
|
||||
end
|
||||
|
||||
def allowed_to_manage_relations?
|
||||
@@ -86,7 +82,7 @@ class WorkPackageRelationsTab::RelationComponent < ApplicationComponent
|
||||
end
|
||||
|
||||
def should_display_description?
|
||||
return false if parent_child_relationship?
|
||||
return false if hierarchy_relationship?
|
||||
|
||||
relation.description.present?
|
||||
end
|
||||
@@ -96,32 +92,32 @@ class WorkPackageRelationsTab::RelationComponent < ApplicationComponent
|
||||
end
|
||||
|
||||
def should_display_dates_row?
|
||||
parent_child_relationship? || relation.follows? || relation.precedes?
|
||||
hierarchy_relationship? || relation.follows? || relation.precedes?
|
||||
end
|
||||
|
||||
def follows?
|
||||
return false if parent_child_relationship?
|
||||
return false if hierarchy_relationship?
|
||||
|
||||
relation.relation_type_for(work_package) == Relation::TYPE_FOLLOWS
|
||||
end
|
||||
|
||||
def precedes?
|
||||
return false if parent_child_relationship?
|
||||
return false if hierarchy_relationship?
|
||||
|
||||
relation.relation_type_for(work_package) == Relation::TYPE_PRECEDES
|
||||
end
|
||||
|
||||
def edit_path
|
||||
if parent_child_relationship?
|
||||
raise NotImplementedError, "Children relationships are not editable"
|
||||
if hierarchy_relationship?
|
||||
raise NotImplementedError, "Children and parent relationships are not editable"
|
||||
else
|
||||
edit_work_package_relation_path(work_package, relation)
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_path
|
||||
if parent_child_relationship?
|
||||
work_package_children_relation_path(work_package, child)
|
||||
if hierarchy_relationship?
|
||||
work_package_hierarchy_relation_path(work_package, related_work_package)
|
||||
else
|
||||
work_package_relation_path(work_package, relation)
|
||||
end
|
||||
|
||||
@@ -37,17 +37,13 @@ class WorkPackageRelationsTab::RelationsMediator
|
||||
end
|
||||
|
||||
def count
|
||||
visible_relations.count + ghost_relations.count
|
||||
[visible_relations, ghost_relations].sum(&:count)
|
||||
end
|
||||
|
||||
def any?
|
||||
visible_relations.any? || ghost_relations.any?
|
||||
end
|
||||
|
||||
def all_relations
|
||||
visible_relations + ghost_relations
|
||||
end
|
||||
|
||||
def all_relation_items
|
||||
(visible_relation_items + ghost_relation_items).sort_by(&:order_key)
|
||||
end
|
||||
@@ -86,10 +82,11 @@ class WorkPackageRelationsTab::RelationsMediator
|
||||
# relation
|
||||
RelationItem = Data.define(:type, :work_package, :related, :relation, :visibility, :closest) do
|
||||
def initialize(type:, work_package:, relation:, visibility: :visible, closest: false)
|
||||
type = ActiveSupport::StringInquirer.new(type.to_s)
|
||||
if relation.is_a?(Relation)
|
||||
related = relation.other_work_package(work_package)
|
||||
else
|
||||
related = relation # for parent-child relations, `relation` parameter holds the child work package
|
||||
related = relation # for parent-child relations, `relation` parameter holds the child or parent work package
|
||||
relation = nil
|
||||
end
|
||||
super(type:, work_package:, related:, relation:, visibility:, closest:)
|
||||
@@ -114,6 +111,10 @@ class WorkPackageRelationsTab::RelationsMediator
|
||||
@visible_relations ||= work_package.relations.visible.includes(:to, :from).load
|
||||
end
|
||||
|
||||
def visible_parents
|
||||
@visible_parents ||= work_package.parent_id && work_package.parent.visible? ? [work_package.parent] : []
|
||||
end
|
||||
|
||||
def visible_children
|
||||
@visible_children ||= work_package.children.visible.load
|
||||
end
|
||||
@@ -122,6 +123,10 @@ class WorkPackageRelationsTab::RelationsMediator
|
||||
@ghost_relations ||= work_package.relations.includes(:to, :from).where.not(id: visible_relations.select(:id)).load
|
||||
end
|
||||
|
||||
def ghost_parents
|
||||
@ghost_parents ||= work_package.parent_id && !work_package.parent.visible? ? [work_package.parent] : []
|
||||
end
|
||||
|
||||
def ghost_children
|
||||
@ghost_children ||= work_package.children.where.not(id: visible_children.select(:id)).load
|
||||
end
|
||||
@@ -132,7 +137,15 @@ class WorkPackageRelationsTab::RelationsMediator
|
||||
end
|
||||
|
||||
def relation_group(type)
|
||||
if type == Relation::TYPE_CHILD
|
||||
case type
|
||||
when Relation::TYPE_PARENT
|
||||
RelationGroup.new(
|
||||
type:,
|
||||
work_package:,
|
||||
visible_relations: visible_parents,
|
||||
ghost_relations: ghost_parents
|
||||
)
|
||||
when Relation::TYPE_CHILD
|
||||
RelationGroup.new(
|
||||
type:,
|
||||
work_package:,
|
||||
@@ -150,7 +163,11 @@ class WorkPackageRelationsTab::RelationsMediator
|
||||
end
|
||||
|
||||
def all_relations_count
|
||||
visible_relations.count + ghost_relations.count + visible_children.count + ghost_children.count
|
||||
[
|
||||
visible_relations, ghost_relations,
|
||||
visible_parents, ghost_parents,
|
||||
visible_children, ghost_children
|
||||
].sum(&:count)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -74,14 +74,14 @@ module WorkPackages
|
||||
def add_comment_wrapper_data_attributes
|
||||
{
|
||||
test_selector: "op-work-package-journal--new-comment-component",
|
||||
controller: restricted_comment_stimulus_controller,
|
||||
controller: internal_comment_stimulus_controller,
|
||||
"application-target": "dynamic",
|
||||
restricted_comment_stimulus_controller("-target") => "formContainer",
|
||||
action: index_stimulus_controller(":onSubmit-end@window->#{restricted_comment_stimulus_controller}#onSubmitEnd"),
|
||||
restricted_comment_stimulus_controller("-highlight-class") => "work-packages-activities-tab-journals-new-component--journal-notes-body__restricted-comment", # rubocop:disable Layout/LineLength
|
||||
restricted_comment_stimulus_controller("-hidden-class") => "d-none",
|
||||
restricted_comment_stimulus_controller("-#{index_stimulus_controller}-outlet") => "##{wrapper_key}",
|
||||
restricted_comment_stimulus_controller("-is-restricted-value") => false # Initial value
|
||||
internal_comment_stimulus_controller("-target") => "formContainer",
|
||||
action: index_stimulus_controller(":onSubmit-end@window->#{internal_comment_stimulus_controller}#onSubmitEnd"),
|
||||
internal_comment_stimulus_controller("-highlight-class") => "work-packages-activities-tab-journals-new-component--journal-notes-body__internal-comment", # rubocop:disable Layout/LineLength
|
||||
internal_comment_stimulus_controller("-hidden-class") => "d-none",
|
||||
internal_comment_stimulus_controller("-#{index_stimulus_controller}-outlet") => "##{wrapper_key}",
|
||||
internal_comment_stimulus_controller("-is-internal-value") => false # Initial value
|
||||
}
|
||||
end
|
||||
|
||||
@@ -95,7 +95,7 @@ module WorkPackages
|
||||
end
|
||||
|
||||
def adding_comment_allowed?
|
||||
User.current.allowed_in_work_package?(:add_work_package_notes, @work_package)
|
||||
User.current.allowed_in_work_package?(:add_work_package_comments, @work_package)
|
||||
end
|
||||
|
||||
def unsaved_changes_confirmation_message
|
||||
|
||||
@@ -71,7 +71,7 @@ module WorkPackages
|
||||
API::V3::Activities::ActivityEagerLoadingWrapper.wrap(
|
||||
work_package
|
||||
.journals
|
||||
.restricted_visible
|
||||
.internal_visible
|
||||
.includes(:user, :customizable_journals, :attachable_journals, :storable_journals, :notifications)
|
||||
.reorder(version: journal_sorting)
|
||||
.with_sequence_version
|
||||
|
||||
@@ -73,23 +73,23 @@
|
||||
Primer::Beta::Octicon.new(
|
||||
:"dot-fill", # color is set via CSS as requested by UI/UX Team
|
||||
classes: "work-packages-activities-tab-journals-item-component--notification-dot-icon",
|
||||
size: (OpenProject::FeatureDecisions.comments_with_restricted_visibility_active? ? :small : :medium),
|
||||
size: (OpenProject::FeatureDecisions.internal_comments_active? ? :small : :medium),
|
||||
data: { test_selector: "op-journal-unread-notification", "op-ian-center-update-immediate": true }
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
if journal.restricted?
|
||||
if journal.internal?
|
||||
header_end_container.with_column(mr: 2) do
|
||||
render(
|
||||
Primer::Beta::Octicon.new(
|
||||
:lock,
|
||||
classes: "work-packages-activities-tab-journals-item-component--restricted-icon",
|
||||
classes: "work-packages-activities-tab-journals-item-component--internal-icon",
|
||||
size: :small,
|
||||
aria: { label: I18n.t("activities.work_packages.activity_tab.restricted_journal") },
|
||||
data: { test_selector: "op-journal-restricted-icon" },
|
||||
color: (journal.restricted? ? :attention : :default)
|
||||
aria: { label: I18n.t("activities.work_packages.activity_tab.internal_journal") },
|
||||
data: { test_selector: "op-journal-internal-icon" },
|
||||
color: (journal.internal? ? :attention : :default)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
@@ -63,24 +63,24 @@ module WorkPackages
|
||||
|
||||
def container_classes
|
||||
[].tap do |classes|
|
||||
if journal.restricted?
|
||||
classes << "work-packages-activities-tab-journals-item-component--container__restricted-comment"
|
||||
if journal.internal?
|
||||
classes << "work-packages-activities-tab-journals-item-component--container__internal-comment"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def comment_header_classes
|
||||
[].tap do |classes|
|
||||
if journal.restricted?
|
||||
classes << "work-packages-activities-tab-journals-item-component--header__restricted-comment"
|
||||
if journal.internal?
|
||||
classes << "work-packages-activities-tab-journals-item-component--header__internal-comment"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def comment_body_classes
|
||||
["work-packages-activities-tab-journals-item-component--journal-notes-body"].tap do |classes|
|
||||
if journal.restricted?
|
||||
classes << "work-packages-activities-tab-journals-item-component--journal-notes-body__restricted-comment"
|
||||
if journal.internal?
|
||||
classes << "work-packages-activities-tab-journals-item-component--journal-notes-body__internal-comment"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -112,7 +112,7 @@ module WorkPackages
|
||||
end
|
||||
|
||||
def allowed_to_quote?
|
||||
User.current.allowed_in_project?(:add_work_package_notes, journal.journable.project)
|
||||
User.current.allowed_in_project?(:add_work_package_comments, journal.journable.project)
|
||||
end
|
||||
|
||||
def copy_url_action_item(menu)
|
||||
@@ -163,10 +163,10 @@ module WorkPackages
|
||||
quote_comments_stimulus_controller("-content-param") => journal.notes,
|
||||
quote_comments_stimulus_controller("-user-id-param") => journal.user_id,
|
||||
quote_comments_stimulus_controller("-user-name-param") => journal.user.name,
|
||||
quote_comments_stimulus_controller("-is-restricted-param") => journal.restricted?,
|
||||
quote_comments_stimulus_controller("-is-internal-param") => journal.internal?,
|
||||
quote_comments_stimulus_controller("-text-wrote-param") => I18n.t(:text_wrote),
|
||||
quote_comments_stimulus_controller("-#{index_stimulus_controller}-outlet") => items_index_selector,
|
||||
quote_comments_stimulus_controller("-#{restricted_comment_stimulus_controller}-outlet") => add_comment_selector,
|
||||
quote_comments_stimulus_controller("-#{internal_comment_stimulus_controller}-outlet") => add_comment_selector,
|
||||
test_selector: "op-wp-journal-#{journal.id}-quote"
|
||||
}
|
||||
end
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
color: var(--bgColor-accent-emphasis)
|
||||
&--header-start-container
|
||||
flex-grow: 1
|
||||
&--container__restricted-comment
|
||||
&--container__internal-comment
|
||||
border-color: var(--display-brown-borderColor-emphasis)
|
||||
&--header__restricted-comment
|
||||
&--header__internal-comment
|
||||
border-color: var(--display-brown-borderColor-emphasis)
|
||||
background-color: var(--display-brown-bgColor-muted)
|
||||
&--journal-notes-body__restricted-comment
|
||||
&--journal-notes-body__internal-comment
|
||||
border-color: var(--display-brown-borderColor-emphasis)
|
||||
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ module WorkPackages
|
||||
def wrapper_uniq_by = journal.id
|
||||
|
||||
def current_user_can_react?
|
||||
User.current.allowed_in_work_package?(:add_work_package_notes, work_package)
|
||||
User.current.allowed_in_work_package?(:add_work_package_comments, work_package)
|
||||
end
|
||||
|
||||
def current_user_cannot_react? = !current_user_can_react?
|
||||
|
||||
@@ -193,7 +193,7 @@ module WorkPackages
|
||||
render(Primer::Beta::Octicon.new(
|
||||
:"dot-fill", # color is set via CSS as requested by UI/UX Team
|
||||
classes: "work-packages-activities-tab-journals-item-component-details--notification-dot-icon",
|
||||
size: (OpenProject::FeatureDecisions.comments_with_restricted_visibility_active? ? :small : :medium),
|
||||
size: (OpenProject::FeatureDecisions.internal_comments_active? ? :small : :medium),
|
||||
data: { test_selector: "op-journal-unread-notification", "op-ian-center-update-immediate": true }
|
||||
))
|
||||
end
|
||||
|
||||
@@ -53,7 +53,7 @@ module WorkPackages
|
||||
|
||||
def wrapper_data_attributes
|
||||
{
|
||||
restricted_comment_stimulus_controller("-is-restricted-value") => journal.restricted?
|
||||
internal_comment_stimulus_controller("-is-internal-value") => journal.internal?
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -88,7 +88,7 @@ module WorkPackages
|
||||
end
|
||||
|
||||
def current_user_can_react?
|
||||
User.current.allowed_in_work_package?(:add_work_package_notes, work_package)
|
||||
User.current.allowed_in_work_package?(:add_work_package_comments, work_package)
|
||||
end
|
||||
|
||||
def current_user_cannot_react? = !current_user_can_react?
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
new_form_container.with_row(
|
||||
display: form_row_display_value,
|
||||
data: { "work-packages--activities-tab--index-target": "formRow" }
|
||||
@@ -61,16 +62,16 @@
|
||||
render(WorkPackages::ActivitiesTab::Journals::NotesForm.new(f))
|
||||
end
|
||||
|
||||
form_container.with_row(flex_layout: true, justify_content: adding_restricted_comment_allowed? ? :space_between : :flex_end) do |form_container_row|
|
||||
if adding_restricted_comment_allowed?
|
||||
form_container_row.with_column(flex_layout: true, align_items: :center) do |restricted_comment_container|
|
||||
restricted_comment_container.with_column(mr: 2, test_selector: "op-work-package-journal-restricted-comment-checkbox") do
|
||||
render(WorkPackages::ActivitiesTab::Journals::RestrictedNoteForm.new(f))
|
||||
form_container.with_row(flex_layout: true, justify_content: adding_internal_comment_allowed? ? :space_between : :flex_end) do |form_container_row|
|
||||
if adding_internal_comment_allowed?
|
||||
form_container_row.with_column(flex_layout: true, align_items: :center) do |internal_comment_container|
|
||||
internal_comment_container.with_column(mr: 2, test_selector: "op-work-package-journal-internal-comment-checkbox") do
|
||||
render(WorkPackages::ActivitiesTab::Journals::InternalNoteForm.new(f))
|
||||
end
|
||||
|
||||
restricted_comment_container.with_column(
|
||||
internal_comment_container.with_column(
|
||||
display: :none,
|
||||
data: { "work-packages--activities-tab--restricted-comment-target": "learnMoreLink" },
|
||||
data: { "work-packages--activities-tab--internal-comment-target": "learnMoreLink" },
|
||||
pb: 1
|
||||
) do
|
||||
render(Primer::Beta::Link.new(href: learn_more_static_link_url, target: "_blank")) do |link|
|
||||
@@ -99,3 +100,21 @@
|
||||
end
|
||||
end
|
||||
%>
|
||||
|
||||
<% if adding_internal_comment_allowed? %>
|
||||
<%=
|
||||
render(
|
||||
Primer::OpenProject::DangerDialog.new(
|
||||
title: I18n.t("activities.work_packages.activity_tab.internal_comment_confirmation.title"),
|
||||
confirm_button_text: I18n.t("activities.work_packages.activity_tab.internal_comment_confirmation.confirm_button_text"),
|
||||
data: confirm_dialog_data_attributes,
|
||||
test_selector: "op-work-package-internal-comment-confirmation-dialog"
|
||||
)
|
||||
) do |dialog|
|
||||
dialog.with_confirmation_message do |message|
|
||||
message.with_heading(tag: :h2) { I18n.t("activities.work_packages.activity_tab.internal_comment_confirmation.title") }
|
||||
message.with_description_content(I18n.t("activities.work_packages.activity_tab.internal_comment_confirmation.description"))
|
||||
end
|
||||
end
|
||||
%>
|
||||
<% end %>
|
||||
|
||||
@@ -33,6 +33,7 @@ module WorkPackages
|
||||
include ApplicationHelper
|
||||
include OpPrimer::ComponentHelpers
|
||||
include OpTurbo::Streamable
|
||||
include WorkPackages::ActivitiesTab::StimulusControllers
|
||||
|
||||
def initialize(work_package:, journal: nil, form_hidden_initially: true)
|
||||
super
|
||||
@@ -58,15 +59,21 @@ module WorkPackages
|
||||
form_hidden_initially ? :none : :block
|
||||
end
|
||||
|
||||
def adding_restricted_comment_allowed?
|
||||
OpenProject::FeatureDecisions.comments_with_restricted_visibility_active? &&
|
||||
work_package.project.enabled_comments_with_restricted_visibility &&
|
||||
User.current.allowed_in_project?(:add_comments_with_restricted_visibility, work_package.project)
|
||||
def adding_internal_comment_allowed?
|
||||
OpenProject::FeatureDecisions.internal_comments_active? &&
|
||||
work_package.project.enabled_internal_comments &&
|
||||
User.current.allowed_in_project?(:add_internal_comments, work_package.project)
|
||||
end
|
||||
|
||||
def learn_more_static_link_url
|
||||
::OpenProject::Static::Links.url_for(:user_guides_work_package_activity)
|
||||
end
|
||||
|
||||
def confirm_dialog_data_attributes
|
||||
{
|
||||
internal_comment_stimulus_controller("-target") => "confirmationDialog"
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -27,6 +27,6 @@
|
||||
max-height: 10vh
|
||||
.ck-editor__preview
|
||||
max-height: 30vh
|
||||
&--journal-notes-body__restricted-comment
|
||||
&--journal-notes-body__internal-comment
|
||||
background-color: var(--display-brown-bgColor-muted) !important
|
||||
border-top: var(--borderWidth-thick) solid var(--display-brown-borderColor-emphasis)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
end
|
||||
|
||||
start_date.with_row(classes: "wp-datepicker-dialog-date-form--text-field-container") do
|
||||
render(Primer::Alpha::TextField.new(**text_field_options(name: :start_date, label: start_date_label), aria: { live: :polite }))
|
||||
render(Primer::Alpha::TextField.new(**text_field_options(name: :start_date, label: start_date_label)))
|
||||
end
|
||||
start_date.with_row(classes: "wp-datepicker-dialog-set-today-link") do
|
||||
render_today_link(name: :start_date)
|
||||
@@ -45,7 +45,7 @@
|
||||
end
|
||||
|
||||
due_date.with_row(classes: "wp-datepicker-dialog-date-form--text-field-container") do
|
||||
render(Primer::Alpha::TextField.new(**text_field_options(name: :due_date, label: I18n.t("attributes.due_date")), aria: { live: :polite }))
|
||||
render(Primer::Alpha::TextField.new(**text_field_options(name: :due_date, label: I18n.t("attributes.due_date"))))
|
||||
end
|
||||
due_date.with_row(classes: "wp-datepicker-dialog-set-today-link") do
|
||||
render_today_link(name: :due_date)
|
||||
@@ -54,7 +54,7 @@
|
||||
end
|
||||
|
||||
body.with_column(classes: "wp-datepicker-dialog-date-form--duration") do
|
||||
render(Primer::Alpha::TextField.new(**text_field_options(name: :duration, label: WorkPackage.human_attribute_name("duration")), aria: { live: :polite }))
|
||||
render(Primer::Alpha::TextField.new(**text_field_options(name: :duration, label: WorkPackage.human_attribute_name("duration"))))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -167,7 +167,7 @@ module WorkPackages
|
||||
|
||||
def field_value(name)
|
||||
errors = @work_package.errors.where(name)
|
||||
if (user_value = errors.map { |error| error.options[:value] }.find { !_1.nil? })
|
||||
if (user_value = errors.map { |error| error.options[:value] }.find { !it.nil? })
|
||||
user_value
|
||||
else
|
||||
@work_package.public_send(name)
|
||||
@@ -211,16 +211,19 @@ module WorkPackages
|
||||
data[:focus] = "true"
|
||||
end
|
||||
|
||||
{ data: }
|
||||
{
|
||||
data: data,
|
||||
aria: { live: :polite, atomic: true }
|
||||
}
|
||||
end
|
||||
|
||||
def single_date_field_button_link(focused_field)
|
||||
permitted_params = params.merge(date_mode: "range", focused_field:).permit!
|
||||
|
||||
if params[:action] == "new"
|
||||
new_work_package_datepicker_dialog_content_path(permitted_params)
|
||||
new_date_picker_path(permitted_params)
|
||||
else
|
||||
work_package_datepicker_dialog_content_path(permitted_params)
|
||||
work_package_date_picker_path(permitted_params)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -61,9 +61,11 @@ module WorkPackages
|
||||
|
||||
def submit_path
|
||||
if work_package.new_record?
|
||||
datepicker_dialog_content_path
|
||||
# create: get json of selected dates
|
||||
date_picker_path
|
||||
else
|
||||
work_package_datepicker_dialog_content_path(work_package)
|
||||
# update dates of work package
|
||||
work_package_date_picker_path(work_package)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -72,9 +74,9 @@ module WorkPackages
|
||||
.merge(schedule_manually:)
|
||||
.permit!
|
||||
if work_package.new_record?
|
||||
new_work_package_datepicker_dialog_content_path("new", dialog_params)
|
||||
new_date_picker_path(dialog_params)
|
||||
else
|
||||
work_package_datepicker_dialog_content_path(work_package, dialog_params)
|
||||
work_package_date_picker_path(work_package, dialog_params)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ module WorkPackages
|
||||
render(Primer::Beta::Octicon.new(
|
||||
icon: "op-enterprise-addons",
|
||||
"aria-label": I18n.t(:label_enterprise_edition),
|
||||
classes: "upsale-colored", ml: 2
|
||||
classes: "upsell-colored", ml: 2
|
||||
))
|
||||
end
|
||||
|
||||
|
||||
@@ -63,9 +63,9 @@ module EmojiReactions
|
||||
def manage_emoji_reactions?
|
||||
case model.reactable
|
||||
when WorkPackage
|
||||
user.allowed_in_work_package?(:add_work_package_notes, model.reactable)
|
||||
user.allowed_in_work_package?(:add_work_package_comments, model.reactable)
|
||||
when Journal
|
||||
user.allowed_in_work_package?(:add_work_package_notes, model.reactable.journable)
|
||||
user.allowed_in_work_package?(:add_work_package_comments, model.reactable.journable)
|
||||
else
|
||||
false
|
||||
end
|
||||
|
||||
@@ -28,47 +28,15 @@
|
||||
|
||||
module ProjectLifeCycleSteps
|
||||
class BaseContract < ::ModelContract
|
||||
validate :select_custom_fields_permission
|
||||
validate :consecutive_steps_have_increasing_dates
|
||||
attribute :start_date
|
||||
attribute :finish_date
|
||||
|
||||
def valid?(context = :saving_phases) = super
|
||||
validate :validate_edit_project_phase_permission
|
||||
|
||||
def select_custom_fields_permission
|
||||
return if user.allowed_in_project?(:edit_project_phases, model)
|
||||
def validate_edit_project_phase_permission
|
||||
return if user.allowed_in_project?(:edit_project_phases, model.project)
|
||||
|
||||
errors.add :base, :error_unauthorized
|
||||
end
|
||||
|
||||
def consecutive_steps_have_increasing_dates
|
||||
# Filter out steps with missing dates before proceeding with comparison
|
||||
filtered_steps = model.available_phases.select(&:start_date)
|
||||
|
||||
# Only proceed with comparisons if there are at least 2 valid steps
|
||||
return if filtered_steps.size < 2
|
||||
|
||||
# Compare consecutive steps in pairs
|
||||
filtered_steps.each_cons(2) do |previous_step, current_step|
|
||||
if has_invalid_dates?(previous_step, current_step)
|
||||
error = current_step.errors.add(:date_range, :non_continuous_dates)
|
||||
unless model.errors.include?(:"available_phases.date_range")
|
||||
model.errors.import(error, attribute: :"available_phases.date_range")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def start_date_for(step)
|
||||
step.start_date
|
||||
end
|
||||
|
||||
def finish_date_for(step)
|
||||
step.finish_date || step.start_date # Use the start_date as fallback for single date stages
|
||||
end
|
||||
|
||||
def has_invalid_dates?(previous_step, current_step)
|
||||
start_date_for(current_step) <= finish_date_for(previous_step)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
class WorkPackages::ActivitiesTab::CommentAttachmentsClaimsContract < ModelContract
|
||||
include ::Attachments::ValidateReplacements
|
||||
end
|
||||
@@ -162,12 +162,6 @@ module WorkPackages
|
||||
|
||||
validate :validate_duration_and_dates_are_not_derivable
|
||||
|
||||
def initialize(work_package, user, options: {})
|
||||
super
|
||||
|
||||
@can = WorkPackagePolicy.new(user)
|
||||
end
|
||||
|
||||
def assignable_statuses(include_default: false)
|
||||
# Do not allow skipping statuses without intermediately saving the work package.
|
||||
# We therefore take the original status of the work_package, while preserving all
|
||||
@@ -226,8 +220,6 @@ module WorkPackages
|
||||
|
||||
private
|
||||
|
||||
attr_reader :can
|
||||
|
||||
def validate_after_soonest_start(date_attribute)
|
||||
return if model.schedule_manually?
|
||||
return if model.children.any?
|
||||
|
||||
@@ -33,26 +33,33 @@ module WorkPackages
|
||||
def self.model = WorkPackage
|
||||
|
||||
attribute :journal_notes do
|
||||
errors.add(:journal_notes, :error_unauthorized) unless can?(:comment)
|
||||
errors.add(:journal_notes, :error_unauthorized) unless adding_notes_allowed?
|
||||
errors.add(:journal_notes, :blank) if model.journal_notes.blank?
|
||||
end
|
||||
|
||||
attribute :journal_restricted do
|
||||
if model.journal_restricted && !OpenProject::FeatureDecisions.comments_with_restricted_visibility_active?
|
||||
errors.add(:journal_restricted, :feature_disabled)
|
||||
attribute :journal_internal do
|
||||
next unless model.journal_internal
|
||||
|
||||
unless OpenProject::FeatureDecisions.internal_comments_active?
|
||||
errors.add(:journal_internal, :feature_disabled)
|
||||
end
|
||||
unless allowed_in_project?(:add_internal_comments)
|
||||
errors.add(:journal_internal, :error_unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def can?(permission)
|
||||
policy.allowed?(model, permission)
|
||||
def adding_notes_allowed?
|
||||
allowed_in_work_package?(:add_work_package_comments) || allowed_in_work_package?(:edit_work_packages)
|
||||
end
|
||||
|
||||
attr_writer :policy
|
||||
def allowed_in_work_package?(permission)
|
||||
user.allowed_in_work_package?(permission, model)
|
||||
end
|
||||
|
||||
def policy
|
||||
@policy ||= WorkPackagePolicy.new(user)
|
||||
def allowed_in_project?(permission)
|
||||
user.allowed_in_project?(permission, model.project)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -49,18 +49,18 @@ module WorkPackages
|
||||
attribute_permission :project_id, :move_work_packages
|
||||
|
||||
def can_set_parent?
|
||||
@can.allowed?(model, :manage_subtasks)
|
||||
allowed_in_project?(:manage_subtasks)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_allowed_to_edit
|
||||
with_unchanged_project_id do
|
||||
next if @can.allowed?(model, :edit) ||
|
||||
@can.allowed?(model, :assign_version) ||
|
||||
@can.allowed?(model, :change_status) ||
|
||||
@can.allowed?(model, :manage_subtasks) ||
|
||||
@can.allowed?(model, :move)
|
||||
next if allowed_in_work_package?(:edit_work_packages) ||
|
||||
allowed_in_project?(:assign_versions) ||
|
||||
allowed_in_project?(:change_work_package_status) ||
|
||||
allowed_in_project?(:manage_subtasks) ||
|
||||
allowed_in_project?(:move_work_packages)
|
||||
next if allowed_journal_addition?
|
||||
|
||||
errors.add :base, :error_unauthorized
|
||||
@@ -74,7 +74,7 @@ module WorkPackages
|
||||
end
|
||||
|
||||
def allowed_journal_addition?
|
||||
model.changes.empty? && model.journal_notes && can.allowed?(model, :comment)
|
||||
model.changes.empty? && model.journal_notes && allowed_in_work_package?(:add_work_package_comments)
|
||||
end
|
||||
|
||||
def can_move_to_milestone
|
||||
@@ -93,5 +93,13 @@ module WorkPackages
|
||||
errors.add :parent_id, :error_unauthorized
|
||||
end
|
||||
end
|
||||
|
||||
def allowed_in_project?(permission)
|
||||
user.allowed_in_project?(permission, model.project)
|
||||
end
|
||||
|
||||
def allowed_in_work_package?(permission)
|
||||
user.allowed_in_work_package?(permission, model)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -51,12 +51,6 @@ module Admin
|
||||
redirect_to action: :index
|
||||
end
|
||||
|
||||
def default_breadcrumb; end
|
||||
|
||||
def show_local_breadcrumb
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_journal(container, user, notes)
|
||||
|
||||
@@ -76,12 +76,6 @@ class Admin::BackupsController < ApplicationController
|
||||
redirect_to action: "show"
|
||||
end
|
||||
|
||||
def default_breadcrumb; end
|
||||
|
||||
def show_local_breadcrumb
|
||||
false
|
||||
end
|
||||
|
||||
def check_enabled
|
||||
render_404 unless OpenProject::Configuration.backup_enabled?
|
||||
end
|
||||
|
||||
@@ -89,12 +89,6 @@ class Admin::CustomFields::CustomFieldProjectsController < ApplicationController
|
||||
respond_to_with_turbo_streams(status: delete_service.success? ? :ok : :unprocessable_entity)
|
||||
end
|
||||
|
||||
def default_breadcrumb; end
|
||||
|
||||
def show_local_breadcrumb
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def render_project_list(url_for_action: action_name)
|
||||
|
||||
@@ -30,12 +30,6 @@ module Admin::Settings
|
||||
class AttachmentsSettingsController < ::Admin::SettingsController
|
||||
menu_item :attachments
|
||||
|
||||
def default_breadcrumb; end
|
||||
|
||||
def show_local_breadcrumb
|
||||
false
|
||||
end
|
||||
|
||||
def settings_params
|
||||
super.tap do |settings|
|
||||
settings["attachment_whitelist"] = settings["attachment_whitelist"].split(/\r?\n/)
|
||||
|
||||
@@ -103,8 +103,6 @@ module Admin
|
||||
@other_enumerations = enumeration_class.all - [@enumeration]
|
||||
end
|
||||
|
||||
def default_breadcrumb; end
|
||||
|
||||
private
|
||||
|
||||
def move_params
|
||||
|
||||
@@ -32,10 +32,6 @@ module Admin::Settings
|
||||
|
||||
before_action :validate_enabled_modules, only: :update # rubocop:disable Rails/LexicallyScopedActionFilter
|
||||
|
||||
def default_breadcrumb
|
||||
t(:label_project_new)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
|
||||
|
||||
@@ -48,10 +48,6 @@ module Admin::Settings
|
||||
before_action :find_unlink_project_custom_field_mapping, only: :unlink
|
||||
# rubocop:enable Rails/LexicallyScopedActionFilter
|
||||
|
||||
def show_local_breadcrumb
|
||||
false
|
||||
end
|
||||
|
||||
def index
|
||||
respond_to :html
|
||||
end
|
||||
|
||||
@@ -37,10 +37,6 @@ module Admin::Settings
|
||||
respond_to :html
|
||||
end
|
||||
|
||||
def show_local_breadcrumb
|
||||
false
|
||||
end
|
||||
|
||||
def settings_params
|
||||
super.tap do |settings|
|
||||
if settings["consent_required"] == "1" && params["toggle_consent_time"] == "1"
|
||||
|
||||
@@ -35,10 +35,6 @@ module Admin::Settings
|
||||
before_action :require_ee, except: :show # rubocop:disable Rails/LexicallyScopedActionFilter
|
||||
before_action :check_clamav, only: %i[update], if: -> { scan_enabled? }
|
||||
|
||||
def show_local_breadcrumb
|
||||
false
|
||||
end
|
||||
|
||||
def av_form
|
||||
selected = params.dig(:settings, :antivirus_scan_mode)&.to_sym || :disabled
|
||||
|
||||
|
||||
@@ -81,18 +81,6 @@ module Admin
|
||||
redirect_to action: :show_plugin, id: @plugin.id
|
||||
end
|
||||
|
||||
def show_local_breadcrumb
|
||||
false
|
||||
end
|
||||
|
||||
def default_breadcrumb
|
||||
if @plugin
|
||||
@plugin.name
|
||||
else
|
||||
I18n.t(:label_setting_plural)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def find_plugin
|
||||
|
||||
@@ -90,12 +90,6 @@ class AdminController < ApplicationController
|
||||
@storage_information = OpenProject::Storage.mount_information
|
||||
end
|
||||
|
||||
def default_breadcrumb; end
|
||||
|
||||
def show_local_breadcrumb
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def hidden_admin_menu_items
|
||||
|
||||
@@ -20,10 +20,6 @@ class AnnouncementsController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def show_local_breadcrumb
|
||||
false
|
||||
end
|
||||
|
||||
def announcement_params
|
||||
params.require(:announcement).permit("text", "show_until", "active")
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
@@ -189,7 +191,7 @@ class ApplicationController < ActionController::Base
|
||||
# Create CSRF issue
|
||||
def log_csrf_failure
|
||||
message = "CSRF validation error"
|
||||
message << " (No session cookie present)" if openproject_cookie_missing?
|
||||
message += " (No session cookie present)" if openproject_cookie_missing?
|
||||
|
||||
op_handle_error message, reference: :csrf_validation_failed
|
||||
end
|
||||
@@ -434,21 +436,6 @@ class ApplicationController < ActionController::Base
|
||||
api_request? ? nil : super
|
||||
end
|
||||
|
||||
def default_breadcrumb
|
||||
label = "label_#{controller_name.singularize}"
|
||||
|
||||
I18n.t(label + "_plural",
|
||||
default: label.to_sym)
|
||||
end
|
||||
|
||||
helper_method :default_breadcrumb
|
||||
|
||||
def show_local_breadcrumb
|
||||
false
|
||||
end
|
||||
|
||||
helper_method :show_local_breadcrumb
|
||||
|
||||
def admin_first_level_menu_entry
|
||||
menu_item = admin_menu_item(current_menu_item)
|
||||
menu_item.parent
|
||||
|
||||
@@ -85,12 +85,6 @@ class AttributeHelpTextsController < ApplicationController
|
||||
|
||||
protected
|
||||
|
||||
def default_breadcrumb; end
|
||||
|
||||
def show_local_breadcrumb
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def permitted_params_with_attachments
|
||||
|
||||
@@ -102,12 +102,6 @@ class ColorsController < ApplicationController
|
||||
|
||||
protected
|
||||
|
||||
def show_local_breadcrumb
|
||||
false
|
||||
end
|
||||
|
||||
def default_breadcrumb; end
|
||||
|
||||
def require_admin_unless_readonly_api_request
|
||||
require_admin unless %w[index show].include? action_name and
|
||||
api_request?
|
||||
|
||||
@@ -100,8 +100,10 @@ module OpTurbo
|
||||
.render_in(view_context)
|
||||
end
|
||||
|
||||
def close_dialog_via_turbo_stream(target)
|
||||
turbo_streams << OpTurbo::StreamComponent.new(action: :closeDialog, target:).render_in(view_context)
|
||||
def close_dialog_via_turbo_stream(target, additional: {})
|
||||
turbo_streams << OpTurbo::StreamComponent
|
||||
.new(action: :closeDialog, target:, additional: additional.to_json)
|
||||
.render_in(view_context)
|
||||
end
|
||||
|
||||
def turbo_streams
|
||||
|
||||
@@ -86,7 +86,7 @@ class CustomActionsController < ApplicationController
|
||||
return if EnterpriseToken.allows_to?(:custom_actions)
|
||||
|
||||
if request.get?
|
||||
render template: "custom_actions/upsale"
|
||||
render template: "custom_actions/upsell"
|
||||
else
|
||||
render_403
|
||||
end
|
||||
@@ -102,10 +102,4 @@ class CustomActionsController < ApplicationController
|
||||
params[:custom_action][:conditions] ||= {}
|
||||
params[:custom_action][:actions] ||= {}
|
||||
end
|
||||
|
||||
def show_local_breadcrumb
|
||||
false
|
||||
end
|
||||
|
||||
def default_breadcrumb; end
|
||||
end
|
||||
|
||||
@@ -61,12 +61,6 @@ class CustomFieldsController < ApplicationController
|
||||
|
||||
protected
|
||||
|
||||
def default_breadcrumb; end
|
||||
|
||||
def show_local_breadcrumb
|
||||
false
|
||||
end
|
||||
|
||||
def validate_enterprise_token
|
||||
if params.dig(:custom_field, :field_format) == "hierarchy" && !EnterpriseToken.allows_to?(:custom_field_hierarchies)
|
||||
render_403
|
||||
|
||||
@@ -39,7 +39,7 @@ class CustomStylesController < ApplicationController
|
||||
before_action :require_admin,
|
||||
except: UNGUARDED_ACTIONS
|
||||
before_action :require_ee_token,
|
||||
except: UNGUARDED_ACTIONS + %i[upsale]
|
||||
except: UNGUARDED_ACTIONS + %i[upsell]
|
||||
skip_before_action :check_if_login_required,
|
||||
only: UNGUARDED_ACTIONS
|
||||
no_authorization_required! *UNGUARDED_ACTIONS
|
||||
@@ -58,7 +58,7 @@ class CustomStylesController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def upsale; end
|
||||
def upsell; end
|
||||
|
||||
def create
|
||||
@custom_style = CustomStyle.create(custom_style_params)
|
||||
@@ -180,7 +180,7 @@ class CustomStylesController < ApplicationController
|
||||
|
||||
def require_ee_token
|
||||
unless EnterpriseToken.allows_to?(:define_custom_style)
|
||||
redirect_to custom_style_upsale_path
|
||||
redirect_to custom_style_upsell_path
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -98,12 +98,6 @@ class EnterprisesController < ApplicationController
|
||||
helpers.write_augur_to_gon
|
||||
end
|
||||
|
||||
def default_breadcrumb; end
|
||||
|
||||
def show_local_breadcrumb
|
||||
false
|
||||
end
|
||||
|
||||
def check_user_limit
|
||||
if OpenProject::Enterprise.user_limit_reached?
|
||||
flash.now[:warning] = I18n.t(
|
||||
|
||||
@@ -160,10 +160,6 @@ class GroupsController < ApplicationController
|
||||
Group.in_project(Project.allowed_to(current_user, :view_members)).exists?
|
||||
end
|
||||
|
||||
def show_local_breadcrumb
|
||||
false
|
||||
end
|
||||
|
||||
def respond_membership_altered(service_call)
|
||||
if service_call.success?
|
||||
flash[:notice] = I18n.t :notice_successful_update
|
||||
|
||||
@@ -61,10 +61,4 @@ class HomescreenController < ApplicationController
|
||||
redirect_to_global_menu_item(params[:jump]) && return
|
||||
end
|
||||
end
|
||||
|
||||
def default_breadcrumb; end
|
||||
|
||||
def show_local_breadcrumb
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@@ -106,10 +106,6 @@ class LdapAuthSourcesController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def show_local_breadcrumb
|
||||
false
|
||||
end
|
||||
|
||||
def block_if_password_login_disabled
|
||||
render_404 if OpenProject::Configuration.disable_password_login?
|
||||
end
|
||||
|
||||
@@ -202,14 +202,6 @@ class MyController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def default_breadcrumb
|
||||
I18n.t(:label_my_account)
|
||||
end
|
||||
|
||||
def show_local_breadcrumb
|
||||
false
|
||||
end
|
||||
|
||||
def redirect_if_password_change_not_allowed_for(user)
|
||||
unless user.change_password_allowed?
|
||||
flash[:error] = I18n.t(:notice_can_t_change_password)
|
||||
|
||||
@@ -31,7 +31,7 @@ class NotificationsController < ApplicationController
|
||||
|
||||
before_action :require_login
|
||||
before_action :filtered_query, only: :mark_all_read
|
||||
no_authorization_required! :index, :split_view, :update_counter, :mark_all_read, :date_alerts, :share_upsale
|
||||
no_authorization_required! :index, :split_view, :update_counter, :mark_all_read, :date_alerts, :share_upsell
|
||||
|
||||
before_action :check_filter, only: %i[index]
|
||||
|
||||
@@ -65,7 +65,7 @@ class NotificationsController < ApplicationController
|
||||
render_notifications_layout
|
||||
end
|
||||
|
||||
def share_upsale
|
||||
def share_upsell
|
||||
render_notifications_layout
|
||||
end
|
||||
|
||||
@@ -73,8 +73,6 @@ class NotificationsController < ApplicationController
|
||||
|
||||
def split_view_base_route = notifications_path(request.query_parameters)
|
||||
|
||||
def default_breadcrumb; end
|
||||
|
||||
def render_notifications_layout
|
||||
# Frontend will handle rendering
|
||||
# but we will need to render with notification specific layout
|
||||
@@ -100,7 +98,7 @@ class NotificationsController < ApplicationController
|
||||
return if EnterpriseToken.allows_to?(:date_alerts)
|
||||
|
||||
if params[:filter] == "reason" && params[:name] == "dateAlert"
|
||||
redirect_to notifications_date_alert_upsale_path
|
||||
redirect_to notifications_date_alert_upsell_path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -97,14 +97,6 @@ module OAuth
|
||||
redirect_to action: :index
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def default_breadcrumb; end
|
||||
|
||||
def show_local_breadcrumb
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def prevent_builtin_edits
|
||||
|
||||
@@ -84,6 +84,4 @@ class OmniAuthLoginController < ApplicationController
|
||||
def omniauth_back_url
|
||||
request.env["omniauth.origin"].presence || params[:RelayState]
|
||||
end
|
||||
|
||||
def default_breadcrumb; end
|
||||
end
|
||||
|
||||
@@ -155,8 +155,4 @@ class PlaceholderUsersController < ApplicationController
|
||||
render_403 message: I18n.t("placeholder_users.right_to_manage_members_missing")
|
||||
end
|
||||
end
|
||||
|
||||
def show_local_breadcrumb
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user