diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 884248e7d..264e79800 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,8 +7,8 @@ on: pull_request: env: - RUBY_VERSION: 3.0.6 - NODE_VERSION: 16.9.1 + RUBY_VERSION: 3.1.1 + NODE_VERSION: 18.17.1 jobs: lint-report: diff --git a/.github/workflows/precompile.yml b/.github/workflows/precompile.yml deleted file mode 100644 index edae4d578..000000000 --- a/.github/workflows/precompile.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: "[CI] Precompile" - -on: - push: - branches: - - main - pull_request: - -env: - RUBY_VERSION: 3.0.6 - NODE_VERSION: 16.9.1 - -jobs: - lint-report: - runs-on: ubuntu-latest - - services: - postgres: - image: postgres:11 - ports: ["5432:5432"] - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - env: - POSTGRES_PASSWORD: postgres - env: - DATABASE_USERNAME: postgres - DATABASE_PASSWORD: postgres - DATABASE_HOST: localhost - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ env.RUBY_VERSION }} - bundler-cache: true - - - uses: actions/setup-node@master - with: - node-version: ${{ env.NODE_VERSION }} - - - name: Setup Test App - run: bundle exec rake test_app - - - name: Precompile - run: | - cd spec/decidim_dummy_app - bundle exec rails assets:precompile - env: - RAILS_ENV: production - SECRET_KEY_BASE: just-testing \ No newline at end of file diff --git a/.github/workflows/tests-legacy.yml b/.github/workflows/tests-legacy.yml deleted file mode 100644 index 3ce5c2d89..000000000 --- a/.github/workflows/tests-legacy.yml +++ /dev/null @@ -1,79 +0,0 @@ -name: "[CI] Tests 0.26" - -on: - push: - branches: - - main - pull_request: - -env: - CI: 1 - SIMPLECOV: 1 - NODE_VERSION: 16.9.1 - RUBY_VERSION: 2.7.7 - BUNDLE_GEMFILE: Gemfile.legacy - -jobs: - tests-legacy: - runs-on: ubuntu-latest - strategy: - matrix: - include: - - rspec: awesome_summary_spec.rb - features: disabled - - rspec: " --exclude-pattern 'spec/system/**/*_spec.rb'" - features: enabled - - rspec: system/admin - features: enabled - - rspec: system/*_spec.rb - features: enabled - - rspec: system/awesome_map - features: enabled - fail-fast: false - - services: - postgres: - image: postgres:11 - ports: ["5432:5432"] - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - env: - POSTGRES_PASSWORD: postgres - env: - DATABASE_USERNAME: postgres - DATABASE_PASSWORD: postgres - DATABASE_HOST: localhost - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ env.RUBY_VERSION }} - bundler-cache: true - - - uses: actions/setup-node@master - with: - node-version: ${{ env.NODE_VERSION }} - - - name: Setup Test App - run: bundle exec rake test_app - - - name: General RSpec with config vars ${{ matrix.features }} - run: bundle exec rspec spec/${{ matrix.rspec }} - env: - FEATURES: ${{ matrix.features }} - - - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v3 - - - uses: actions/upload-artifact@v2-preview - if: always() - with: - name: screenshots - path: ./spec/decidim_dummy_app/tmp/screenshots diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1f72f5e93..84495b812 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,4 +1,4 @@ -name: "[CI] Tests 0.27" +name: "[CI] Tests 0.28" on: push: @@ -9,27 +9,24 @@ on: env: CI: 1 SIMPLECOV: 1 - NODE_VERSION: 16.9.1 - RUBY_VERSION: 3.0.6 + NODE_VERSION: 18.17.1 + RUBY_VERSION: 3.1.1 BUNDLE_GEMFILE: Gemfile + DISPLAY: ":99" + PARALLEL_TEST_PROCESSORS: 2 + SHAKAPACKER_RUNTIME_COMPILE: "false" + NODE_ENV: "test" + RAILS_ENV: "test" + RUBYOPT: '-W:no-deprecated' + DATABASE_USERNAME: postgres + DATABASE_PASSWORD: postgres + DATABASE_HOST: localhost + POSTGRES_PASSWORD: postgres jobs: - tests-latest: + build: + name: Build & Precompile runs-on: ubuntu-latest - strategy: - matrix: - include: - - rspec: awesome_summary_spec.rb - features: disabled - - rspec: " --exclude-pattern 'spec/system/**/*_spec.rb'" - features: enabled - - rspec: system/admin - features: enabled - - rspec: system/*_spec.rb - features: enabled - - rspec: system/awesome_map - features: enabled - fail-fast: false services: postgres: @@ -42,10 +39,6 @@ jobs: --health-retries 5 env: POSTGRES_PASSWORD: postgres - env: - DATABASE_USERNAME: postgres - DATABASE_PASSWORD: postgres - DATABASE_HOST: localhost steps: - uses: actions/checkout@v4 @@ -75,18 +68,104 @@ jobs: with: node-version: ${{ env.NODE_VERSION }} - - name: Setup Test App - run: bundle exec rake test_app + - uses: actions/cache@v3 + id: app-cache + with: + path: ./spec/decidim_dummy_app/ + key: app-${{ github.sha }} + restore-keys: app-${{ github.sha }} + + - run: bundle exec rake test_app + name: Create test app + shell: "bash" + + - run: mkdir -p ./spec/decidim_dummy_app/tmp/screenshots + name: Create the screenshots folder + shell: "bash" + + - run: bundle exec rails assets:precompile + name: Precompile assets + working-directory: ./spec/decidim_dummy_app/ + shell: "bash" + env: + BUNDLE_GEMFILE: ../../Gemfile + + - run: tar -zcf /tmp/testapp-env.tar.gz ./spec/decidim_dummy_app + + - uses: actions/upload-artifact@v3 + with: + name: workspace + path: /tmp/testapp-env.tar.gz + + tests-latest: + name: Tests latest + runs-on: ubuntu-latest + needs: build + + strategy: + matrix: + include: + - rspec: spec/awesome_summary_spec.rb + features: disabled + - rspec: spec\/(?!system) + features: enabled + - rspec: spec/system/admin + features: enabled + - rspec: spec/system/public + features: enabled + - rspec: spec/system/awesome_map + features: enabled + fail-fast: false + + services: + postgres: + image: postgres:11 + ports: ["5432:5432"] + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + env: + POSTGRES_PASSWORD: postgres + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ env.RUBY_VERSION }} + bundler-cache: true + + - uses: actions/download-artifact@v3 + with: + name: workspace + path: /tmp + + - run: tar -zxf /tmp/testapp-env.tar.gz + name: Restore application + + - run: bundle exec rake parallel:create parallel:load_schema + name: Parallel tests + shell: "bash" + working-directory: ./spec/decidim_dummy_app/ + env: + BUNDLE_GEMFILE: ../../Gemfile - name: General RSpec with config vars ${{ matrix.features }} - run: bundle exec rspec spec/${{ matrix.rspec }} + run: | + sudo Xvfb -ac $DISPLAY -screen 0 1920x1084x24 > /dev/null 2>&1 & # optional + ln -s spec/decidim_dummy_app spec/decidim_dummy_app_last + bundle exec rake parallel:spec['${{ matrix.rspec }}'] env: FEATURES: ${{ matrix.features }} - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v3 - - uses: actions/upload-artifact@v2-preview + - uses: actions/upload-artifact@v3 if: always() with: name: screenshots diff --git a/.gitignore b/.gitignore index f651acad8..bf8715337 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ node_modules/ npm_debug.log .ruby-version +.rubocop-http* \ No newline at end of file diff --git a/.gitpod.dockerfile b/.gitpod.dockerfile index 59df600d9..e5b1c718f 100644 --- a/.gitpod.dockerfile +++ b/.gitpod.dockerfile @@ -5,4 +5,4 @@ RUN sudo apt-get update && sudo apt-get install -y redis-server apt-transport-h USER gitpod SHELL ["/bin/bash", "-c"] -RUN cd && /home/gitpod/.rvm/bin/rvm install "ruby-3.0.5" +RUN cd && /home/gitpod/.rvm/bin/rvm install "ruby-3.1.1" diff --git a/.gitpod.yml b/.gitpod.yml index 7dc46bcda..a48d71e73 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -8,7 +8,7 @@ tasks: command: bundle exec rspec #- name: webpacker # before: rvm --default use "ruby-3.0.4" - # command: bin/webpack-dev-server + # command: bin/shakapacker-dev-server vscode: extensions: - dbaeumer.vscode-eslint diff --git a/.rubocop-https---raw-githubusercontent-com-decidim-decidim-release-0-28-stable--rubocop-yml b/.rubocop-https---raw-githubusercontent-com-decidim-decidim-release-0-28-stable--rubocop-yml deleted file mode 100644 index 13bc55c4a..000000000 --- a/.rubocop-https---raw-githubusercontent-com-decidim-decidim-release-0-28-stable--rubocop-yml +++ /dev/null @@ -1,94 +0,0 @@ -inherit_gem: - decidim-dev: rubocop-decidim.yml - -inherit_mode: - merge: - - Exclude - -AllCops: - Exclude: - - "decidim-initiatives/lib/gem_overrides/origami/date.rb" - -Naming/FileName: - Exclude: - - "decidim-dev/lib/decidim-dev.rb" - -Metrics/CyclomaticComplexity: - Exclude: - - "decidim-admin/app/queries/decidim/admin/newsletter_recipients.rb" - -Metrics/ParameterLists: - Exclude: - - "decidim-core/lib/decidim/filter_form_builder.rb" - -Metrics/PerceivedComplexity: - Exclude: - - "decidim-admin/app/queries/decidim/admin/newsletter_recipients.rb" - -RSpec/DescribeClass: - Exclude: - - decidim-core/spec/lib/global_engines_spec.rb - -RSpec/EmptyExampleGroup: - Exclude: - - decidim-core/spec/lib/participatory_space_manifest_spec.rb - -RSpec/MultipleMemoizedHelpers: - Exclude: - - decidim-assemblies/spec/forms/assembly_form_spec.rb - -Rails/Output: - Exclude: - - lib/decidim/git_backport_manager.rb - - lib/decidim/github_manager/poster.rb - - decidim-core/lib/decidim/core.rb - - decidim-core/lib/decidim/component_manifest.rb - - decidim-core/lib/decidim/participatory_space_manifest.rb - -Rails/Exit: - Exclude: - - lib/decidim/git_backport_manager.rb - -RSpec/NoExpectationExample: - Exclude: - - decidim-admin/spec/system/participatory_space_private_user_spec.rb - - decidim-comments/spec/services/decidim/comments/comment_creation_spec.rb - - decidim-conferences/spec/cells/decidim/conferences/conference_speaker_cell_spec.rb - - decidim-core/spec/cells/decidim/date_range_cell_spec.rb - - decidim-core/spec/commands/decidim/unsubscribe_settings_spec.rb - - decidim-core/spec/controllers/registrations_controller_spec.rb - - decidim-core/spec/lib/importers/import_manifest_spec.rb - - decidim-core/spec/lib/map/geocoding_spec.rb - - decidim-core/spec/lib/participatory_space_manifest_spec.rb - - decidim-core/spec/services/decidim/events_manager_spec.rb - - decidim-core/spec/services/decidim/settings_change_spec.rb - - decidim-core/spec/services/decidim/zip_stream/zip_stream_writer_spec.rb - - decidim-core/spec/tasks/decidim_tasks_right_to_be_forgotten_spec.rb - - decidim-elections/spec/lib/tasks/decidim_election_generate_identification_keys_spec.rb - - decidim-elections/spec/lib/tasks/decidim_election_scheduled_tasks_spec.rb - - decidim-elections/spec/shared/vote_examples.rb - - decidim-elections/spec/system/key_ceremony_spec.rb - - decidim-elections/spec/system/vote_online_inside_a_voting_spec.rb - - decidim-initiatives/spec/system/admin/update_initiative_spec.rb - - decidim-initiatives/spec/system/initiative_signing_spec.rb - - decidim-meetings/spec/commands/admin/export_meeting_registrations_spec.rb - - decidim-meetings/spec/system/explore_meeting_directory_spec.rb - - decidim-meetings/spec/system/explore_meetings_spec.rb - - decidim-proposals/spec/lib/decidim/proposals/markdown_to_proposals_spec.rb - - decidim-proposals/spec/shared/import_proposals_examples.rb - - decidim-proposals/spec/shared/proposals_wizards_examples.rb - - decidim-proposals/spec/system/admin/admin_manages_participatory_texts_spec.rb - - decidim-proposals/spec/system/participatory_texts_spec.rb - - decidim-participatory_processes/spec/system/participatory_processes_spec.rb - -# fix these rules later -Capybara/SpecificFinders: - Enabled: false - -RSpec/IndexedLet: - Enabled: false - -Rails/HelperInstanceVariable: - Enabled: false - -# EOF fix these rules later diff --git a/.rubocop.yml b/.rubocop.yml index 870c0b9ff..7f5e26818 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,3 +1,6 @@ inherit_from: - - .rubocop_ruby.yml - - .rubocop_rails.yml + - https://raw.githubusercontent.com/decidim/decidim/release/0.28-stable/.rubocop.yml + +AllCops: + Exclude: + - "spec/decidim_dummy_app*/**/*" diff --git a/.simplecov b/.simplecov index 23809933a..1aa1259d4 100644 --- a/.simplecov +++ b/.simplecov @@ -6,6 +6,7 @@ if ENV["SIMPLECOV"] add_filter "/config/" add_filter "/db/" add_filter "lib/decidim/decidim_awesome/version.rb" + add_filter "lib/decidim/decidim_awesome/test" add_filter "/spec" end diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e5c29977..eea1b9597 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,30 @@ CHANGELOG ========= +v0.11.1 +------ + +Compatibility: + - Decidim v0.28.x + +Features: + - Added Private Custom Fields feature + - Added GraphQL types for weighted voting in the API + - Added GraphQL types for custom fields in the API + - Adds parsed information about custom fields in the Proposals export + - Adds parsed information bout private custom fields when admins exports private data + - Adds a maintenance menu with tools to remove old private data + +v0.11 +------ + +Compatibility: + - Decidim v0.28.x + +Features: + - Redesign to version 0.28 + - Removed markdown editor + v0.10.2 ------ diff --git a/Gemfile b/Gemfile index d2b2f13ab..eed36eb35 100644 --- a/Gemfile +++ b/Gemfile @@ -4,32 +4,33 @@ source "https://rubygems.org" ruby RUBY_VERSION -DECIDIM_VERSION = "0.27.6" +DECIDIM_VERSION = "0.28.2" gem "decidim", DECIDIM_VERSION +# this causes failures if not enabled (check if still necessary in the future) gem "decidim-decidim_awesome", path: "." +gem "decidim-templates", DECIDIM_VERSION gem "bootsnap", "~> 1.4" -gem "puma", ">= 5.5.1" - -gem "faker", "~> 2.14" +gem "puma", ">= 6.3.1" group :development, :test do gem "byebug", "~> 11.0", platform: :mri gem "decidim-dev", DECIDIM_VERSION + + gem "brakeman", "~> 5.4" + gem "net-imap", "~> 0.2.3" + gem "net-pop", "~> 0.1.1" + gem "net-smtp", "~> 0.3.1" + gem "parallel_tests", "~> 4.2" end group :development do - gem "letter_opener_web", "~> 1.3" + gem "letter_opener_web", "~> 2.0" gem "listen", "~> 3.1" - gem "rubocop-faker" gem "spring", "~> 2.0" - gem "spring-watcher-listen", "~> 2.0.0" - gem "web-console" -end - -group :test do - gem "codecov", require: false + gem "spring-watcher-listen", "~> 2.0" + gem "web-console", "~> 4.2" end diff --git a/Gemfile.legacy b/Gemfile.legacy deleted file mode 100644 index c276279d9..000000000 --- a/Gemfile.legacy +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -source "https://rubygems.org" - -ruby RUBY_VERSION -DECIDIM_VERSION = "0.26.8" - -gem "decidim", DECIDIM_VERSION -gem "decidim-decidim_awesome", path: "." - -gem "bootsnap", "~> 1.4" - -gem "puma", ">= 5.5.1" -gem "uglifier", "~> 4.1" - -gem "faker", "~> 2.14" - -group :development, :test do - gem "byebug", "~> 11.0", platform: :mri - - gem "decidim-dev", DECIDIM_VERSION -end - -group :development do - gem "letter_opener_web", "~> 1.3" - gem "listen", "~> 3.1" - gem "rubocop-faker" - gem "spring", "~> 2.0" - gem "spring-watcher-listen", "~> 2.0.0" - gem "web-console", "~> 3.5" -end - -group :test do - gem "codecov", require: false -end diff --git a/Gemfile.legacy.lock b/Gemfile.legacy.lock deleted file mode 100644 index b0620d4c4..000000000 --- a/Gemfile.legacy.lock +++ /dev/null @@ -1,811 +0,0 @@ -PATH - remote: . - specs: - decidim-decidim_awesome (0.10.2) - decidim-admin (>= 0.26.0, < 0.28) - decidim-core (>= 0.26.0, < 0.28) - deface (>= 1.5) - sassc (~> 2.3) - -GEM - remote: https://rubygems.org/ - specs: - actioncable (6.0.6.1) - actionpack (= 6.0.6.1) - nio4r (~> 2.0) - websocket-driver (>= 0.6.1) - actionmailbox (6.0.6.1) - actionpack (= 6.0.6.1) - activejob (= 6.0.6.1) - activerecord (= 6.0.6.1) - activestorage (= 6.0.6.1) - activesupport (= 6.0.6.1) - mail (>= 2.7.1) - actionmailer (6.0.6.1) - actionpack (= 6.0.6.1) - actionview (= 6.0.6.1) - activejob (= 6.0.6.1) - mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 2.0) - actionpack (6.0.6.1) - actionview (= 6.0.6.1) - activesupport (= 6.0.6.1) - rack (~> 2.0, >= 2.0.8) - rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.0.6.1) - actionpack (= 6.0.6.1) - activerecord (= 6.0.6.1) - activestorage (= 6.0.6.1) - activesupport (= 6.0.6.1) - nokogiri (>= 1.8.5) - actionview (6.0.6.1) - activesupport (= 6.0.6.1) - builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.1, >= 1.2.0) - active_link_to (1.0.5) - actionpack - addressable - activejob (6.0.6.1) - activesupport (= 6.0.6.1) - globalid (>= 0.3.6) - activemodel (6.0.6.1) - activesupport (= 6.0.6.1) - activerecord (6.0.6.1) - activemodel (= 6.0.6.1) - activesupport (= 6.0.6.1) - activestorage (6.0.6.1) - actionpack (= 6.0.6.1) - activejob (= 6.0.6.1) - activerecord (= 6.0.6.1) - marcel (~> 1.0) - activesupport (6.0.6.1) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) - acts_as_list (0.9.19) - activerecord (>= 3.0) - addressable (2.8.5) - public_suffix (>= 2.0.2, < 6.0) - ast (2.4.2) - axe-core-api (4.8.0) - dumb_delegator - virtus - axe-core-rspec (4.1.0) - axe-core-api - dumb_delegator - virtus - axiom-types (0.1.1) - descendants_tracker (~> 0.0.4) - ice_nine (~> 0.11.0) - thread_safe (~> 0.3, >= 0.3.1) - base64 (0.2.0) - batch-loader (1.5.0) - bcrypt (3.1.19) - better_html (1.0.16) - actionview (>= 4.0) - activesupport (>= 4.0) - ast (~> 2.0) - erubi (~> 1.4) - html_tokenizer (~> 0.0.6) - parser (>= 2.4) - smart_properties - bindex (0.8.1) - bootsnap (1.17.0) - msgpack (~> 1.2) - browser (2.7.1) - builder (3.2.4) - byebug (11.1.3) - capybara (3.39.2) - addressable - matrix - mini_mime (>= 0.1.3) - nokogiri (~> 1.8) - rack (>= 1.6.0) - rack-test (>= 0.6.3) - regexp_parser (>= 1.5, < 3.0) - xpath (~> 3.2) - carrierwave (2.2.4) - activemodel (>= 5.0.0) - activesupport (>= 5.0.0) - addressable (~> 2.6) - image_processing (~> 1.1) - marcel (~> 1.0.0) - mini_mime (>= 0.1.3) - ssrf_filter (~> 1.0) - cells (4.1.7) - declarative-builder (< 0.2.0) - declarative-option (< 0.2.0) - tilt (>= 1.4, < 3) - uber (< 0.2.0) - cells-erb (0.1.0) - cells (~> 4.0) - erbse (>= 0.1.1) - cells-rails (0.1.5) - actionpack (>= 5.0) - cells (>= 4.1.6, < 5.0.0) - charlock_holmes (0.7.7) - chef-utils (18.3.0) - concurrent-ruby - childprocess (3.0.0) - codecov (0.6.0) - simplecov (>= 0.15, < 0.22) - coercible (1.0.0) - descendants_tracker (~> 0.0.1) - coffee-rails (5.0.0) - coffee-script (>= 2.2.0) - railties (>= 5.2.0) - coffee-script (2.4.1) - coffee-script-source - execjs - coffee-script-source (1.12.2) - concurrent-ruby (1.2.2) - crack (0.4.5) - rexml - crass (1.0.6) - css_parser (1.16.0) - addressable - date (3.3.4) - date_validator (0.9.0) - activemodel - activesupport - db-query-matchers (0.10.0) - activesupport (>= 4.0, < 7) - rspec (~> 3.0) - decidim (0.26.8) - decidim-accountability (= 0.26.8) - decidim-admin (= 0.26.8) - decidim-api (= 0.26.8) - decidim-assemblies (= 0.26.8) - decidim-blogs (= 0.26.8) - decidim-budgets (= 0.26.8) - decidim-comments (= 0.26.8) - decidim-core (= 0.26.8) - decidim-debates (= 0.26.8) - decidim-forms (= 0.26.8) - decidim-generators (= 0.26.8) - decidim-meetings (= 0.26.8) - decidim-pages (= 0.26.8) - decidim-participatory_processes (= 0.26.8) - decidim-proposals (= 0.26.8) - decidim-sortitions (= 0.26.8) - decidim-surveys (= 0.26.8) - decidim-system (= 0.26.8) - decidim-templates (= 0.26.8) - decidim-verifications (= 0.26.8) - decidim-accountability (0.26.8) - decidim-comments (= 0.26.8) - decidim-core (= 0.26.8) - decidim-admin (0.26.8) - active_link_to (~> 1.0) - decidim-core (= 0.26.8) - devise (~> 4.7) - devise-i18n (~> 1.2) - devise_invitable (~> 2.0) - decidim-api (0.26.8) - graphql (~> 1.12, < 1.13) - rack-cors (~> 1.0) - redcarpet (~> 3.5, >= 3.5.1) - decidim-assemblies (0.26.8) - decidim-core (= 0.26.8) - decidim-blogs (0.26.8) - decidim-admin (= 0.26.8) - decidim-comments (= 0.26.8) - decidim-core (= 0.26.8) - decidim-budgets (0.26.8) - decidim-comments (= 0.26.8) - decidim-core (= 0.26.8) - decidim-comments (0.26.8) - decidim-core (= 0.26.8) - redcarpet (~> 3.5, >= 3.5.1) - decidim-core (0.26.8) - active_link_to (~> 1.0) - acts_as_list (~> 0.9) - batch-loader (~> 1.2) - browser (~> 2.7) - carrierwave (~> 2.2.1) - cells-erb (~> 0.1.0) - cells-rails (~> 0.1.3) - charlock_holmes (~> 0.7) - date_validator (~> 0.9.0) - decidim-api (= 0.26.8) - devise (~> 4.7) - devise-i18n (~> 1.2) - diffy (~> 3.3) - doorkeeper (~> 5.1) - doorkeeper-i18n (~> 4.0) - file_validators (~> 2.1) - fog-local (~> 0.6) - foundation_rails_helper - geocoder (~> 1.7.5) - hashdiff (>= 0.4.0, < 2.0.0) - invisible_captcha (~> 0.12) - kaminari (~> 1.2, >= 1.2.1) - loofah (~> 2.3.1) - mime-types (>= 1.16, < 4.0) - mini_magick (~> 4.9) - mustache (~> 1.1.0) - omniauth (~> 2.0) - omniauth-facebook (~> 5.0) - omniauth-google-oauth2 (~> 1.0) - omniauth-rails_csrf_protection (~> 1.0) - omniauth-twitter (~> 1.4) - paper_trail (~> 12.0) - pg (~> 1.1.4, < 2) - pg_search (~> 2.2) - premailer-rails (~> 1.10) - rack (~> 2.2, >= 2.2.3) - rack-attack (~> 6.0) - rails (~> 6.0.4) - rails-i18n (~> 6.0) - ransack (~> 2.4.1) - rectify (~> 0.13.0) - redis (~> 4.1) - request_store (~> 1.5.0) - rubyXL (~> 3.4) - rubyzip (~> 2.0) - searchlight (~> 4.1) - seven_zip_ruby (~> 1.3) - social-share-button (~> 1.2, >= 1.2.1) - valid_email2 (~> 2.1) - webpacker (= 6.0.0.rc.5) - wisper (~> 2.0) - decidim-debates (0.26.8) - decidim-comments (= 0.26.8) - decidim-core (= 0.26.8) - decidim-dev (0.26.8) - axe-core-rspec (~> 4.1.0) - byebug (~> 11.0) - capybara (~> 3.24) - db-query-matchers (~> 0.10.0) - decidim (= 0.26.8) - erb_lint (~> 0.0.35) - factory_bot_rails (~> 4.8) - i18n-tasks (~> 0.9.18) - mdl (~> 0.5) - nokogiri (~> 1.12) - puma (~> 5.0) - rails-controller-testing (~> 1.0) - rspec-cells (~> 0.3.4) - rspec-html-matchers (~> 0.9.1) - rspec-rails (~> 4.0) - rspec-retry (~> 0.6.2) - rspec_junit_formatter (~> 0.3.0) - rubocop (~> 0.92.0) - rubocop-rails (~> 2.8) - rubocop-rspec (= 1.43.2) - selenium-webdriver (~> 3.142) - simplecov (~> 0.19.0) - simplecov-cobertura (~> 1.3.1) - system_test_html_screenshots (~> 0.2) - w3c_rspec_validators (~> 0.3.0) - webmock (~> 3.6) - wisper-rspec (~> 1.0) - decidim-forms (0.26.8) - decidim-core (= 0.26.8) - wicked_pdf (~> 2.1) - wkhtmltopdf-binary (~> 0.12) - decidim-generators (0.26.8) - decidim-core (= 0.26.8) - decidim-meetings (0.26.8) - decidim-core (= 0.26.8) - decidim-forms (= 0.26.8) - icalendar (~> 2.5) - decidim-pages (0.26.8) - decidim-core (= 0.26.8) - decidim-participatory_processes (0.26.8) - decidim-core (= 0.26.8) - decidim-proposals (0.26.8) - decidim-comments (= 0.26.8) - decidim-core (= 0.26.8) - doc2text (~> 0.4.4) - redcarpet (~> 3.5, >= 3.5.1) - decidim-sortitions (0.26.8) - decidim-admin (= 0.26.8) - decidim-comments (= 0.26.8) - decidim-core (= 0.26.8) - decidim-proposals (= 0.26.8) - decidim-surveys (0.26.8) - decidim-core (= 0.26.8) - decidim-forms (= 0.26.8) - decidim-templates (= 0.26.8) - decidim-system (0.26.8) - active_link_to (~> 1.0) - decidim-core (= 0.26.8) - devise (~> 4.7) - devise-i18n (~> 1.2) - devise_invitable (~> 2.0) - decidim-templates (0.26.8) - decidim-core (= 0.26.8) - decidim-forms (= 0.26.8) - decidim-verifications (0.26.8) - decidim-core (= 0.26.8) - declarative-builder (0.1.0) - declarative-option (< 0.2.0) - declarative-option (0.1.0) - deface (1.9.0) - actionview (>= 5.2) - nokogiri (>= 1.6) - polyglot - railties (>= 5.2) - rainbow (>= 2.1.0) - descendants_tracker (0.0.4) - thread_safe (~> 0.3, >= 0.3.1) - devise (4.9.3) - bcrypt (~> 3.0) - orm_adapter (~> 0.1) - railties (>= 4.1.0) - responders - warden (~> 1.2.3) - devise-i18n (1.12.0) - devise (>= 4.9.0) - devise_invitable (2.0.9) - actionmailer (>= 5.0) - devise (>= 4.6) - diff-lcs (1.5.0) - diffy (3.4.2) - doc2text (0.4.6) - nokogiri (>= 1.13.2, < 1.15.0) - rubyzip (~> 2.3.0) - docile (1.4.0) - doorkeeper (5.6.6) - railties (>= 5) - doorkeeper-i18n (4.0.1) - dumb_delegator (1.0.0) - equalizer (0.0.11) - erb_lint (0.0.37) - activesupport - better_html (~> 1.0.7) - html_tokenizer - parser (>= 2.7.1.4) - rainbow - rubocop - smart_properties - erbse (0.1.4) - temple - erubi (1.12.0) - excon (0.104.0) - execjs (2.9.1) - factory_bot (4.11.1) - activesupport (>= 3.0.0) - factory_bot_rails (4.11.1) - factory_bot (~> 4.11.1) - railties (>= 3.0.0) - faker (2.23.0) - i18n (>= 1.8.11, < 2) - faraday (2.7.11) - base64 - faraday-net_http (>= 2.0, < 3.1) - ruby2_keywords (>= 0.0.4) - faraday-net_http (3.0.2) - ffi (1.16.3) - file_validators (2.3.0) - activemodel (>= 3.2) - mime-types (>= 1.0) - fog-core (2.3.0) - builder - excon (~> 0.71) - formatador (>= 0.2, < 2.0) - mime-types - fog-local (0.8.0) - fog-core (>= 1.27, < 3.0) - formatador (1.1.0) - foundation_rails_helper (4.0.1) - actionpack (>= 4.1, < 7.1) - activemodel (>= 4.1, < 7.1) - activesupport (>= 4.1, < 7.1) - railties (>= 4.1, < 7.1) - geocoder (1.7.5) - globalid (1.1.0) - activesupport (>= 5.0) - graphql (1.12.24) - hashdiff (1.0.1) - hashie (5.0.0) - highline (2.1.0) - html_tokenizer (0.0.7) - htmlentities (4.3.4) - i18n (1.14.1) - concurrent-ruby (~> 1.0) - i18n-tasks (0.9.37) - activesupport (>= 4.0.2) - ast (>= 2.1.0) - erubi - highline (>= 2.0.0) - i18n - parser (>= 2.2.3.0) - rails-i18n - rainbow (>= 2.2.2, < 4.0) - terminal-table (>= 1.5.1) - icalendar (2.10.0) - ice_cube (~> 0.16) - ice_cube (0.16.4) - ice_nine (0.11.2) - image_processing (1.12.2) - mini_magick (>= 4.9.5, < 5) - ruby-vips (>= 2.0.17, < 3) - invisible_captcha (0.13.0) - rails (>= 3.2.0) - json (2.6.3) - jwt (2.7.1) - kaminari (1.2.2) - activesupport (>= 4.1.0) - kaminari-actionview (= 1.2.2) - kaminari-activerecord (= 1.2.2) - kaminari-core (= 1.2.2) - kaminari-actionview (1.2.2) - actionview - kaminari-core (= 1.2.2) - kaminari-activerecord (1.2.2) - activerecord - kaminari-core (= 1.2.2) - kaminari-core (1.2.2) - kramdown (2.4.0) - rexml - kramdown-parser-gfm (1.1.0) - kramdown (~> 2.0) - launchy (2.5.2) - addressable (~> 2.8) - letter_opener (1.8.1) - launchy (>= 2.2, < 3) - letter_opener_web (1.4.1) - actionmailer (>= 3.2) - letter_opener (~> 1.0) - railties (>= 3.2) - listen (3.8.0) - rb-fsevent (~> 0.10, >= 0.10.3) - rb-inotify (~> 0.9, >= 0.9.10) - loofah (2.3.1) - crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.8.1) - mini_mime (>= 0.1.1) - net-imap - net-pop - net-smtp - marcel (1.0.2) - matrix (0.4.2) - mdl (0.13.0) - kramdown (~> 2.3) - kramdown-parser-gfm (~> 1.1) - mixlib-cli (~> 2.1, >= 2.1.1) - mixlib-config (>= 2.2.1, < 4) - mixlib-shellout - method_source (1.0.0) - mime-types (3.5.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2023.1003) - mini_magick (4.12.0) - mini_mime (1.1.5) - minitest (5.20.0) - mixlib-cli (2.1.8) - mixlib-config (3.0.27) - tomlrb - mixlib-shellout (3.2.7) - chef-utils - msgpack (1.7.2) - multi_xml (0.6.0) - mustache (1.1.1) - net-imap (0.4.5) - date - net-protocol - net-pop (0.1.2) - net-protocol - net-protocol (0.2.2) - timeout - net-smtp (0.4.0) - net-protocol - nio4r (2.5.9) - nokogiri (1.14.5-x86_64-linux) - racc (~> 1.4) - oauth (1.1.0) - oauth-tty (~> 1.0, >= 1.0.1) - snaky_hash (~> 2.0) - version_gem (~> 1.1) - oauth-tty (1.0.5) - version_gem (~> 1.1, >= 1.1.1) - oauth2 (2.0.9) - faraday (>= 0.17.3, < 3.0) - jwt (>= 1.0, < 3.0) - multi_xml (~> 0.5) - rack (>= 1.2, < 4) - snaky_hash (~> 2.0) - version_gem (~> 1.1) - omniauth (2.1.1) - hashie (>= 3.4.6) - rack (>= 2.2.3) - rack-protection - omniauth-facebook (5.0.0) - omniauth-oauth2 (~> 1.2) - omniauth-google-oauth2 (1.1.1) - jwt (>= 2.0) - oauth2 (~> 2.0.6) - omniauth (~> 2.0) - omniauth-oauth2 (~> 1.8.0) - omniauth-oauth (1.2.0) - oauth - omniauth (>= 1.0, < 3) - omniauth-oauth2 (1.8.0) - oauth2 (>= 1.4, < 3) - omniauth (~> 2.0) - omniauth-rails_csrf_protection (1.0.1) - actionpack (>= 4.2) - omniauth (~> 2.0) - omniauth-twitter (1.4.0) - omniauth-oauth (~> 1.1) - rack - orm_adapter (0.5.0) - paper_trail (12.3.0) - activerecord (>= 5.2) - request_store (~> 1.1) - parallel (1.23.0) - parser (3.2.2.4) - ast (~> 2.4.1) - racc - pg (1.1.4) - pg_search (2.3.6) - activerecord (>= 5.2) - activesupport (>= 5.2) - polyglot (0.3.5) - premailer (1.21.0) - addressable - css_parser (>= 1.12.0) - htmlentities (>= 4.0.0) - premailer-rails (1.12.0) - actionmailer (>= 3) - net-smtp - premailer (~> 1.7, >= 1.7.9) - public_suffix (5.0.3) - puma (5.6.7) - nio4r (~> 2.0) - racc (1.7.3) - rack (2.2.8) - rack-attack (6.7.0) - rack (>= 1.0, < 4) - rack-cors (1.1.1) - rack (>= 2.0.0) - rack-protection (3.1.0) - rack (~> 2.2, >= 2.2.4) - rack-proxy (0.7.7) - rack - rack-test (2.1.0) - rack (>= 1.3) - rails (6.0.6.1) - actioncable (= 6.0.6.1) - actionmailbox (= 6.0.6.1) - actionmailer (= 6.0.6.1) - actionpack (= 6.0.6.1) - actiontext (= 6.0.6.1) - actionview (= 6.0.6.1) - activejob (= 6.0.6.1) - activemodel (= 6.0.6.1) - activerecord (= 6.0.6.1) - activestorage (= 6.0.6.1) - activesupport (= 6.0.6.1) - bundler (>= 1.3.0) - railties (= 6.0.6.1) - sprockets-rails (>= 2.0.0) - rails-controller-testing (1.0.5) - actionpack (>= 5.0.1.rc1) - actionview (>= 5.0.1.rc1) - activesupport (>= 5.0.1.rc1) - rails-dom-testing (2.2.0) - activesupport (>= 5.0.0) - minitest - nokogiri (>= 1.6) - rails-html-sanitizer (1.4.3) - loofah (~> 2.3) - rails-i18n (6.0.0) - i18n (>= 0.7, < 2) - railties (>= 6.0.0, < 7) - railties (6.0.6.1) - actionpack (= 6.0.6.1) - activesupport (= 6.0.6.1) - method_source - rake (>= 0.8.7) - thor (>= 0.20.3, < 2.0) - rainbow (3.1.1) - rake (13.1.0) - ransack (2.4.2) - activerecord (>= 5.2.4) - activesupport (>= 5.2.4) - i18n - rb-fsevent (0.11.2) - rb-inotify (0.10.1) - ffi (~> 1.0) - rectify (0.13.0) - activemodel (>= 4.1.0) - activerecord (>= 4.1.0) - activesupport (>= 4.1.0) - virtus (~> 1.0.5) - wisper (>= 1.6.1) - redcarpet (3.6.0) - redis (4.8.1) - regexp_parser (2.8.2) - request_store (1.5.1) - rack (>= 1.4) - responders (3.1.1) - actionpack (>= 5.2) - railties (>= 5.2) - rexml (3.2.6) - rspec (3.12.0) - rspec-core (~> 3.12.0) - rspec-expectations (~> 3.12.0) - rspec-mocks (~> 3.12.0) - rspec-cells (0.3.8) - cells (>= 4.0.0, < 6.0.0) - rspec-rails (>= 3.0.0, < 6.1.0) - rspec-core (3.12.2) - rspec-support (~> 3.12.0) - rspec-expectations (3.12.3) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.12.0) - rspec-html-matchers (0.9.4) - nokogiri (~> 1) - rspec (>= 3.0.0.a, < 4) - rspec-mocks (3.12.6) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.12.0) - rspec-rails (4.1.2) - actionpack (>= 4.2) - activesupport (>= 4.2) - railties (>= 4.2) - rspec-core (~> 3.10) - rspec-expectations (~> 3.10) - rspec-mocks (~> 3.10) - rspec-support (~> 3.10) - rspec-retry (0.6.2) - rspec-core (> 3.3) - rspec-support (3.12.1) - rspec_junit_formatter (0.3.0) - rspec-core (>= 2, < 4, != 2.12.0) - rubocop (0.92.0) - parallel (~> 1.10) - parser (>= 2.7.1.5) - rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.7) - rexml - rubocop-ast (>= 0.5.0) - ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 2.0) - rubocop-ast (1.30.0) - parser (>= 3.2.1.0) - rubocop-faker (1.1.0) - faker (>= 2.12.0) - rubocop (>= 0.82.0) - rubocop-rails (2.9.1) - activesupport (>= 4.2.0) - rack (>= 1.1) - rubocop (>= 0.90.0, < 2.0) - rubocop-rspec (1.43.2) - rubocop (~> 0.87) - ruby-progressbar (1.13.0) - ruby-vips (2.2.0) - ffi (~> 1.12) - ruby2_keywords (0.0.5) - rubyXL (3.4.25) - nokogiri (>= 1.10.8) - rubyzip (>= 1.3.0) - rubyzip (2.3.2) - sassc (2.4.0) - ffi (~> 1.9) - searchlight (4.1.0) - selenium-webdriver (3.142.7) - childprocess (>= 0.5, < 4.0) - rubyzip (>= 1.2.2) - semantic_range (3.0.0) - seven_zip_ruby (1.3.0) - simplecov (0.19.1) - docile (~> 1.1) - simplecov-html (~> 0.11) - simplecov-cobertura (1.3.1) - simplecov (~> 0.8) - simplecov-html (0.12.3) - smart_properties (1.17.0) - snaky_hash (2.0.1) - hashie - version_gem (~> 1.1, >= 1.1.1) - social-share-button (1.2.4) - coffee-rails - spring (2.1.1) - spring-watcher-listen (2.0.1) - listen (>= 2.7, < 4.0) - spring (>= 1.2, < 3.0) - sprockets (4.2.1) - concurrent-ruby (~> 1.0) - rack (>= 2.2.4, < 4) - sprockets-rails (3.4.2) - actionpack (>= 5.2) - activesupport (>= 5.2) - sprockets (>= 3.0.0) - ssrf_filter (1.1.2) - system_test_html_screenshots (0.2.0) - actionpack (>= 5.2, < 6.1.a) - temple (0.10.3) - terminal-table (3.0.2) - unicode-display_width (>= 1.1.1, < 3) - thor (1.3.0) - thread_safe (0.3.6) - tilt (2.3.0) - timeout (0.4.1) - tomlrb (2.0.3) - tzinfo (1.2.11) - thread_safe (~> 0.1) - uber (0.1.0) - uglifier (4.2.0) - execjs (>= 0.3.0, < 3) - unicode-display_width (1.8.0) - valid_email2 (2.3.1) - activemodel (>= 3.2) - mail (~> 2.5) - version_gem (1.1.3) - virtus (1.0.5) - axiom-types (~> 0.1) - coercible (~> 1.0) - descendants_tracker (~> 0.0, >= 0.0.3) - equalizer (~> 0.0, >= 0.0.9) - w3c_rspec_validators (0.3.0) - rails - rspec - w3c_validators - w3c_validators (1.3.7) - json (>= 1.8) - nokogiri (~> 1.6) - rexml (~> 3.2) - warden (1.2.9) - rack (>= 2.0.9) - web-console (3.7.0) - actionview (>= 5.0) - activemodel (>= 5.0) - bindex (>= 0.4.0) - railties (>= 5.0) - webmock (3.19.1) - addressable (>= 2.8.0) - crack (>= 0.3.2) - hashdiff (>= 0.4.0, < 2.0.0) - webpacker (6.0.0.rc.5) - activesupport (>= 5.2) - rack-proxy (>= 0.6.1) - railties (>= 5.2) - semantic_range (>= 2.3.0) - websocket-driver (0.7.6) - websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.5) - wicked_pdf (2.7.0) - activesupport - wisper (2.0.1) - wisper-rspec (1.1.0) - wkhtmltopdf-binary (0.12.6.6) - xpath (3.2.0) - nokogiri (~> 1.8) - zeitwerk (2.6.12) - -PLATFORMS - x86_64-linux - -DEPENDENCIES - bootsnap (~> 1.4) - byebug (~> 11.0) - codecov - decidim (= 0.26.8) - decidim-decidim_awesome! - decidim-dev (= 0.26.8) - faker (~> 2.14) - letter_opener_web (~> 1.3) - listen (~> 3.1) - puma (>= 5.5.1) - rubocop-faker - spring (~> 2.0) - spring-watcher-listen (~> 2.0.0) - uglifier (~> 4.1) - web-console (~> 3.5) - -RUBY VERSION - ruby 2.7.7p221 - -BUNDLED WITH - 2.3.20 diff --git a/Gemfile.lock b/Gemfile.lock index 84a507f9c..8e942e19c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,49 +1,49 @@ PATH remote: . specs: - decidim-decidim_awesome (0.10.2) - decidim-admin (>= 0.26.0, < 0.28) - decidim-core (>= 0.26.0, < 0.28) + decidim-decidim_awesome (0.11.1) + decidim-admin (>= 0.28.0, < 0.29) + decidim-core (>= 0.28.0, < 0.29) deface (>= 1.5) sassc (~> 2.3) GEM remote: https://rubygems.org/ specs: - actioncable (6.1.7.6) - actionpack (= 6.1.7.6) - activesupport (= 6.1.7.6) + actioncable (6.1.7.8) + actionpack (= 6.1.7.8) + activesupport (= 6.1.7.8) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.7.6) - actionpack (= 6.1.7.6) - activejob (= 6.1.7.6) - activerecord (= 6.1.7.6) - activestorage (= 6.1.7.6) - activesupport (= 6.1.7.6) + actionmailbox (6.1.7.8) + actionpack (= 6.1.7.8) + activejob (= 6.1.7.8) + activerecord (= 6.1.7.8) + activestorage (= 6.1.7.8) + activesupport (= 6.1.7.8) mail (>= 2.7.1) - actionmailer (6.1.7.6) - actionpack (= 6.1.7.6) - actionview (= 6.1.7.6) - activejob (= 6.1.7.6) - activesupport (= 6.1.7.6) + actionmailer (6.1.7.8) + actionpack (= 6.1.7.8) + actionview (= 6.1.7.8) + activejob (= 6.1.7.8) + activesupport (= 6.1.7.8) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.1.7.6) - actionview (= 6.1.7.6) - activesupport (= 6.1.7.6) + actionpack (6.1.7.8) + actionview (= 6.1.7.8) + activesupport (= 6.1.7.8) rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.7.6) - actionpack (= 6.1.7.6) - activerecord (= 6.1.7.6) - activestorage (= 6.1.7.6) - activesupport (= 6.1.7.6) + actiontext (6.1.7.8) + actionpack (= 6.1.7.8) + activerecord (= 6.1.7.8) + activestorage (= 6.1.7.8) + activesupport (= 6.1.7.8) nokogiri (>= 1.8.5) - actionview (6.1.7.6) - activesupport (= 6.1.7.6) + actionview (6.1.7.8) + activesupport (= 6.1.7.8) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -51,60 +51,53 @@ GEM active_link_to (1.0.5) actionpack addressable - activejob (6.1.7.6) - activesupport (= 6.1.7.6) + activejob (6.1.7.8) + activesupport (= 6.1.7.8) globalid (>= 0.3.6) - activemodel (6.1.7.6) - activesupport (= 6.1.7.6) - activerecord (6.1.7.6) - activemodel (= 6.1.7.6) - activesupport (= 6.1.7.6) - activestorage (6.1.7.6) - actionpack (= 6.1.7.6) - activejob (= 6.1.7.6) - activerecord (= 6.1.7.6) - activesupport (= 6.1.7.6) + activemodel (6.1.7.8) + activesupport (= 6.1.7.8) + activerecord (6.1.7.8) + activemodel (= 6.1.7.8) + activesupport (= 6.1.7.8) + activestorage (6.1.7.8) + actionpack (= 6.1.7.8) + activejob (= 6.1.7.8) + activerecord (= 6.1.7.8) + activesupport (= 6.1.7.8) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (6.1.7.6) + activesupport (6.1.7.8) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) zeitwerk (~> 2.3) - acts_as_list (0.9.19) - activerecord (>= 3.0) - addressable (2.8.5) - public_suffix (>= 2.0.2, < 6.0) + acts_as_list (1.2.1) + activerecord (>= 6.1) + activesupport (>= 6.1) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) ast (2.4.2) - axe-core-api (4.9.0) - dumb_delegator - virtus - axe-core-rspec (4.1.0) - axe-core-api - dumb_delegator - virtus - axiom-types (0.1.1) - descendants_tracker (~> 0.0.4) - ice_nine (~> 0.11.0) - thread_safe (~> 0.3, >= 0.3.1) - base64 (0.1.1) + base64 (0.2.0) batch-loader (1.5.0) - bcrypt (3.1.19) - better_html (1.0.16) - actionview (>= 4.0) - activesupport (>= 4.0) + bcrypt (3.1.20) + better_html (2.1.1) + actionview (>= 6.0) + activesupport (>= 6.0) ast (~> 2.0) erubi (~> 1.4) - html_tokenizer (~> 0.0.6) parser (>= 2.4) smart_properties bigdecimal (3.1.8) bindex (0.8.1) - bootsnap (1.17.0) + bootsnap (1.18.3) msgpack (~> 1.2) + brakeman (5.4.1) browser (2.7.1) - builder (3.2.4) + builder (3.3.0) + bullet (7.2.0) + activesupport (>= 3.0.0) + uniform_notifier (~> 1.11) byebug (11.1.3) capybara (3.40.0) addressable @@ -134,86 +127,71 @@ GEM cells-rails (0.1.5) actionpack (>= 5.0) cells (>= 4.1.6, < 5.0.0) - charlock_holmes (0.7.7) - chef-utils (18.4.12) - concurrent-ruby - childprocess (4.1.0) - codecov (0.6.0) - simplecov (>= 0.15, < 0.22) - coercible (1.0.0) - descendants_tracker (~> 0.0.1) - coffee-rails (5.0.0) - coffee-script (>= 2.2.0) - railties (>= 5.2.0) - coffee-script (2.4.1) - coffee-script-source - execjs - coffee-script-source (1.12.2) + charlock_holmes (0.7.8) + childprocess (5.0.0) commonmarker (0.23.10) - concurrent-ruby (1.2.2) + concurrent-ruby (1.3.3) crack (1.0.0) bigdecimal rexml crass (1.0.6) - css_parser (1.16.0) + css_parser (1.17.1) addressable - date (3.3.3) + csv (3.3.0) date_validator (0.12.0) activemodel (>= 3) activesupport (>= 3) - db-query-matchers (0.10.0) - activesupport (>= 4.0, < 7) - rspec (~> 3.0) - decidim (0.27.6) - decidim-accountability (= 0.27.6) - decidim-admin (= 0.27.6) - decidim-api (= 0.27.6) - decidim-assemblies (= 0.27.6) - decidim-blogs (= 0.27.6) - decidim-budgets (= 0.27.6) - decidim-comments (= 0.27.6) - decidim-core (= 0.27.6) - decidim-debates (= 0.27.6) - decidim-forms (= 0.27.6) - decidim-generators (= 0.27.6) - decidim-meetings (= 0.27.6) - decidim-pages (= 0.27.6) - decidim-participatory_processes (= 0.27.6) - decidim-proposals (= 0.27.6) - decidim-sortitions (= 0.27.6) - decidim-surveys (= 0.27.6) - decidim-system (= 0.27.6) - decidim-templates (= 0.27.6) - decidim-verifications (= 0.27.6) - decidim-accountability (0.27.6) - decidim-comments (= 0.27.6) - decidim-core (= 0.27.6) - decidim-admin (0.27.6) + decidim (0.28.2) + decidim-accountability (= 0.28.2) + decidim-admin (= 0.28.2) + decidim-api (= 0.28.2) + decidim-assemblies (= 0.28.2) + decidim-blogs (= 0.28.2) + decidim-budgets (= 0.28.2) + decidim-comments (= 0.28.2) + decidim-core (= 0.28.2) + decidim-debates (= 0.28.2) + decidim-forms (= 0.28.2) + decidim-generators (= 0.28.2) + decidim-meetings (= 0.28.2) + decidim-pages (= 0.28.2) + decidim-participatory_processes (= 0.28.2) + decidim-proposals (= 0.28.2) + decidim-sortitions (= 0.28.2) + decidim-surveys (= 0.28.2) + decidim-system (= 0.28.2) + decidim-templates (= 0.28.2) + decidim-verifications (= 0.28.2) + decidim-accountability (0.28.2) + decidim-comments (= 0.28.2) + decidim-core (= 0.28.2) + decidim-admin (0.28.2) active_link_to (~> 1.0) - decidim-core (= 0.27.6) + decidim-core (= 0.28.2) devise (~> 4.7) devise-i18n (~> 1.2) devise_invitable (~> 2.0, >= 2.0.9) - decidim-api (0.27.6) - decidim-core (= 0.27.6) - graphql (~> 1.12, < 1.13) - graphql-docs (~> 2.1.0) + decidim-api (0.28.2) + commonmarker (~> 0.23.0, >= 0.23.9) + decidim-core (= 0.28.2) + graphql (~> 2.0.0) + graphql-docs (~> 3.0.1) rack-cors (~> 1.0) - decidim-assemblies (0.27.6) - decidim-core (= 0.27.6) - decidim-blogs (0.27.6) - decidim-admin (= 0.27.6) - decidim-comments (= 0.27.6) - decidim-core (= 0.27.6) - decidim-budgets (0.27.6) - decidim-comments (= 0.27.6) - decidim-core (= 0.27.6) - decidim-comments (0.27.6) - decidim-core (= 0.27.6) + decidim-assemblies (0.28.2) + decidim-core (= 0.28.2) + decidim-blogs (0.28.2) + decidim-admin (= 0.28.2) + decidim-comments (= 0.28.2) + decidim-core (= 0.28.2) + decidim-budgets (0.28.2) + decidim-comments (= 0.28.2) + decidim-core (= 0.28.2) + decidim-comments (0.28.2) + decidim-core (= 0.28.2) redcarpet (~> 3.5, >= 3.5.1) - decidim-core (0.27.6) + decidim-core (0.28.2) active_link_to (~> 1.0) - acts_as_list (~> 0.9) + acts_as_list (~> 1.0) batch-loader (~> 1.2) browser (~> 2.7) carrierwave (~> 2.2.5, >= 2.2.5) @@ -222,9 +200,9 @@ GEM charlock_holmes (~> 0.7) date_validator (~> 0.12.0) devise (~> 4.7) - devise-i18n (~> 1.2) + devise-i18n (~> 1.2, < 1.11.1) diffy (~> 3.3) - doorkeeper (~> 5.1) + doorkeeper (~> 5.6, >= 5.6.6) doorkeeper-i18n (~> 4.0) file_validators (~> 3.0) fog-local (~> 0.6) @@ -233,102 +211,104 @@ GEM hashdiff (>= 0.4.0, < 2.0.0) invisible_captcha (~> 0.12) kaminari (~> 1.2, >= 1.2.1) - loofah (~> 2.19.0) + loofah (~> 2.19, >= 2.19.1) mime-types (>= 1.16, < 4.0) mini_magick (~> 4.9) + net-smtp (~> 0.3.1) omniauth (~> 2.0) omniauth-facebook (~> 5.0) omniauth-google-oauth2 (~> 1.0) omniauth-rails_csrf_protection (~> 1.0) omniauth-twitter (~> 1.4) paper_trail (~> 12.0) - pg (~> 1.1.4, < 2) + pg (~> 1.4.0, < 2) pg_search (~> 2.2) premailer-rails (~> 1.10) - rack (~> 2.2, >= 2.2.3) + psych (~> 4.0) + rack (~> 2.2, >= 2.2.6.4) rack-attack (~> 6.0) - rails (~> 6.1.0) + rails (~> 6.1.7, >= 6.1.7.4) rails-i18n (~> 6.0) - ransack (~> 2.4.1) + ransack (~> 3.2.1) redis (~> 4.1) request_store (~> 1.5.0) rubyXL (~> 3.4) rubyzip (~> 2.0) seven_zip_ruby (~> 1.3) - social-share-button (~> 1.2, >= 1.2.1) - valid_email2 (~> 2.1) - webpacker (= 6.0.0.rc.5) - webpush (~> 1.1) + shakapacker (~> 7.1.0) + valid_email2 (~> 4.0) + web-push (~> 3.0) wisper (~> 2.0) - decidim-debates (0.27.6) - decidim-comments (= 0.27.6) - decidim-core (= 0.27.6) - decidim-dev (0.27.6) - axe-core-rspec (~> 4.1.0) + decidim-debates (0.28.2) + decidim-comments (= 0.28.2) + decidim-core (= 0.28.2) + decidim-dev (0.28.2) + bullet (~> 7.0) byebug (~> 11.0) - capybara (~> 3.24) - db-query-matchers (~> 0.10.0) - decidim (= 0.27.6) - erb_lint (~> 0.0.35) - factory_bot_rails (~> 4.8) - i18n-tasks (~> 0.9.18) - mdl (~> 0.5) - nokogiri (~> 1.13) - parallel_tests (~> 3.7) - puma (~> 5.0) + capybara (~> 3.39) + decidim (= 0.28.2) + erb_lint (~> 0.4.0) + factory_bot_rails (~> 6.2) + faker (~> 3.2) + i18n-tasks (~> 1.0) + nokogiri (~> 1.14, >= 1.14.3) + parallel_tests (~> 4.2) + puma (~> 6.2, >= 6.3.1) rails-controller-testing (~> 1.0) + rspec (~> 3.12) rspec-cells (~> 0.3.7) - rspec-html-matchers (~> 0.9.1) - rspec-rails (~> 4.0) + rspec-html-matchers (~> 0.10) + rspec-rails (~> 6.0) rspec-retry (~> 0.6.2) - rspec_junit_formatter (~> 0.3.0) - rubocop (~> 1.28.0) - rubocop-rails (~> 2.14) - rubocop-rspec (~> 2.10) - selenium-webdriver (~> 4.1.0) - simplecov (~> 0.21.0) + rspec_junit_formatter (~> 0.6.0) + rubocop (~> 1.50.0) + rubocop-faker (~> 1.1) + rubocop-rails (~> 2.19) + rubocop-rspec (~> 2.20) + selenium-webdriver (~> 4.9) + simplecov (~> 0.22.0) simplecov-cobertura (~> 2.1.0) w3c_rspec_validators (~> 0.3.0) - webmock (~> 3.6) + webmock (~> 3.18) wisper-rspec (~> 1.0) - decidim-forms (0.27.6) - decidim-core (= 0.27.6) + decidim-forms (0.28.2) + decidim-core (= 0.28.2) wicked_pdf (~> 2.1) wkhtmltopdf-binary (~> 0.12) - decidim-generators (0.27.6) - decidim-core (= 0.27.6) - decidim-meetings (0.27.6) - decidim-core (= 0.27.6) - decidim-forms (= 0.27.6) + decidim-generators (0.28.2) + decidim-core (= 0.28.2) + decidim-meetings (0.28.2) + decidim-core (= 0.28.2) + decidim-forms (= 0.28.2) icalendar (~> 2.5) - decidim-pages (0.27.6) - decidim-core (= 0.27.6) - decidim-participatory_processes (0.27.6) - decidim-core (= 0.27.6) - decidim-proposals (0.27.6) - decidim-comments (= 0.27.6) - decidim-core (= 0.27.6) - doc2text (~> 0.4.5) + decidim-pages (0.28.2) + decidim-core (= 0.28.2) + decidim-participatory_processes (0.28.2) + decidim-core (= 0.28.2) + decidim-proposals (0.28.2) + decidim-comments (= 0.28.2) + decidim-core (= 0.28.2) + doc2text (~> 0.4.6) redcarpet (~> 3.5, >= 3.5.1) - decidim-sortitions (0.27.6) - decidim-admin (= 0.27.6) - decidim-comments (= 0.27.6) - decidim-core (= 0.27.6) - decidim-proposals (= 0.27.6) - decidim-surveys (0.27.6) - decidim-core (= 0.27.6) - decidim-forms (= 0.27.6) - decidim-system (0.27.6) + decidim-sortitions (0.28.2) + decidim-admin (= 0.28.2) + decidim-comments (= 0.28.2) + decidim-core (= 0.28.2) + decidim-proposals (= 0.28.2) + decidim-surveys (0.28.2) + decidim-core (= 0.28.2) + decidim-forms (= 0.28.2) + decidim-system (0.28.2) active_link_to (~> 1.0) - decidim-core (= 0.27.6) + decidim-core (= 0.28.2) devise (~> 4.7) devise-i18n (~> 1.2) devise_invitable (~> 2.0, >= 2.0.9) - decidim-templates (0.27.6) - decidim-core (= 0.27.6) - decidim-forms (= 0.27.6) - decidim-verifications (0.27.6) - decidim-core (= 0.27.6) + decidim-templates (0.28.2) + decidim-core (= 0.28.2) + decidim-forms (= 0.28.2) + decidim-verifications (0.28.2) + decidim-core (= 0.28.2) declarative-builder (0.1.0) declarative-option (< 0.2.0) declarative-option (0.1.0) @@ -338,62 +318,58 @@ GEM polyglot railties (>= 5.2) rainbow (>= 2.1.0) - descendants_tracker (0.0.4) - thread_safe (~> 0.3, >= 0.3.1) - devise (4.9.3) + devise (4.9.4) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) responders warden (~> 1.2.3) - devise-i18n (1.12.0) + devise-i18n (1.11.0) devise (>= 4.9.0) devise_invitable (2.0.9) actionmailer (>= 5.0) devise (>= 4.6) diff-lcs (1.5.1) diffy (3.4.2) + digest (3.1.1) doc2text (0.4.7) nokogiri (>= 1.13.2, < 1.17.0) rubyzip (~> 2.3.0) - docile (1.4.0) - doorkeeper (5.6.6) + docile (1.4.1) + doorkeeper (5.7.1) railties (>= 5) doorkeeper-i18n (4.0.1) - dumb_delegator (1.0.0) - erb_lint (0.0.37) + erb_lint (0.4.0) activesupport - better_html (~> 1.0.7) - html_tokenizer + better_html (>= 2.0.1) parser (>= 2.7.1.4) rainbow rubocop smart_properties erbse (0.1.4) temple - erubi (1.12.0) - escape_utils (1.3.0) - excon (0.104.0) - execjs (2.9.1) + erubi (1.13.0) + escape_utils (1.2.2) + excon (0.111.0) extended-markdown-filter (0.7.0) html-pipeline (~> 2.9) - factory_bot (4.11.1) - activesupport (>= 3.0.0) - factory_bot_rails (4.11.1) - factory_bot (~> 4.11.1) - railties (>= 3.0.0) - faker (2.23.0) + factory_bot (6.4.6) + activesupport (>= 5.0.0) + factory_bot_rails (6.4.3) + factory_bot (~> 6.4) + railties (>= 5.0.0) + faker (3.4.2) i18n (>= 1.8.11, < 2) - faraday (2.7.11) - base64 - faraday-net_http (>= 2.0, < 3.1) - ruby2_keywords (>= 0.0.4) - faraday-net_http (3.0.2) - ffi (1.16.3) + faraday (2.10.0) + faraday-net_http (>= 2.0, < 3.2) + logger + faraday-net_http (3.1.0) + net-http + ffi (1.17.0) file_validators (3.0.0) activemodel (>= 3.2) mime-types (>= 1.0) - fog-core (2.3.0) + fog-core (2.4.0) builder excon (~> 0.71) formatador (>= 0.2, < 2.0) @@ -407,50 +383,53 @@ GEM activesupport (>= 4.1, < 7.1) railties (>= 4.1, < 7.1) gemoji (3.0.1) - geocoder (1.8.2) + geocoder (1.8.3) + base64 (>= 0.1.0) + csv (>= 3.0.0) globalid (1.2.1) activesupport (>= 6.1) - graphql (1.12.24) - graphql-docs (2.1.0) + graphql (2.0.31) + base64 + graphql-docs (3.0.1) commonmarker (~> 0.16) - escape_utils (~> 1.2) + escape_utils (~> 1.2.2) extended-markdown-filter (~> 0.4) gemoji (~> 3.0) - graphql (~> 1.12) + graphql (~> 2.0) html-pipeline (~> 2.9) sass (~> 3.4) - hashdiff (1.0.1) + hashdiff (1.1.0) hashie (5.0.0) - highline (3.0.1) - hkdf (0.3.0) + highline (3.1.0) + reline html-pipeline (2.14.3) activesupport (>= 2) nokogiri (>= 1.4) - html_tokenizer (0.0.8) htmlentities (4.3.4) - i18n (1.14.1) + i18n (1.14.5) concurrent-ruby (~> 1.0) - i18n-tasks (0.9.37) + i18n-tasks (1.0.14) activesupport (>= 4.0.2) ast (>= 2.1.0) erubi highline (>= 2.0.0) i18n - parser (>= 2.2.3.0) + parser (>= 3.2.2.1) rails-i18n rainbow (>= 2.2.2, < 4.0) terminal-table (>= 1.5.1) - icalendar (2.10.1) + icalendar (2.10.2) ice_cube (~> 0.16) - ice_cube (0.16.4) - ice_nine (0.11.2) + ice_cube (0.17.0) image_processing (1.12.2) mini_magick (>= 4.9.5, < 5) ruby-vips (>= 2.0.17, < 3) invisible_captcha (0.13.0) rails (>= 3.2.0) + io-console (0.7.2) json (2.7.2) - jwt (2.7.1) + jwt (2.8.2) + base64 kaminari (1.2.2) activesupport (>= 4.1.0) kaminari-actionview (= 1.2.2) @@ -463,62 +442,53 @@ GEM activerecord kaminari-core (= 1.2.2) kaminari-core (1.2.2) - kramdown (2.4.0) - rexml - kramdown-parser-gfm (1.1.0) - kramdown (~> 2.0) - launchy (2.5.2) + launchy (3.0.1) addressable (~> 2.8) - letter_opener (1.8.1) - launchy (>= 2.2, < 3) - letter_opener_web (1.4.1) - actionmailer (>= 3.2) - letter_opener (~> 1.0) - railties (>= 3.2) - listen (3.8.0) + childprocess (~> 5.0) + letter_opener (1.10.0) + launchy (>= 2.2, < 4) + letter_opener_web (2.0.0) + actionmailer (>= 5.2) + letter_opener (~> 1.7) + railties (>= 5.2) + rexml + listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - loofah (2.19.1) + logger (1.6.0) + loofah (2.22.0) crass (~> 1.0.2) - nokogiri (>= 1.5.9) + nokogiri (>= 1.12.0) mail (2.8.1) mini_mime (>= 0.1.1) net-imap net-pop net-smtp - marcel (1.0.2) + marcel (1.0.4) matrix (0.4.2) - mdl (0.13.0) - kramdown (~> 2.3) - kramdown-parser-gfm (~> 1.1) - mixlib-cli (~> 2.1, >= 2.1.1) - mixlib-config (>= 2.2.1, < 4) - mixlib-shellout - method_source (1.0.0) - mime-types (3.5.1) + method_source (1.1.0) + mime-types (3.5.2) mime-types-data (~> 3.2015) - mime-types-data (3.2023.1003) - mini_magick (4.12.0) + mime-types-data (3.2024.0702) + mini_magick (4.13.2) mini_mime (1.1.5) - minitest (5.20.0) - mixlib-cli (2.1.8) - mixlib-config (3.0.27) - tomlrb - mixlib-shellout (3.2.7) - chef-utils + minitest (5.24.1) msgpack (1.7.2) multi_xml (0.6.0) - net-imap (0.4.4) - date + net-http (0.4.1) + uri + net-imap (0.2.4) + digest net-protocol + strscan net-pop (0.1.2) net-protocol - net-protocol (0.2.1) + net-protocol (0.2.2) timeout - net-smtp (0.4.0) + net-smtp (0.3.4) net-protocol - nio4r (2.5.9) - nokogiri (1.14.5-x86_64-linux) + nio4r (2.7.3) + nokogiri (1.16.6-x86_64-linux) racc (~> 1.4) oauth (1.1.0) oauth-tty (~> 1.0, >= 1.0.1) @@ -533,45 +503,46 @@ GEM rack (>= 1.2, < 4) snaky_hash (~> 2.0) version_gem (~> 1.1) - omniauth (2.1.1) + omniauth (2.1.2) hashie (>= 3.4.6) rack (>= 2.2.3) rack-protection omniauth-facebook (5.0.0) omniauth-oauth2 (~> 1.2) - omniauth-google-oauth2 (1.1.1) + omniauth-google-oauth2 (1.1.2) jwt (>= 2.0) - oauth2 (~> 2.0.6) + oauth2 (~> 2.0) omniauth (~> 2.0) - omniauth-oauth2 (~> 1.8.0) + omniauth-oauth2 (~> 1.8) omniauth-oauth (1.2.0) oauth omniauth (>= 1.0, < 3) omniauth-oauth2 (1.8.0) oauth2 (>= 1.4, < 3) omniauth (~> 2.0) - omniauth-rails_csrf_protection (1.0.1) + omniauth-rails_csrf_protection (1.0.2) actionpack (>= 4.2) omniauth (~> 2.0) omniauth-twitter (1.4.0) omniauth-oauth (~> 1.1) rack + openssl (3.2.0) orm_adapter (0.5.0) paper_trail (12.3.0) activerecord (>= 5.2) request_store (~> 1.1) - parallel (1.23.0) - parallel_tests (3.13.0) + parallel (1.25.1) + parallel_tests (4.7.1) parallel - parser (3.2.2.4) + parser (3.3.4.0) ast (~> 2.4.1) racc - pg (1.1.4) + pg (1.4.6) pg_search (2.3.6) activerecord (>= 5.2) activesupport (>= 5.2) polyglot (0.3.5) - premailer (1.21.0) + premailer (1.23.0) addressable css_parser (>= 1.12.0) htmlentities (>= 4.0.0) @@ -579,35 +550,38 @@ GEM actionmailer (>= 3) net-smtp premailer (~> 1.7, >= 1.7.9) - public_suffix (5.0.3) - puma (5.6.7) + psych (4.0.6) + stringio + public_suffix (6.0.0) + puma (6.4.2) nio4r (~> 2.0) - racc (1.7.2) - rack (2.2.8) + racc (1.8.0) + rack (2.2.9) rack-attack (6.7.0) rack (>= 1.0, < 4) rack-cors (1.1.1) rack (>= 2.0.0) - rack-protection (3.1.0) + rack-protection (3.2.0) + base64 (>= 0.1.0) rack (~> 2.2, >= 2.2.4) rack-proxy (0.7.7) rack rack-test (2.1.0) rack (>= 1.3) - rails (6.1.7.6) - actioncable (= 6.1.7.6) - actionmailbox (= 6.1.7.6) - actionmailer (= 6.1.7.6) - actionpack (= 6.1.7.6) - actiontext (= 6.1.7.6) - actionview (= 6.1.7.6) - activejob (= 6.1.7.6) - activemodel (= 6.1.7.6) - activerecord (= 6.1.7.6) - activestorage (= 6.1.7.6) - activesupport (= 6.1.7.6) + rails (6.1.7.8) + actioncable (= 6.1.7.8) + actionmailbox (= 6.1.7.8) + actionmailer (= 6.1.7.8) + actionpack (= 6.1.7.8) + actiontext (= 6.1.7.8) + actionview (= 6.1.7.8) + activejob (= 6.1.7.8) + activemodel (= 6.1.7.8) + activerecord (= 6.1.7.8) + activestorage (= 6.1.7.8) + activesupport (= 6.1.7.8) bundler (>= 1.15.0) - railties (= 6.1.7.6) + railties (= 6.1.7.8) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) @@ -617,35 +591,39 @@ GEM activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.5.0) - loofah (~> 2.19, >= 2.19.1) + rails-html-sanitizer (1.6.0) + loofah (~> 2.21) + nokogiri (~> 1.14) rails-i18n (6.0.0) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 7) - railties (6.1.7.6) - actionpack (= 6.1.7.6) - activesupport (= 6.1.7.6) + railties (6.1.7.8) + actionpack (= 6.1.7.8) + activesupport (= 6.1.7.8) method_source rake (>= 12.2) thor (~> 1.0) rainbow (3.1.1) - rake (13.1.0) - ransack (2.4.2) - activerecord (>= 5.2.4) - activesupport (>= 5.2.4) + rake (13.2.1) + ransack (3.2.1) + activerecord (>= 6.1.5) + activesupport (>= 6.1.5) i18n rb-fsevent (0.11.2) - rb-inotify (0.10.1) + rb-inotify (0.11.1) ffi (~> 1.0) redcarpet (3.6.0) redis (4.8.1) - regexp_parser (2.8.2) + regexp_parser (2.9.2) + reline (0.5.9) + io-console (~> 0.5) request_store (1.5.1) rack (>= 1.4) responders (3.1.1) actionpack (>= 5.2) railties (>= 5.2) - rexml (3.2.6) + rexml (3.3.1) + strscan rspec (3.13.0) rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) @@ -655,53 +633,56 @@ GEM rspec-rails (>= 3.0.0, < 6.2.0) rspec-core (3.13.0) rspec-support (~> 3.13.0) - rspec-expectations (3.13.0) + rspec-expectations (3.13.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-html-matchers (0.9.4) + rspec-html-matchers (0.10.0) nokogiri (~> 1) - rspec (>= 3.0.0.a, < 4) + rspec (>= 3.0.0.a) rspec-mocks (3.13.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-rails (4.1.2) - actionpack (>= 4.2) - activesupport (>= 4.2) - railties (>= 4.2) - rspec-core (~> 3.10) - rspec-expectations (~> 3.10) - rspec-mocks (~> 3.10) - rspec-support (~> 3.10) + rspec-rails (6.1.3) + actionpack (>= 6.1) + activesupport (>= 6.1) + railties (>= 6.1) + rspec-core (~> 3.13) + rspec-expectations (~> 3.13) + rspec-mocks (~> 3.13) + rspec-support (~> 3.13) rspec-retry (0.6.2) rspec-core (> 3.3) rspec-support (3.13.1) - rspec_junit_formatter (0.3.0) + rspec_junit_formatter (0.6.0) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (1.28.2) + rubocop (1.50.2) + json (~> 2.3) parallel (~> 1.10) - parser (>= 3.1.0.0) + parser (>= 3.2.0.0) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) - rexml - rubocop-ast (>= 1.17.0, < 2.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.28.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.30.0) - parser (>= 3.2.1.0) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.31.3) + parser (>= 3.3.1.0) + rubocop-capybara (2.18.0) + rubocop (~> 1.41) rubocop-faker (1.1.0) faker (>= 2.12.0) rubocop (>= 0.82.0) - rubocop-rails (2.15.2) + rubocop-rails (2.19.1) activesupport (>= 4.2.0) rack (>= 1.1) - rubocop (>= 1.7.0, < 2.0) - rubocop-rspec (2.11.1) - rubocop (~> 1.19) + rubocop (>= 1.33.0, < 2.0) + rubocop-rspec (2.20.0) + rubocop (~> 1.33) + rubocop-capybara (~> 2.17) ruby-progressbar (1.13.0) - ruby-vips (2.2.0) + ruby-vips (2.2.1) ffi (~> 1.12) - ruby2_keywords (0.0.5) - rubyXL (3.4.25) + rubyXL (3.4.27) nokogiri (>= 1.10.8) rubyzip (>= 1.3.0) rubyzip (2.3.2) @@ -712,13 +693,20 @@ GEM rb-inotify (~> 0.9, >= 0.9.7) sassc (2.4.0) ffi (~> 1.9) - selenium-webdriver (4.1.0) - childprocess (>= 0.5, < 5.0) + selenium-webdriver (4.23.0) + base64 (~> 0.2) + logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) - rubyzip (>= 1.2.2) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) semantic_range (3.0.0) seven_zip_ruby (1.3.0) - simplecov (0.21.2) + shakapacker (7.1.0) + activesupport (>= 5.2) + rack-proxy (>= 0.6.1) + railties (>= 5.2) + semantic_range (>= 2.3.0) + simplecov (0.22.0) docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) @@ -731,8 +719,6 @@ GEM snaky_hash (2.0.1) hashie version_gem (~> 1.1, >= 1.1.1) - social-share-button (1.2.4) - coffee-rails spring (2.1.1) spring-watcher-listen (2.0.1) listen (>= 2.7, < 4.0) @@ -740,31 +726,29 @@ GEM sprockets (4.2.1) concurrent-ruby (~> 1.0) rack (>= 2.2.4, < 4) - sprockets-rails (3.4.2) - actionpack (>= 5.2) - activesupport (>= 5.2) + sprockets-rails (3.5.1) + actionpack (>= 6.1) + activesupport (>= 6.1) sprockets (>= 3.0.0) ssrf_filter (1.1.2) + stringio (3.1.1) + strscan (3.1.0) temple (0.10.3) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) - thor (1.3.0) - thread_safe (0.3.6) - tilt (2.3.0) - timeout (0.4.0) - tomlrb (2.0.3) + thor (1.3.1) + tilt (2.4.0) + timeout (0.4.1) tzinfo (2.0.6) concurrent-ruby (~> 1.0) uber (0.1.0) unicode-display_width (2.5.0) - valid_email2 (2.3.1) + uniform_notifier (1.16.0) + uri (0.13.0) + valid_email2 (4.0.6) activemodel (>= 3.2) mail (~> 2.5) - version_gem (1.1.3) - virtus (2.0.0) - axiom-types (~> 0.1) - coercible (~> 1.0) - descendants_tracker (~> 0.0, >= 0.0.3) + version_gem (1.1.4) w3c_rspec_validators (0.3.0) rails rspec @@ -780,18 +764,14 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - webmock (3.23.0) + web-push (3.0.1) + jwt (~> 2.0) + openssl (~> 3.0) + webmock (3.23.1) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webpacker (6.0.0.rc.5) - activesupport (>= 5.2) - rack-proxy (>= 0.6.1) - railties (>= 5.2) - semantic_range (>= 2.3.0) - webpush (1.1.0) - hkdf (~> 0.2) - jwt (~> 2.0) + websocket (1.2.11) websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) @@ -799,32 +779,35 @@ GEM activesupport wisper (2.0.1) wisper-rspec (1.1.0) - wkhtmltopdf-binary (0.12.6.6) + wkhtmltopdf-binary (0.12.6.7) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.12) + zeitwerk (2.6.16) PLATFORMS x86_64-linux DEPENDENCIES bootsnap (~> 1.4) + brakeman (~> 5.4) byebug (~> 11.0) - codecov - decidim (= 0.27.6) + decidim (= 0.28.2) decidim-decidim_awesome! - decidim-dev (= 0.27.6) - faker (~> 2.14) - letter_opener_web (~> 1.3) + decidim-dev (= 0.28.2) + decidim-templates (= 0.28.2) + letter_opener_web (~> 2.0) listen (~> 3.1) - puma (>= 5.5.1) - rubocop-faker + net-imap (~> 0.2.3) + net-pop (~> 0.1.1) + net-smtp (~> 0.3.1) + parallel_tests (~> 4.2) + puma (>= 6.3.1) spring (~> 2.0) - spring-watcher-listen (~> 2.0.0) - web-console + spring-watcher-listen (~> 2.0) + web-console (~> 4.2) RUBY VERSION - ruby 3.0.6p216 + ruby 3.1.1p18 BUNDLED WITH - 2.4.22 + 2.5.4 diff --git a/README.md b/README.md index cc818ace6..26c1f9a45 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,14 @@ # Decidim::DecidimAwesome -[![[CI] Tests 0.27](https://github.com/decidim-ice/decidim-module-decidim_awesome/actions/workflows/tests.yml/badge.svg)](https://github.com/decidim-ice/decidim-module-decidim_awesome/actions/workflows/tests.yml) -[![[CI] Tests 0.26](https://github.com/decidim-ice/decidim-module-decidim_awesome/actions/workflows/tests-legacy.yml/badge.svg)](https://github.com/decidim-ice/decidim-module-decidim_awesome/actions/workflows/tests-legacy.yml) +[![[CI] Tests 0.28](https://github.com/decidim-ice/decidim-module-decidim_awesome/actions/workflows/tests.yml/badge.svg)](https://github.com/decidim-ice/decidim-module-decidim_awesome/actions/workflows/tests.yml) [![[CI] Lint](https://github.com/decidim-ice/decidim-module-decidim_awesome/actions/workflows/lint.yml/badge.svg)](https://github.com/decidim-ice/decidim-module-decidim_awesome/actions/workflows/lint.yml) -[![[CI] Precompile](https://github.com/decidim-ice/decidim-module-decidim_awesome/actions/workflows/precompile.yml/badge.svg)](https://github.com/decidim-ice/decidim-module-decidim_awesome/actions/workflows/precompile.yml) [![Maintainability](https://api.codeclimate.com/v1/badges/2dada53525dd5a944089/maintainability)](https://codeclimate.com/github/decidim-ice/decidim-module-decidim_awesome/maintainability) [![Test Coverage](https://codecov.io/gh/decidim-ice/decidim-module-decidim_awesome/branch/main/graph/badge.svg?token=TFBMCLLZJG)](https://codecov.io/gh/decidim-ice/decidim-module-decidim_awesome) -Usability and UX tweaks for Decidim. +**Usability and UX tweaks for Decidim.** This plugin allows the administrators to expand the possibilities of Decidim beyond some existing limitations. -All tweaks are provided in a optional fashion with granular permissions that let the administrator to choose exactly where to apply those mods. Some tweaks can be applied to any assembly, other in an specific participatory process or even in type of component only. +All tweaks are provided in a optional fashion with granular permissions that let the administrator to choose exactly where to apply those mods. Some tweaks can be applied to an assembly, other in an specific participatory process or even in a type of component only (for instance, only in proposals). **DISCLAIMER: This module is heavily tested and widely used, however we do not accept any responsibility for breaking anything. Feedback is appreciated though.** @@ -31,13 +29,13 @@ Each hack can be scoped to one or more specific participatory spaces or componen ### Tweaks: -#### 1. Image support for the Quill editor +#### 1. Image support for the RichText editor Modifies the WYSIWYG editor in Decidim by adding the possibility to insert images. When uploading images, Drag & Drop is supported. Images will be uploaded to the server and inserted as external resources (it doesn't use base64 in-line encoding). This feature allows you use images in newsletters as well. -![Images in Quill Editor](examples/quill-images.png) +![Images in RichText Editor](examples/quill-images.png) #### 2. Auto-save for surveys and forms @@ -47,27 +45,23 @@ It works purely in the client side by using LocalStorage capabilities of the bro Saving the form removes the stored data. -![Auto save in forms](examples/auto-save.png) +![Auto save in forms](examples/auto-save.gif) #### 3. Images in proposals -Event if you haven't activated the WYSIWYG editor (Quill) in public views (eg: proposals use a simple textarea if rich text editor has not been activated for users). You can allow users to upload images in them by drag & drop over the text area. +Event if you haven't activated the WYSIWYG editor (RichText) in public views (eg: proposals use a simple textarea if rich text editor has not been activated for users). You can allow users to upload images in them by drag & drop over the text area. ![Proposal images](examples/proposal-images.png) -#### 4. Markdown editor for proposals - -Allows to use markdown when creating proposals instead of a bare textarea. - -#### 5. Admin scope configuration +#### 4. Restrict scope for tweaks All tweaks can be configured and scoped to a specific participatory space, a type of participatory space, a type of component or a specific component. -Many scopes can be defined for every tweak. +Many scopes can be defined for every tweak. If a tweak is not scoped, it will be applied globally. -![Admin tweaks for editors](examples/admin-editors.png) +![Tweak scopes](examples/tweak-scopes.png) -#### 6. Awesome map component +#### 5. Awesome map component This is a component you can add in any participatory space. It retrieves all the geolocated content in that participatory space (meetings or proposals) and displays it in a big map. @@ -75,25 +69,25 @@ It also provides a simple search by category, each category is assigned to a dif ![Awesome map](examples/awesome-map.png) -#### 7. Fullscreen Iframe component +#### 6. Fullscreen Iframe component Another simple component that can be used to embed and Iframe with any external content in it that fills all the viewport. ![Fullscreen iframe](examples/fullscreen-iframe.png) -#### 8. Live support chat +#### 7. Live support chat -With this feature you can have a support chat in Decidim. It is linked to a [Telegram](https://telegram.org/) group or a single user chat using the [[IntergramBot](https://web.telegram.org/#/im?p=@IntergramBot). Just invite the bot to a group or chat with it directly, grab your ID, put it on the Awesome settings and have fun!. For more info or for hosting your own version of the bot check the [Intergram project](https://github.com/idoco/intergram). +With this feature you can have a support chat in Decidim. It is linked to a [Telegram](https://telegram.org/) group or a single user chat using the [IntergramBot](https://web.telegram.org/#/im?p=@IntergramBot). Just invite the bot to a group or chat with it directly, grab your ID, put it on the Awesome settings and have fun!. For more info or for hosting your own version of the bot check the [Intergram project](https://github.com/idoco/intergram). ![Intergram screenshot](examples/intergram.png) -#### 9. Custom CSS applied only according scoped restrictions +#### 8. Custom CSS applied only according scoped restrictions With this feature you can create directly in the admin a CSS snipped that is only applied globally, in a particular assembly or even a single proposal! ![CSS screenshot](examples/custom_styles.png) -#### 10. Change the main menu of Decidim entirely! +#### 9. Change the main menu of Decidim entirely! Feel free to hide, modify or add items in the Decidim's main menu. You can also change the order, establish some conditions (like showing only for logged users) or open in a new window. @@ -102,7 +96,7 @@ Feel free to hide, modify or add items in the Decidim's main menu. You can also ![Menu hacks screenshot](examples/menu-3.png) ![Menu hacks screenshot](examples/menu-4.png) -#### 11. Assign admins to specific scopes and prevent them modify anything else +#### 10. Assign admins to specific scopes and prevent them modify anything else Convert any user on the platform (that is not currently an admin) to a limited subset of participatory spaces or event components. Just add users to a box and scope them to some constraints. These users will see the "Edit" button in everywhere they have permissions. Any access to non allowed zones will redirect the user to the admin index page. @@ -110,7 +104,7 @@ Convert any user on the platform (that is not currently an admin) to a limited s ![Scoped admins unauthorized](examples/scoped_admins_unauthorized.png) ![Scoped admins configuration](examples/scoped_admins_config.png) -#### 12. Custom fields for proposals +#### 11. Custom fields for proposals Now admins can substitute the body of a proposal with a set of form fields. Edition is make with a Drag & Drop interface in the admin and can (and should) be scoped to apply only to certain proposal components. @@ -121,12 +115,79 @@ Technically, the content is stored in the database as an XML document compatible ![Custom fields screenshot](examples/custom-fields-2.png) ![Custom fields screenshot](examples/custom-fields-1.gif) -#### 13. Custom Redirections (or URL shortener feature) +Note that the custom fields are build using the jQuery library [formBuilder](https://formbuilder.online). This package is included in Decidim Awesome but the i18n translations are not. By default they are dynamically downloaded from the CDN https://cdn.jsdelivr.net/npm/formbuilder-languages@1.1.0/. +If you wish to provide an alternative place for those files, you can configure the variable `form_builder_langs_location` in an initializer: + +```ruby +# config/initializers/awesome_defaults.rb + +# A URL where to obtain the translations for the FormBuilder component +# you can a custom place if you are worried about the CDN geolocation +# Download them from https://github.com/kevinchappell/formBuilder-languages + +# For instance, copy them to your /public/fb_locales/ directory and set the path here: +Decidim::DecidimAwesome.configure do |config| + config.form_builder_langs_location = "/fb_locales/" +end +``` + +##### 11.1. GraphQL types for custom fields + +#### 11.1. GraphQL types for custom fields + +Custom fields are displayed in the GaphQL API according to their definition in a formatted array of objects in the attribute `bodyFields`. + +A query to extract this information could look like this (see that the original `body` is also available): + +```graphql +{ + component(id: 999) { + ... on Proposals { + proposals { + edges { + node { + id + bodyFields { + locales + translation(locale: "en") + translations { + locale + fields + machineTranslated + } + } + body { + locales + translations { + locale + text + machineTranslated + } + } + } + } + } + } + } +} +``` + +You can then use this custom type in your GraphQL queries and mutations to handle the custom fields in proposals. + + +##### 11.2. Private Custom fields + +Similar to the custom fields feature, but only admins can see the content of the fields. This is useful for adding metadata to proposals that should not be visible to the public (such as contact data). +Data is stored encrypted in the database. + +![Private Custom fields screenshot](examples/private_custom_fields.png) + +#### 12. Custom Redirections (or URL shortener feature) Admins can create custom paths that redirect to other places. Destinations can be internal absolute paths or external sites. There's also possible to choose to sanitize (ie: remove) any query string or to maintain it (so you can decide to use). -For instance you can create a redirection like +For instance you can create a redirection like * `/take-me-somewhere` => `/processes/canary-islands` @@ -141,7 +202,7 @@ Using a link with a query string (ie: `/take-me-somewhere?locale=es`) that will ![Custom redirections screenshot](examples/custom-redirections.png) -#### 14. Custom validation rules for title and body in proposals +#### 13. Custom validation rules for title and body in proposals Configure as you wish how the fields "title" and "body" are validated in proposals creation. @@ -154,7 +215,7 @@ Rules available: ![Custom validations](examples/custom_validations.png) -#### 15. Admin accountability +#### 14. Admin accountability This feature allows you to list all the users that are, or have been at any point in time, admins, valuators, user managers or any other role in Decidim. Including global admin roles or private admins of a particular participatory space. @@ -162,7 +223,7 @@ Results can be filtered by role and by time range and also exported as CSV or ot ![Admin accountability](examples/admin_accountability.png) -#### 16. Additional proposal sortings +#### 15. Additional proposal sortings ![Proposal sorting](examples/proposal_sorting.png) ![Proposal sorting admin](examples/proposal_sorting-admin.png) @@ -191,7 +252,7 @@ Decidim::DecidimAwesome.configure do |config| end ``` -#### 17. Weighted voting +#### 16. Weighted voting This feature allows you to configure a proposals component to use a weighted voting system. This means that each vote can have a different weight and the result of the vote is calculated as the sum of all the weights. @@ -214,7 +275,7 @@ if Decidim::DecidimAwesome.enabled?(:weighted_proposal_voting) voting.show_vote_button_view = "decidim/decidim_awesome/voting/no_admins_vote/show_vote_button" voting.show_votes_count_view = "decidim/decidim_awesome/voting/no_admins_vote/show_votes_count" # voting.show_votes_count_view = "" # hide votes count if needed - voting.proposal_m_cell_footer = "decidim/decidim_awesome/voting/no_admins_vote/proposal_m_cell_footer" + voting.proposal_metadata_cell = "decidim/decidim_awesome/voting/proposal_metadata" # define a weight validator (optional, by default all weights are valid) voting.weight_validator do |weight, context| # don't allow admins to vote @@ -240,11 +301,11 @@ A manifest must define a vote button view for the main proposal view, a vote cou All views are optional, if set to `nil` they will use the original ones. If set to an empty string `""` they will be hidden. -The `weight_validator` is a Proc that receives the weight value and the context with the current user and the proposal and returns true or false if the weight is valid or not. +The `weight_validator` is a `Proc` that receives the weight value and the context with the current user and the proposal and returns true or false if the weight is valid or not. **Notes for view `show_vote_button_view`** -When building a new view for the vote button ([see the original](https://github.com/decidim/decidim/blob/release/0.27-stable/decidim-proposals/app/views/decidim/proposals/proposals/_vote_button.html.erb)) is important to take into account the following situations: +When building a new view for the vote button ([see the original](https://github.com/decidim/decidim/blob/release/0.28-stable/decidim-proposals/app/views/decidim/proposals/proposals/_vote_button.html.erb)) is important to take into account the following situations: - If there's a `current_user` logged in - If votes are blocked `if current_settings.votes_blocked?` @@ -252,7 +313,7 @@ When building a new view for the vote button ([see the original](https://github. - If maximum votes have already reached `if proposal.maximum_votes_reached?` - If the proposal can accumulate supports beyond maximum `if proposal.can_accumulate_supports_beyond_threshold` - If the current component allows the user to participate `if current_component.participatory_space.can_participate?(current_user)` -- Note that the [original view](https://github.com/decidim/decidim/blob/release/0.27-stable/decidim-proposals/app/views/decidim/proposals/proposals/_vote_button.html.erb) is overridden only inside the tag `
`. You only need to substitute the part inside. +- Note that the [original view](https://github.com/decidim/decidim/blob/release/0.28-stable/decidim-proposals/app/views/decidim/proposals/proposals/_vote_button.html.erb) is overridden only inside the tag `
`. You only need to substitute the part inside. To cast a vote a `POST` action is needed with the parameters `proposal_id`, `from_proposals_list` and `weight`. The route where to send the vote can be constructed such as: @@ -268,13 +329,35 @@ This view must implement the number of votes already cast. It requires an HTML t You can also completely hide this view (using `voting.show_votes_count_view = ""` in the manifest declaration). This is useful if you are using the same `show_vote_button_view` to also display the total counters (or your implementation does not use that). -**Notes for view `proposal_m_cell_footer`** +**Notes for cell `voting.proposal_metadata_cell`** + +This is the Decidim cell used to provide the metadata that is rendered at the bottom of a proposal card. If empty, defaults to [ProposalMetadataCell](https://github.com/decidim/decidim/blob/release/0.28-stable/decidim-proposals/app/cells/decidim/proposals/proposal_metadata_cell.rb), **wich does not renders the votes**. + +What this cell must do is to provide an array of items to render as part of the cell footer. Check the example used at the [voting cards implementation](app/cells/decidim/decidim_awesome/voting/proposal_metadata_cell.rb) for reference. -This view is used by the proposal cell in lists. It must implement the vote button and the vote count. The vote button must be a link with the same characteristics as the one explained above for the `show_vote_button_view` (typically you can just render the same view using `<%= render partial: my/path/to/view, { locals: model: proposal, from_proposals_list: true } %>`). +##### 16.1 GraphQL Types for weighted voting -Note that, it is strongly recommended to add and HTML tag element with the id `proposal-<%= proposal.id %>-votes-count` so the Ajax vote re-loader can work. Even if you don't use (in this case use a `style="display:none"` attribute), this is because the Ajax reloader always look for this element and throw JavaScript errors if not. +When a weighed voting mechanism is selected, the GraphQL API will show those weights separated in each proposal. +The attribute that holds this information is `vote_weights`, a query example could look like this: + +```graphql +{ + component(id: 999) { + ... on Proposals { + proposals { + edges { + node { + id + voteWeights + } + } + } + } + } +} +``` -#### 18. Limiting amendments in proposals +#### 17. Limiting amendments in proposals By default, when proposals can be amended, any number of amendments can be created. @@ -285,6 +368,17 @@ This option is disable by default, must be enabled in the component's configurat ![Limiting amendments](examples/limit_amendments.png) +#### 18. Maintenance tools + +The awesome admin provides with some maintenance tools (more to come in the future); + +##### 18.1 Old private data removal + +These tools are designed to help remove old data as required by laws such as GDPR, particularly in relation to private custom fields. +This menu will show if there's any data older than 6 months (configurable) and will let admins remove it component by component. + +![Private data](examples/private_data.png) + #### To be continued... We're not done! Please check the [issues](/decidim-ice/decidim-module-decidim_awesome/issues) (and participate) to see what's on our mind @@ -303,11 +397,22 @@ And then execute: ```bash bundle -bundle exec rails decidim_decidim_awesome:install:migrations -bundle exec rails decidim:upgrade -bundle exec rails db:migrate +bin/rails decidim:upgrade +bin/rails db:migrate ``` +Go to `yourdomain/admin/decidim_awesome` and start tweaking things! + +> **EXPERTS ONLY** +> +> Under the hood, when running `bundle exec rails decidim:upgrade` the `decidim-decidim_awesome` gem will run the following two tasks (that can also be run manually if you consider): +> +> ```bash +> bin/rails decidim_decidim_awesome:install:migrations +> bin/rails decidim_decidim_awesome:webpacker:install +> ``` + + If you are upgrading from a version prior to 0.8, make sure to visit the URL `/admin/decidim_awesome/checks` and run image migrations for the old images: ![Check image migrations](examples/check_image_migrations.png) @@ -324,13 +429,14 @@ RAILS_ENV=production bin/rails decidim_awesome:active_storage_migrations:check_m ``` The correct version of Decidim Awesome should resolved automatically by the Bundler. -However you can force some specific version using `gem "decidim-decidim_awesome", "~> 0.10.0"` in the Gemfile. +However you can force some specific version using `gem "decidim-decidim_awesome", "~> 0.11.0"` in the Gemfile. Depending on your Decidim version, choose the corresponding Awesome version to ensure compatibility: | Awesome version | Compatible Decidim versions | |---|---| -| 0.10.0 | >= 0.26.7, >= 0.27.3 | +| 0.11.x | 0.28.x | +| 0.10.x | >= 0.26.7, >= 0.27.x | | 0.9.2 | >= 0.26.7, >= 0.27.3 | | 0.9.x | 0.26.x, 0.27.x | | 0.8.x | 0.25.x, 0.26.x | @@ -338,10 +444,11 @@ Depending on your Decidim version, choose the corresponding Awesome version to e | 0.6.x | 0.22.x, 0.23.x | | 0.5.x | 0.21.x, 0.22.x | -> *Heads up!* +> *Heads up!* +> * version 0.11.0 is only compatible with Decidim v0.28 as a major redesign makes backward compatibility impractical. > * version 0.10.0 requires database migrations! Don't forget the migrations step when updating. > * version 0.8.0 removes CSS Themes for tenants. If you have been using them you will have to manually migrate them to custom styles. -> * version 0.8.0 uses ActiveStorage, same as Decidim 0.25. 2 new rake task have been introduced to facilitate the migration: `bin/rails decidim_awesome:active_storage_migrations:check_migration_from_carrierwave` and +> * version 0.8.0 uses ActiveStorage, same as Decidim 0.25. 2 new rake task have been introduced to facilitate the migration: `bin/rails decidim_awesome:active_storage_migrations:check_migration_from_carrierwave` and `bin/rails decidim_awesome:active_storage_migrations:migrate_from_carrierwave` > * version 0.7.1 requires database migrations! Don't forget the migrations step when updating. @@ -360,13 +467,10 @@ In order to personalize default values, create an initializer such as: # Change some variables defaults Decidim::DecidimAwesome.configure do |config| # Enabled by default to all scopes, admins can still limit it's scope - config.allow_images_in_full_editor = true + config.allow_images_in_editors = true # Disabled by default to all scopes, admins can enable it and limit it's scope - config.allow_images_in_small_editor = false - - # De-activated, admins don't even see it as an option - config.use_markdown_editor = :disabled + config.allow_videos_in_editors = false # Disable scoped admins config.scoped_admins = :disabled @@ -458,31 +562,34 @@ DATABASE_USERNAME= DATABASE_PASSWORD= bundle exec rake test_ DATABASE_USERNAME= DATABASE_PASSWORD= bundle exec rspec ``` +> Note: the following is not currently applicable as version v0.11 is only compatible with version Decidim v0.28 +> Is left here for future reference + However, this project works with different versions of Decidim. In order to test them all, we maintain two different Gemfiles: `Gemfile` and `Gemfile.legacy`. The first one is used for development and testing the latest Decidim version supported, the second one is used for testing against the old Decidim version. You can run run tests against the legacy Decidim versions by using: ```bash -export DATABASE_USERNAME= -export DATABASE_PASSWORD= -RBENV_VERSION=2.7.6 BUNDLE_GEMFILE=Gemfile.legacy bundle -RBENV_VERSION=2.7.6 BUNDLE_GEMFILE=Gemfile.legacy bundle exec rake test_app -RBENV_VERSION=2.7.6 BUNDLE_GEMFILE=Gemfile.legacy bundle exec rspec +export DATABASE_USERNAME= +export DATABASE_PASSWORD= +RBENV_VERSION=3.1.1 BUNDLE_GEMFILE=Gemfile.legacy bundle +RBENV_VERSION=3.1.1 BUNDLE_GEMFILE=Gemfile.legacy bundle exec rake test_app +RBENV_VERSION=3.1.1 BUNDLE_GEMFILE=Gemfile.legacy bundle exec rspec ``` -For convenience, you can use the scripts `bin/rspec` and `bin/rspec-legacy` to run tests against one or the other version: +For convenience, you can use the scripts `bin/test` and `bin/test-legacy` to run tests against one or the other version: ```bash -bin/rspec spec/ -bin/rspec-legacy spec/ +bin/test spec/ +bin/test-legacy spec/ ``` - Rbenv is required for this script to work. > **NOTE:** Remember to reset the database when changing between tests: > ```bash -> bin/rspec --reset -> bin/rspec-legacy --reset +> bin/test --reset +> bin/test-legacy --reset > ``` @@ -517,4 +624,4 @@ This engine is distributed under the GNU AFFERO GENERAL PUBLIC LICENSE. ## Credits -This plugin maintainted by ![PokeCode](app/packs/images/decidim/decidim_awesome/pokecode-logo.png) +This plugin maintainted by [![PokeCode](app/packs/images/decidim/decidim_awesome/pokecode-logo.png) PokeCode](https://pokecode.net/) diff --git a/app/cells/concerns/decidim/decidim_awesome/global_menu_cell_override.rb b/app/cells/concerns/decidim/decidim_awesome/global_menu_cell_override.rb new file mode 100644 index 000000000..66b3256ee --- /dev/null +++ b/app/cells/concerns/decidim/decidim_awesome/global_menu_cell_override.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Decidim + module DecidimAwesome + module GlobalMenuCellOverride + extend ActiveSupport::Concern + + included do + def cache_hash + [ + "decidim/content_blocks/global_menu", + current_organization.cache_key_with_version, + I18n.locale, + awesome_config[:home_content_block_menu].to_s + ].join(Decidim.cache_key_separator) + end + end + end + end +end diff --git a/app/cells/concerns/decidim/decidim_awesome/proposal_m_cell_override.rb b/app/cells/concerns/decidim/decidim_awesome/proposal_l_cell_override.rb similarity index 76% rename from app/cells/concerns/decidim/decidim_awesome/proposal_m_cell_override.rb rename to app/cells/concerns/decidim/decidim_awesome/proposal_l_cell_override.rb index 5e1e987fd..df9325997 100644 --- a/app/cells/concerns/decidim/decidim_awesome/proposal_m_cell_override.rb +++ b/app/cells/concerns/decidim/decidim_awesome/proposal_l_cell_override.rb @@ -2,17 +2,24 @@ module Decidim module DecidimAwesome - module ProposalMCellOverride + module ProposalLCellOverride extend ActiveSupport::Concern included do + private + + def metadata_cell + awesome_voting_manifest_for(resource&.component)&.proposal_metadata_cell.presence || "decidim/proposals/proposal_metadata" + end + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/PerceivedComplexity def cache_hash hash = [] hash << I18n.locale.to_s hash << model.cache_key_with_version hash << model.proposal_votes_count - hash << model.extra_fields&.vote_weight_totals + hash << model.extra_fields&.reload&.vote_weight_totals hash << model.endorsements_count hash << model.comments_count hash << Digest::MD5.hexdigest(model.component.cache_key_with_version) @@ -26,12 +33,11 @@ def cache_hash hash << Digest::MD5.hexdigest(model.authors.map(&:cache_key_with_version).to_s) hash << (model.must_render_translation?(model.organization) ? 1 : 0) if model.respond_to?(:must_render_translation?) hash << model.component.participatory_space.active_step.id if model.component.participatory_space.try(:active_step) - hash << has_footer? - hash << has_actions? hash.join(Decidim.cache_key_separator) end # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/PerceivedComplexity end end end diff --git a/app/cells/decidim/decidim_awesome/content_blocks/map/show.erb b/app/cells/decidim/decidim_awesome/content_blocks/map/show.erb index a273df5cc..ed493f3e0 100644 --- a/app/cells/decidim/decidim_awesome/content_blocks/map/show.erb +++ b/app/cells/decidim/decidim_awesome/content_blocks/map/show.erb @@ -1,14 +1,7 @@ -
-
+
+ <%= content_tag("h3", section_title) if section_title.present? %> - <%= section_title %> - -
-
- <%= awesome_map_for global_map_components do %> - <%= render partial: "decidim/decidim_awesome/map_component/map/map_template.html", locals: { categories: all_categories, map_height: model.settings.map_height } %> - <% end %> -
-
-
+ <%= awesome_map_for global_map_components do %> + <%= render partial: "decidim/decidim_awesome/map_component/map/map_template.html", locals: { categories: all_categories, map_height: model.settings.map_height } %> + <% end %>
diff --git a/app/cells/decidim/decidim_awesome/voting/proposal_metadata_cell.rb b/app/cells/decidim/decidim_awesome/voting/proposal_metadata_cell.rb new file mode 100644 index 000000000..f5731e724 --- /dev/null +++ b/app/cells/decidim/decidim_awesome/voting/proposal_metadata_cell.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module Decidim + module DecidimAwesome + module Voting + # This cell renders metadata for an instance of a Proposal + class ProposalMetadataCell < ::Decidim::Proposals::ProposalMetadataCell + private + + def proposal_items + [coauthors_item, comments_count_item, endorsements_count_item, weight_count_item, state_item, emendation_item] + end + + def current_vote + @current_vote ||= Decidim::Proposals::ProposalVote.find_by(author: current_user, proposal: resource) + end + + def user_voted_weight + current_vote&.weight + end + + def all_weights + @all_weights ||= begin + weights = [3, 2, 1] + weights << 0 if resource.component.settings.voting_cards_show_abstain + weights.index_with do |weight| + resource.weight_count(weight) + end + end + end + + def weight_tags + @weight_tags ||= all_weights.map do |num, weight| + content_tag "span", title: resource.manifest.label_for(num), class: "voting-weight_#{num}" do + "#{t("decidim.decidim_awesome.voting.voting_cards.weights.weight_#{num}_short")} #{weight}" + end.html_safe + end + end + + def weight_count_item + return unless resource.respond_to?(:weight_count) + return if resource.component.current_settings.votes_hidden? + return if resource&.rejected? || resource&.withdrawn? + + { + text: weight_tags.join(" | ").html_safe, + icon: "#{user_voted_weight ? "checkbox" : "close"}-circle-line", + data_attributes: all_weights.transform_keys { |num| "weight-#{num}" } + } + end + end + end + end +end diff --git a/app/cells/decidim/decidim_awesome/voting/voting_cards_counter/show.erb b/app/cells/decidim/decidim_awesome/voting/voting_cards_counter/show.erb deleted file mode 100644 index 4b8ec1e93..000000000 --- a/app/cells/decidim/decidim_awesome/voting/voting_cards_counter/show.erb +++ /dev/null @@ -1,15 +0,0 @@ -<% unless proposal.rejected? || proposal.withdrawn? %> - <% unless current_settings.votes_hidden? %> - - <%= t("decidim.decidim_awesome.voting.voting_cards.weights.weight_3_short") %> <%= model.weight_count(3) %> | - <%= t("decidim.decidim_awesome.voting.voting_cards.weights.weight_2_short") %> <%= model.weight_count(2) %> | - <%= t("decidim.decidim_awesome.voting.voting_cards.weights.weight_1_short") %> <%= model.weight_count(1) %> - <% if current_component.settings.voting_cards_show_abstain %> - | <%= t("decidim.decidim_awesome.voting.voting_cards.weights.weight_0_short") %> <%= model.weight_count(0) %> - <% end %> - - <% end %> -
- <%= render "vote_button" %> -
-<% end %> diff --git a/app/cells/decidim/decidim_awesome/voting/voting_cards_counter/vote_button.erb b/app/cells/decidim/decidim_awesome/voting/voting_cards_counter/vote_button.erb deleted file mode 100644 index 517fd4c52..000000000 --- a/app/cells/decidim/decidim_awesome/voting/voting_cards_counter/vote_button.erb +++ /dev/null @@ -1,15 +0,0 @@ -<%= link_to resource_path, - class: "button #{vote_btn_class} small button--sc#{" disabled" if current_settings.votes_blocked?}", - title: t("decidim.decidim_awesome.voting.voting_cards.vote_button") do %> - <% if user_voted_weight %> - <%= icon "actions", class: "icon" %> <%= t("decidim.decidim_awesome.voting.voting_cards.voted") %> - <% elsif proposal.maximum_votes_reached? && !proposal.can_accumulate_supports_beyond_threshold && current_component.participatory_space.can_participate?(current_user) %> - <%= t("decidim.proposals.proposals.vote_button.maximum_votes_reached") %> - <% elsif vote_limit_enabled? && remaining_votes_count_for(current_user) <= 0 %> - <%= t("decidim.proposals.proposals.vote_button.no_votes_remaining") %> - <% elsif current_settings.votes_blocked? || !current_component.participatory_space.can_participate?(current_user) %> - <%= t("decidim.proposals.proposals.vote_button.votes_blocked") %> - <% else %> - <%= t("decidim.decidim_awesome.voting.voting_cards.vote_button") %> - <% end %> -<% end %> diff --git a/app/cells/decidim/decidim_awesome/voting/voting_cards_counter_cell.rb b/app/cells/decidim/decidim_awesome/voting/voting_cards_counter_cell.rb deleted file mode 100644 index 3c9b0f5ee..000000000 --- a/app/cells/decidim/decidim_awesome/voting/voting_cards_counter_cell.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -module Decidim - module DecidimAwesome - module Voting - class VotingCardsCounterCell < VotingCardsBaseCell - def show - render :show - end - - def resource_path - resource_locator(model).path - end - - def vote_btn_class - user_voted_weight ? "weight_#{user_voted_weight.to_i}" : "hollow" - end - end - end - end -end diff --git a/app/cells/decidim/decidim_awesome/voting/voting_cards_proposal/modal.erb b/app/cells/decidim/decidim_awesome/voting/voting_cards_proposal/modal.erb new file mode 100644 index 000000000..5f784d9de --- /dev/null +++ b/app/cells/decidim/decidim_awesome/voting/voting_cards_proposal/modal.erb @@ -0,0 +1,18 @@ +<%= decidim_modal id: "voting-cards-modal-help", class:"reveal vote_proposal_modal voting-voting_cards" do %> +
+
+
<%= vote_instructions %>
+
+
+
+
+ <%= check_box_tag "voting_cards-skip_help", current_component.id, false %> + <%= label_tag "voting_cards-skip_help", t("decidim.decidim_awesome.voting.voting_cards.modal.skip") %> +
+
+
+
+ + +
+<% end %> diff --git a/app/cells/decidim/decidim_awesome/voting/voting_cards_proposal/show.erb b/app/cells/decidim/decidim_awesome/voting/voting_cards_proposal/show.erb index 0daf553f2..6297177c7 100644 --- a/app/cells/decidim/decidim_awesome/voting/voting_cards_proposal/show.erb +++ b/app/cells/decidim/decidim_awesome/voting/voting_cards_proposal/show.erb @@ -1,35 +1,40 @@ -

<%= title %>

+
+

<%= title %>

-
- <%= vote_block_for(proposal, 3) %> - <%= vote_block_for(proposal, 2) %> - <%= vote_block_for(proposal, 1) %> -
+
+ <%= vote_block_for(proposal, 3) %> + <%= vote_block_for(proposal, 2) %> + <%= vote_block_for(proposal, 1) %> +
+ + <% if component_settings.voting_cards_show_abstain? %> + <%= action_authorized_link_to :vote, + voted_for?(0) ? t("decidim.decidim_awesome.voting.voting_cards.abstained") : proposal.manifest.label_for(0), + proposal_vote_path(0), + link_options(0).merge({ + title: t("decidim.decidim_awesome.voting.voting_cards.voting_for", proposal: sanitized_title, type: proposal.manifest.label_for(0)), + class: "button button__sm button__transparent-secondary mb-4 vote-action abstain-button #{classes_for(0)}" + }) %> + <% end %> -<% if component_settings.voting_cards_show_abstain? %> - <%= action_authorized_link_to :vote, - voted_for?(0) ? t("decidim.decidim_awesome.voting.voting_cards.abstained") : proposal.manifest.label_for(0), - proposal_vote_path(0), - link_options(0).merge({ - title: t("decidim.decidim_awesome.voting.voting_cards.voting_for", proposal: sanitized_title, type: proposal.manifest.label_for(0)), - class: "button expanded vote-action abstain-button small #{classes_for(0)}" - }) %> -<% end %> + <% if voted_for_any? && !current_settings.votes_blocked? %> +

+ <%= action_authorized_link_to :unvote, + t("decidim.decidim_awesome.voting.voting_cards.change_vote"), + proposal_vote_path(current_vote&.weight), + remote: true, + method: :delete, + id: "change-vote", + class: "change-vote-button vote-action font-semibold" %> +

+ <% elsif proposal.maximum_votes_reached? && !proposal.can_accumulate_supports_beyond_threshold && current_component.participatory_space.can_participate?(current_user) %> +

<%= t("decidim.proposals.proposals.vote_button.maximum_votes_reached") %>

+ <% elsif vote_limit_enabled? && remaining_votes_count_for(current_user) <= 0 %> +

<%= t("decidim.proposals.proposals.vote_button.no_votes_remaining") %>

+ <% elsif current_settings.votes_blocked? || !current_component.participatory_space.can_participate?(current_user) %> +

<%= t("decidim.proposals.proposals.vote_button.votes_blocked") %>

+ <% end %> + +
-<% if voted_for_any? && !current_settings.votes_blocked? %> -

- <%= action_authorized_link_to :unvote, - t("decidim.decidim_awesome.voting.voting_cards.change_vote"), - proposal_vote_path(current_vote&.weight), - remote: true, - method: :delete, - id: "change-vote", - class: "change-vote-button vote-action" %> -

-<% elsif proposal.maximum_votes_reached? && !proposal.can_accumulate_supports_beyond_threshold && current_component.participatory_space.can_participate?(current_user) %> -

<%= t("decidim.proposals.proposals.vote_button.maximum_votes_reached") %>

-<% elsif vote_limit_enabled? && remaining_votes_count_for(current_user) <= 0 %> -

<%= t("decidim.proposals.proposals.vote_button.no_votes_remaining") %>

-<% elsif current_settings.votes_blocked? || !current_component.participatory_space.can_participate?(current_user) %> -

<%= t("decidim.proposals.proposals.vote_button.votes_blocked") %>

-<% end %> +<%= render :modal if current_component.settings.voting_cards_show_modal_help %> diff --git a/app/cells/decidim/decidim_awesome/voting/voting_cards_proposal_cell.rb b/app/cells/decidim/decidim_awesome/voting/voting_cards_proposal_cell.rb index 6b71f21b7..5779442e9 100644 --- a/app/cells/decidim/decidim_awesome/voting/voting_cards_proposal_cell.rb +++ b/app/cells/decidim/decidim_awesome/voting/voting_cards_proposal_cell.rb @@ -12,11 +12,16 @@ def show def vote_block_for(proposal, weight) render partial: "vote_block", locals: { - proposal: proposal, - weight: weight + proposal:, + weight: } end + def vote_instructions + translated_attribute(current_component.settings.voting_cards_instructions).presence || t("decidim.decidim_awesome.voting.voting_cards.default_instructions_html", + organization: current_organization.name) + end + def proposal_votes(weight) model.weight_count(weight) end @@ -30,7 +35,7 @@ def from_proposals_list end def proposal_vote_path(weight) - proposal_proposal_vote_path(proposal_id: proposal.id, from_proposals_list: from_proposals_list, weight: weight) + proposal_proposal_vote_path(proposal_id: proposal.id, from_proposals_list:, weight:) end def link_options(weight) @@ -68,7 +73,7 @@ def disabled? return true end - return true if vote_limit_enabled? && remaining_votes_count_for(current_user) <= 0 + true if vote_limit_enabled? && remaining_votes_count_for(current_user) <= 0 end def voted_for_any? diff --git a/app/cells/decidim/decidim_awesome/voting/voting_cards_proposal_modal/show.erb b/app/cells/decidim/decidim_awesome/voting/voting_cards_proposal_modal/show.erb deleted file mode 100644 index 1814fc874..000000000 --- a/app/cells/decidim/decidim_awesome/voting/voting_cards_proposal_modal/show.erb +++ /dev/null @@ -1,16 +0,0 @@ - - diff --git a/app/cells/decidim/decidim_awesome/voting/voting_cards_proposal_modal_cell.rb b/app/cells/decidim/decidim_awesome/voting/voting_cards_proposal_modal_cell.rb deleted file mode 100644 index 642f1190e..000000000 --- a/app/cells/decidim/decidim_awesome/voting/voting_cards_proposal_modal_cell.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -module Decidim - module DecidimAwesome - module Voting - class VotingCardsProposalModalCell < VotingCardsBaseCell - include Decidim::Proposals::Engine.routes.url_helpers - - def show - render :show - end - - def vote_instructions - translated_attribute(current_component.settings.voting_cards_instructions).presence || t("decidim.decidim_awesome.voting.voting_cards.default_instructions_html", - organization: current_organization.name) - end - end - end - end -end diff --git a/app/cells/decidim/proposals/proposal_m/footer.erb b/app/cells/decidim/proposals/proposal_m/footer.erb deleted file mode 100644 index 80eb55d5f..000000000 --- a/app/cells/decidim/proposals/proposal_m/footer.erb +++ /dev/null @@ -1,13 +0,0 @@ -<%# - A hack for the Trailblazer Cell component, here we obtain the "parent" view - https://github.com/trailblazer/cells/blob/master/lib/cell/view_model.rb#L102 - renders parent view by removing the awesome override from the paths -%> -<% original = normalize_options(:footer) %> -<% original[:prefixes] = original[:prefixes].filter {|path| path != Decidim::DecidimAwesome::Engine.root.join("app/cells/decidim/proposals/proposal_m").to_s } %> - -<% if view = awesome_voting_manifest_for(current_component)&.proposal_m_cell_footer %> - <%= render partial: view if view.present? %> -<% else %> - <%= render original %> -<% end %> diff --git a/app/commands/concerns/decidim/decidim_awesome/admin/needs_constraint_helpers.rb b/app/commands/concerns/decidim/decidim_awesome/admin/needs_constraint_helpers.rb index 87e19a8b5..7ffce60be 100644 --- a/app/commands/concerns/decidim/decidim_awesome/admin/needs_constraint_helpers.rb +++ b/app/commands/concerns/decidim/decidim_awesome/admin/needs_constraint_helpers.rb @@ -11,7 +11,7 @@ def create_constraint_never(var) subconfig = AwesomeConfig.find_or_initialize_by(var: "#{var}_#{@ident}", organization: @organization) @constraint = ConfigConstraint.create!( awesome_config: subconfig, - settings: settings + settings: ) end @@ -20,7 +20,7 @@ def constraint_can_be_destroyed?(constraint) return true if constraint.awesome_config.constraints.count > 1 case constraint.awesome_config.var.to_s - when /^proposal_custom_field/ + when /^proposal_(private_)?custom_field/ false else true diff --git a/app/commands/concerns/decidim/decidim_awesome/proposals/admin/update_proposal_override.rb b/app/commands/concerns/decidim/decidim_awesome/proposals/admin/update_proposal_override.rb new file mode 100644 index 000000000..2aa2639bd --- /dev/null +++ b/app/commands/concerns/decidim/decidim_awesome/proposals/admin/update_proposal_override.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Decidim + module DecidimAwesome + module Proposals + module Admin + ## + # Decorates update draft and update proposal + # to avoid private field to be logged in PaperTrail. + module UpdateProposalOverride + extend ActiveSupport::Concern + + included do + private + + alias_method :decidim_original_update_proposal, :update_proposal + + def update_proposal + decidim_original_update_proposal + update_private_field! + end + + def update_private_field! + @proposal.update_private_body!(form.private_body) if form.private_body.present? + end + end + end + end + end + end +end diff --git a/app/commands/concerns/decidim/decidim_awesome/proposals/create_collaborative_draft_override.rb b/app/commands/concerns/decidim/decidim_awesome/proposals/create_collaborative_draft_override.rb new file mode 100644 index 000000000..762832664 --- /dev/null +++ b/app/commands/concerns/decidim/decidim_awesome/proposals/create_collaborative_draft_override.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Decidim + module DecidimAwesome + module Proposals + ## + # Decorate create_collaborative_draft to avoid + # private data to be in PaperTrail + module CreateCollaborativeDraftOverride + extend ActiveSupport::Concern + + included do + private + + alias_method :decidim_original_create_collaborative_draft, :create_collaborative_draft + + def create_collaborative_draft + created_draft = decidim_original_create_collaborative_draft + # Update the proposal with the private body, to + # avoid tracebility on private fields. + created_draft.update_private_body!(form.private_body) if form.private_body.present? + end + end + end + end + end +end diff --git a/app/commands/concerns/decidim/decidim_awesome/proposals/create_proposal_override.rb b/app/commands/concerns/decidim/decidim_awesome/proposals/create_proposal_override.rb new file mode 100644 index 000000000..eadff000f --- /dev/null +++ b/app/commands/concerns/decidim/decidim_awesome/proposals/create_proposal_override.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Decidim + module DecidimAwesome + module Proposals + ## + # Decorate create_proposal to avoid + # private data to be in PaperTrail + module CreateProposalOverride + extend ActiveSupport::Concern + + included do + private + + alias_method :decidim_original_create_proposal, :create_proposal + + def create_proposal + created_proposal = decidim_original_create_proposal + # Update the proposal with the private body, to + # avoid tracebility on private fields. + created_proposal.update_private_body!(form.private_body) if form.private_body.present? + end + end + end + end + end +end diff --git a/app/commands/concerns/decidim/decidim_awesome/proposals/update_collaborative_draft_override.rb b/app/commands/concerns/decidim/decidim_awesome/proposals/update_collaborative_draft_override.rb new file mode 100644 index 000000000..1a16231fd --- /dev/null +++ b/app/commands/concerns/decidim/decidim_awesome/proposals/update_collaborative_draft_override.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Decidim + module DecidimAwesome + module Proposals + ## + # Decorates update draft and update proposal + # to avoid private field to be logged in PaperTrail. + module UpdateCollaborativeDraftOverride + extend ActiveSupport::Concern + + included do + private + + alias_method :decidim_original_update_collaborative_draft, :update_collaborative_draft + + def update_collaborative_draft + decidim_original_update_collaborative_draft + # Update the proposal with the private body, to + # avoid tracebility on private fields. + @collaborative_draft.update_private_body!(form.private_body) if form.private_body.present? + end + end + end + end + end +end diff --git a/app/commands/concerns/decidim/decidim_awesome/proposals/update_proposal_override.rb b/app/commands/concerns/decidim/decidim_awesome/proposals/update_proposal_override.rb new file mode 100644 index 000000000..f1d0ffd18 --- /dev/null +++ b/app/commands/concerns/decidim/decidim_awesome/proposals/update_proposal_override.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Decidim + module DecidimAwesome + module Proposals + ## + # Decorates update draft and update proposal + # to avoid private field to be logged in PaperTrail. + module UpdateProposalOverride + extend ActiveSupport::Concern + include Admin::UpdateProposalOverride + + included do + private + + alias_method :decidim_original_update_draft, :update_draft + + def update_proposal + decidim_original_update_proposal + update_private_field! + end + end + end + end + end +end diff --git a/app/commands/decidim/decidim_awesome/admin/create_constraint.rb b/app/commands/decidim/decidim_awesome/admin/create_constraint.rb index 6d58a1bcc..0ca2d59e5 100644 --- a/app/commands/decidim/decidim_awesome/admin/create_constraint.rb +++ b/app/commands/decidim/decidim_awesome/admin/create_constraint.rb @@ -18,7 +18,7 @@ def initialize(form) # # Returns nothing. def call - return broadcast(:invalid) if form.invalid? + return broadcast(:invalid, form.errors.full_messages.join(". ")) if form.invalid? return broadcast(:invalid) if attributes.blank? begin diff --git a/app/commands/decidim/decidim_awesome/admin/create_proposal_custom_field.rb b/app/commands/decidim/decidim_awesome/admin/create_proposal_custom_field.rb index b0a7264b7..1dcdba039 100644 --- a/app/commands/decidim/decidim_awesome/admin/create_proposal_custom_field.rb +++ b/app/commands/decidim/decidim_awesome/admin/create_proposal_custom_field.rb @@ -8,9 +8,10 @@ class CreateProposalCustomField < Command # Public: Initializes the command. # - def initialize(organization) + def initialize(organization, config_var = :proposal_custom_fields) @organization = organization @ident = rand(36**8).to_s(36) + @config_var = config_var end # Executes the command. Broadcasts these events: @@ -20,13 +21,13 @@ def initialize(organization) # # Returns nothing. def call - fields = AwesomeConfig.find_or_initialize_by(var: :proposal_custom_fields, organization: @organization) + fields = AwesomeConfig.find_or_initialize_by(var: @config_var, organization: @organization) fields.value = {} unless fields.value.is_a? Hash # TODO: prevent (unlikely) colisions with exisiting values fields.value[@ident] = default_definition fields.save! - create_constraint_never(:proposal_custom_field) + create_constraint_never(@config_var == :proposal_custom_fields ? :proposal_custom_field : :proposal_private_custom_field) broadcast(:ok, @ident) rescue StandardError => e diff --git a/app/commands/decidim/decidim_awesome/admin/destroy_custom_redirect.rb b/app/commands/decidim/decidim_awesome/admin/destroy_custom_redirect.rb index b56945f20..7ebc62478 100644 --- a/app/commands/decidim/decidim_awesome/admin/destroy_custom_redirect.rb +++ b/app/commands/decidim/decidim_awesome/admin/destroy_custom_redirect.rb @@ -11,7 +11,7 @@ class DestroyCustomRedirect < Command def initialize(item, organization) @item = item @organization = organization - @redirections = AwesomeConfig.find_by(var: :custom_redirects, organization: organization) + @redirections = AwesomeConfig.find_by(var: :custom_redirects, organization:) end # Executes the command. Broadcasts these events: diff --git a/app/commands/decidim/decidim_awesome/admin/destroy_menu_hack.rb b/app/commands/decidim/decidim_awesome/admin/destroy_menu_hack.rb index 4d57a29fc..49db867cb 100644 --- a/app/commands/decidim/decidim_awesome/admin/destroy_menu_hack.rb +++ b/app/commands/decidim/decidim_awesome/admin/destroy_menu_hack.rb @@ -11,7 +11,7 @@ class DestroyMenuHack < Command def initialize(item, menu_name, organization) @item = item @organization = organization - @menu = AwesomeConfig.find_by(var: menu_name, organization: organization) + @menu = AwesomeConfig.find_by(var: menu_name, organization:) end # Executes the command. Broadcasts these events: diff --git a/app/commands/decidim/decidim_awesome/admin/destroy_proposal_custom_field.rb b/app/commands/decidim/decidim_awesome/admin/destroy_proposal_custom_field.rb index 8f32fe5e1..654fb18f4 100644 --- a/app/commands/decidim/decidim_awesome/admin/destroy_proposal_custom_field.rb +++ b/app/commands/decidim/decidim_awesome/admin/destroy_proposal_custom_field.rb @@ -8,9 +8,10 @@ class DestroyProposalCustomField < Command # # key - the key to destroy inise proposal_custom_fields # organization - def initialize(key, organization) + def initialize(key, organization, config_var = :proposal_custom_fields) @key = key @organization = organization + @config_var = config_var end # Executes the command. Broadcasts these events: @@ -20,14 +21,16 @@ def initialize(key, organization) # # Returns nothing. def call - fields = AwesomeConfig.find_by(var: :proposal_custom_fields, organization: @organization) + fields = AwesomeConfig.find_by(var: @config_var, organization: @organization) return broadcast(:invalid, "Not a hash") unless fields&.value.is_a? Hash return broadcast(:invalid, "#{key} key invalid") unless fields.value.has_key?(@key) fields.value.except!(@key) fields.save! + # remove constrains associated (a new config var is generated automatically, by removing it, it will trigger destroy on dependents) - constraint = AwesomeConfig.find_by(var: "proposal_custom_field_#{@key}", organization: @organization) + constraint = @config_var == :proposal_custom_fields ? :proposal_custom_field : :proposal_private_custom_field + constraint = AwesomeConfig.find_by(var: "#{constraint}_#{@key}", organization: @organization) constraint.destroy! if constraint.present? broadcast(:ok, @key) diff --git a/app/commands/decidim/decidim_awesome/admin/rename_scope_label.rb b/app/commands/decidim/decidim_awesome/admin/rename_scope_label.rb index a4aa4f7f4..1be255820 100644 --- a/app/commands/decidim/decidim_awesome/admin/rename_scope_label.rb +++ b/app/commands/decidim/decidim_awesome/admin/rename_scope_label.rb @@ -34,7 +34,7 @@ def call end end - broadcast(:ok, { scope: scope, key: @text }) + broadcast(:ok, { scope:, key: @text }) rescue StandardError => e broadcast(:invalid, e.message) end diff --git a/app/commands/decidim/decidim_awesome/admin/update_constraint.rb b/app/commands/decidim/decidim_awesome/admin/update_constraint.rb index 3f7985e9d..818623e5c 100644 --- a/app/commands/decidim/decidim_awesome/admin/update_constraint.rb +++ b/app/commands/decidim/decidim_awesome/admin/update_constraint.rb @@ -18,7 +18,7 @@ def initialize(form) # # Returns nothing. def call - return broadcast(:invalid) if form.invalid? + return broadcast(:invalid, form.errors.full_messages.join(". ")) if form.invalid? return broadcast(:invalid) if attributes.blank? begin diff --git a/app/commands/decidim/decidim_awesome/admin/update_custom_redirect.rb b/app/commands/decidim/decidim_awesome/admin/update_custom_redirect.rb index 50412539a..aef76a009 100644 --- a/app/commands/decidim/decidim_awesome/admin/update_custom_redirect.rb +++ b/app/commands/decidim/decidim_awesome/admin/update_custom_redirect.rb @@ -38,8 +38,8 @@ def call delegate :to_params, to: :form def url_exists? - return unless redirects - return unless redirects.value.is_a? Hash + return false unless redirects + return false unless redirects.value.is_a? Hash redirects.value[item.origin].present? end diff --git a/app/commands/decidim/decidim_awesome/command.rb b/app/commands/decidim/decidim_awesome/command.rb index 01ffdb77e..abf99d26b 100644 --- a/app/commands/decidim/decidim_awesome/command.rb +++ b/app/commands/decidim/decidim_awesome/command.rb @@ -2,13 +2,7 @@ module Decidim module DecidimAwesome - # maintains compatibility with v0.26 which uses Rectify - if defined? ::Decidim::Command - class Command < ::Decidim::Command - end - else - class Command < ::Rectify::Command - end + class Command < ::Decidim::Command end end end diff --git a/app/controllers/concerns/decidim/decidim_awesome/admin/maintenance_context.rb b/app/controllers/concerns/decidim/decidim_awesome/admin/maintenance_context.rb new file mode 100644 index 000000000..e2fd2abb2 --- /dev/null +++ b/app/controllers/concerns/decidim/decidim_awesome/admin/maintenance_context.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Decidim + module DecidimAwesome + module Admin + module MaintenanceContext + extend ActiveSupport::Concern + + included do + layout "decidim/decidim_awesome/admin/maintenance" + helper_method :current_view, :available_views, :present_private_data + + private + + def present_private_data(model) + PrivateDataPresenter.new(model) + end + + def current_view + return params[:id] if available_views.include?(params[:id]) + + available_views.keys.first + end + + def available_views + { + "private_data" => { + title: I18n.t("private_data", scope: "decidim.decidim_awesome.admin.menu.maintenance"), + icon: "spy-line", + path: decidim_admin_decidim_awesome.maintenance_path("private_data") + }, + "checks" => { + title: I18n.t("checks", scope: "decidim.decidim_awesome.admin.menu.maintenance"), + icon: "pulse", + path: decidim_admin_decidim_awesome.checks_maintenance_index_path + } + } + end + end + end + end + end +end diff --git a/app/controllers/concerns/decidim/decidim_awesome/admin_accountability/admin/filterable_helper.rb b/app/controllers/concerns/decidim/decidim_awesome/admin_accountability/admin/filterable_helper.rb index 3da344bd9..242f3255a 100644 --- a/app/controllers/concerns/decidim/decidim_awesome/admin_accountability/admin/filterable_helper.rb +++ b/app/controllers/concerns/decidim/decidim_awesome/admin_accountability/admin/filterable_helper.rb @@ -18,7 +18,7 @@ def applied_filters_tags(i18n_ctx) concat "#{i18n_filter_label(:admin_role_type, filterable_i18n_scope_from_ctx(i18n_ctx))}: " concat t("decidim.decidim_awesome.admin.admin_accountability.admin_roles.#{params[:admin_role_type]}", default: params[:admin_role_type]) concat icon_link_to( - "circle-x", + "close-circle-line", url_for(export_params.except(:admin_role_type)), t("decidim.admin.actions.cancel"), class: "action-icon--remove" diff --git a/app/controllers/concerns/decidim/decidim_awesome/content_security_policy.rb b/app/controllers/concerns/decidim/decidim_awesome/content_security_policy.rb new file mode 100644 index 000000000..a180f3dd3 --- /dev/null +++ b/app/controllers/concerns/decidim/decidim_awesome/content_security_policy.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Decidim + module DecidimAwesome + module ContentSecurityPolicy + extend ActiveSupport::Concern + + included do + after_action :append_awesome_csp_directives + end + + private + + def append_awesome_csp_directives + # this is necessary for custom fields + content_security_policy.append_csp_directive("font-src", "data:") if DecidimAwesome.enabled?(:proposal_custom_fields) + append_awesome_intergram_directives + end + + def append_awesome_intergram_directives + return unless DecidimAwesome.enabled?(:intergram_for_admins) || DecidimAwesome.enabled?(:intergram_for_public) + + intergram = URI.parse(DecidimAwesome.intergram_url) + if intergram.host && intergram.scheme + content_security_policy.append_csp_directive("script-src", "#{intergram.scheme}://#{intergram.host}") + content_security_policy.append_csp_directive("frame-src", "#{intergram.scheme}://#{intergram.host}") + # this script is in the intergram code, but does not look necessary to work + # content_security_policy.append_csp_directive("frame-src", "http://www.loadmill.com") + # content_security_policy.append_csp_directive("frame-src", "http://app.loadmill.com") + end + end + end + end +end diff --git a/app/controllers/concerns/decidim/decidim_awesome/not_found_redirect.rb b/app/controllers/concerns/decidim/decidim_awesome/not_found_redirect.rb index af56b3531..fa5a39833 100644 --- a/app/controllers/concerns/decidim/decidim_awesome/not_found_redirect.rb +++ b/app/controllers/concerns/decidim/decidim_awesome/not_found_redirect.rb @@ -50,7 +50,7 @@ def custom_redirects_destination(fullpath) destination = "#{destination}#{union}#{query}" end - return destination.strip if destination.present? + destination.strip if destination.present? end end end diff --git a/app/controllers/concerns/decidim/decidim_awesome/proposals/proposal_votes_controller_override.rb b/app/controllers/concerns/decidim/decidim_awesome/proposals/proposal_votes_controller_override.rb index 42802e560..3b7349f22 100644 --- a/app/controllers/concerns/decidim/decidim_awesome/proposals/proposal_votes_controller_override.rb +++ b/app/controllers/concerns/decidim/decidim_awesome/proposals/proposal_votes_controller_override.rb @@ -13,7 +13,7 @@ module ProposalVotesControllerOverride # rubocop:enable Rails/LexicallyScopedActionFilter def create - enforce_permission_to :vote, :proposal, proposal: proposal + enforce_permission_to(:vote, :proposal, proposal:) @from_proposals_list = params[:from_proposals_list] == "true" Decidim::Proposals::VoteProposal.call(proposal, current_user) do @@ -27,7 +27,7 @@ def create proposal: Decidim::Proposals::Proposal.where(component: current_component) ).map(&:proposal) - expose(proposals: proposals) + expose(proposals:) render :update_buttons_and_counters end @@ -47,7 +47,7 @@ def validate_weight end def current_vote - @current_vote ||= Decidim::Proposals::ProposalVote.find_by(author: current_user, proposal: proposal) + @current_vote ||= Decidim::Proposals::ProposalVote.find_by(author: current_user, proposal:) end def vote_manifest diff --git a/app/controllers/decidim/decidim_awesome/admin/application_controller.rb b/app/controllers/decidim/decidim_awesome/admin/application_controller.rb index 63ad2672f..f0c578e54 100644 --- a/app/controllers/decidim/decidim_awesome/admin/application_controller.rb +++ b/app/controllers/decidim/decidim_awesome/admin/application_controller.rb @@ -9,6 +9,8 @@ module Admin # Note that it inherits from `Decidim::Admin::Components::BaseController`, which # override its layout and provide all kinds of useful methods. class ApplicationController < Decidim::Admin::ApplicationController + layout "decidim/decidim_awesome/admin/application" + def permission_class_chain [::Decidim::DecidimAwesome::Admin::Permissions] + super end diff --git a/app/controllers/decidim/decidim_awesome/admin/checks_controller.rb b/app/controllers/decidim/decidim_awesome/admin/checks_controller.rb index b58592998..47f01a0f4 100644 --- a/app/controllers/decidim/decidim_awesome/admin/checks_controller.rb +++ b/app/controllers/decidim/decidim_awesome/admin/checks_controller.rb @@ -8,17 +8,17 @@ module Admin # System compatibility analyzer class ChecksController < DecidimAwesome::Admin::ApplicationController include NeedsAwesomeConfig + include MaintenanceContext + helper ConfigConstraintsHelpers helper SystemCheckerHelpers - layout "decidim/decidim_awesome/admin/application" - helper_method :head, :admin_head, :head_addons, :admin_addons def migrate_images Decidim::DecidimAwesome::MigrateLegacyImagesJob.perform_later(current_organization.id) flash[:notice] = I18n.t("image_migrations_started", scope: "decidim.decidim_awesome.admin.checks.index") - redirect_to checks_path + redirect_to checks_maintenance_index_path end private @@ -34,30 +34,34 @@ def admin_head def head_addons(part) case part when :CSS - ['<%= stylesheet_pack_tag "decidim_decidim_awesome", media: "all" %>', + ['<%= append_stylesheet_pack_tag "decidim_decidim_awesome", media: "all" %>', '<%= render(partial: "layouts/decidim/decidim_awesome/custom_styles") if awesome_custom_styles %>'].join("\n") when :JavaScript ['<%= render partial: "layouts/decidim/decidim_awesome/awesome_config" %>', - '<%= javascript_pack_tag "decidim_decidim_awesome" %>', - '<%= javascript_pack_tag "decidim_decidim_awesome_custom_fields" if awesome_proposal_custom_fields %>'].join("\n") + '<%= append_javascript_pack_tag "decidim_decidim_awesome", defer: false %>', + '<%= append_javascript_pack_tag "decidim_decidim_awesome_custom_fields" if awesome_proposal_custom_fields %>'].join("\n") end end def admin_addons(part) case part when :CSS - '<%= stylesheet_pack_tag "decidim_admin_decidim_awesome", media: "all" %>' + '<%= append_stylesheet_pack_tag "decidim_admin_decidim_awesome", media: "all" %>' when :JavaScript - ['<%= javascript_pack_tag "decidim_admin_decidim_awesome", defer: false %>', - '<%= javascript_pack_tag "decidim_decidim_awesome_custom_fields" if awesome_proposal_custom_fields %>'].join("\n") + ['<%= append_javascript_pack_tag "decidim_admin_decidim_awesome", defer: false %>', + '<%= append_javascript_pack_tag "decidim_decidim_awesome_custom_fields" if awesome_proposal_custom_fields %>'].join("\n") end end def render_template(partial) - render_to_string(partial: partial) + render_to_string(partial:) rescue ActionView::Template::Error => e flash.now[:alert] = "Partial [#{partial}] has thrown an error: #{e.message}" end + + def current_view + "checks" + end end end end diff --git a/app/controllers/decidim/decidim_awesome/admin/config_controller.rb b/app/controllers/decidim/decidim_awesome/admin/config_controller.rb index 9ea107486..fe4dfea6f 100644 --- a/app/controllers/decidim/decidim_awesome/admin/config_controller.rb +++ b/app/controllers/decidim/decidim_awesome/admin/config_controller.rb @@ -9,8 +9,6 @@ class ConfigController < DecidimAwesome::Admin::ApplicationController include ConfigConstraintsHelpers helper ConfigConstraintsHelpers - layout "decidim/decidim_awesome/admin/application" - helper_method :constraints_for, :users_for, :config_var before_action do enforce_permission_to :edit_config, configs @@ -19,7 +17,7 @@ class ConfigController < DecidimAwesome::Admin::ApplicationController def show @form = form(ConfigForm).from_params(organization_awesome_config) - redirect_to decidim_admin_decidim_awesome.checks_path unless config_var + redirect_to decidim_admin_decidim_awesome.checks_maintenance_index_path unless config_var end def update @@ -43,8 +41,13 @@ def users if (term = params[:term].to_s).present? query = current_organization.users.order(name: :asc) query = query.where("name ILIKE :term OR nickname ILIKE :term OR email ILIKE :term", term: "%#{term}%") - - render json: query.all.collect { |u| { id: u.id, text: format_user_name(u) } } + render json: query.all.collect { |u| + { + value: u.id, + text: format_user_name(u), + is_admin: u.read_attribute("admin") + } + } else render json: [] end @@ -88,14 +91,12 @@ def configs DecidimAwesome.config.keys end - # rubocop:disable Style/OpenStructUse def users_for(ids_list) - Decidim::User.where(id: ids_list).map { |user| OpenStruct.new(text: format_user_name(user), id: user.id) } + Decidim::User.where(id: ids_list).map { |user| [format_user_name(user), user.id, { data: { is_admin: user.read_attribute("admin") } }] } end - # rubocop:enable Style/OpenStructUse def format_user_name(user) - "#{user.name} (@#{user.nickname} - #{user.email})" + "#{user.name} (@#{user.nickname} - #{user.email})" end end end diff --git a/app/controllers/decidim/decidim_awesome/admin/constraints_controller.rb b/app/controllers/decidim/decidim_awesome/admin/constraints_controller.rb index 346cbd688..24de5c416 100644 --- a/app/controllers/decidim/decidim_awesome/admin/constraints_controller.rb +++ b/app/controllers/decidim/decidim_awesome/admin/constraints_controller.rb @@ -6,21 +6,22 @@ module Admin # Constraints configuration controller for config keys class ConstraintsController < DecidimAwesome::Admin::ApplicationController include NeedsAwesomeConfig + include Decidim::Headers::HttpCachingDisabler helper ConfigConstraintsHelpers layout false before_action do - enforce_permission_to :edit_config, constraint_key - end - - def new - @form = form(ConstraintForm).from_params(filtered_params, setting: current_setting) + render plain: "no permissions for #{constraint_key}" unless allowed_to? :edit_config, constraint_key end def show @form = form(ConstraintForm).from_params(constraint.settings.merge(filtered_params)) end + def new + @form = form(ConstraintForm).from_params(filtered_params, setting: current_setting) + end + def create @form = form(ConstraintForm).from_params(params, setting: current_setting) CreateConstraint.call(@form) do @@ -39,11 +40,11 @@ def create on(:invalid) do |message| render json: { - id: params[:id], - key: current_setting.var, - message: I18n.t("decidim_awesome.admin.constraints.create.error", scope: "decidim"), - error: message - }, + id: params[:id], + key: current_setting.var, + message: I18n.t("decidim_awesome.admin.constraints.create.error", scope: "decidim"), + error: message + }, status: :unprocessable_entity end end @@ -67,11 +68,11 @@ def update on(:invalid) do |message| render json: { - id: params[:id], - key: constraint.awesome_config.var, - message: I18n.t("decidim_awesome.admin.constraints.update.error", scope: "decidim"), - error: message - }, + id: params[:id], + key: constraint.awesome_config.var, + message: I18n.t("decidim_awesome.admin.constraints.update.error", scope: "decidim"), + error: message + }, status: :unprocessable_entity end end @@ -94,11 +95,11 @@ def destroy on(:invalid) do |message| render json: { - id: params[:id], - key: constraint.awesome_config.var, - message: I18n.t("decidim_awesome.admin.constraints.destroy.error", scope: "decidim"), - error: message - }, + id: params[:id], + key: constraint.awesome_config.var, + message: I18n.t("decidim_awesome.admin.constraints.destroy.error", scope: "decidim"), + error: message + }, status: :unprocessable_entity end end @@ -131,6 +132,8 @@ def constraint_key :scoped_admins when /^proposal_custom_field_/ :proposal_custom_fields + when /^proposal_private_custom_field_/ + :proposal_private_custom_fields else key end diff --git a/app/controllers/decidim/decidim_awesome/admin/custom_redirects_controller.rb b/app/controllers/decidim/decidim_awesome/admin/custom_redirects_controller.rb index ad5408d45..f0856897e 100644 --- a/app/controllers/decidim/decidim_awesome/admin/custom_redirects_controller.rb +++ b/app/controllers/decidim/decidim_awesome/admin/custom_redirects_controller.rb @@ -8,8 +8,6 @@ class CustomRedirectsController < DecidimAwesome::Admin::ApplicationController include NeedsAwesomeConfig include ConfigConstraintsHelpers - layout "decidim/decidim_awesome/admin/application" - before_action do enforce_permission_to :edit_config, :menu end @@ -23,6 +21,10 @@ def new @form = form(CustomRedirectForm).instance end + def edit + @form = form(CustomRedirectForm).from_model(redirect_item) + end + def create @form = form(CustomRedirectForm).from_params(params) CreateCustomRedirect.call(@form) do @@ -38,10 +40,6 @@ def create end end - def edit - @form = form(CustomRedirectForm).from_model(redirect_item) - end - def update @form = form(CustomRedirectForm).from_params(params) UpdateCustomRedirect.call(@form, redirect_item) do @@ -63,7 +61,7 @@ def destroy flash[:notice] = I18n.t("custom_redirects.destroy.success", scope: "decidim.decidim_awesome.admin") end on(:invalid) do |error| - flash[:alert] = I18n.t("custom_redirects.destroy.error", scope: "decidim.decidim_awesome.admin", error: error) + flash[:alert] = I18n.t("custom_redirects.destroy.error", scope: "decidim.decidim_awesome.admin", error:) end end redirect_to decidim_admin_decidim_awesome.custom_redirects_path @@ -75,13 +73,11 @@ def redirect_item origin, item = current_config.find { |origin, _| md5(origin) == params[:id] } raise ActiveRecord::RecordNotFound unless item - # rubocop:disable Style/OpenStructUse OpenStruct.new( - origin: origin, + origin:, destination: item["destination"], active: item["active"] ) - # rubocop:enable Style/OpenStructUse end def current_config diff --git a/app/controllers/decidim/decidim_awesome/admin/maintenance_controller.rb b/app/controllers/decidim/decidim_awesome/admin/maintenance_controller.rb new file mode 100644 index 000000000..b85947cff --- /dev/null +++ b/app/controllers/decidim/decidim_awesome/admin/maintenance_controller.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require "decidim/decidim_awesome/version" + +module Decidim + module DecidimAwesome + module Admin + # System compatibility analyzer + class MaintenanceController < DecidimAwesome::Admin::ApplicationController + include NeedsAwesomeConfig + include MaintenanceContext + include Decidim::Admin::Filterable + include ActionView::Helpers::DateHelper + + helper ConfigConstraintsHelpers + helper_method :collection, :resource, :present, :time_ago + + before_action do + enforce_permission_to :edit_config, :private_data, private_data: + end + + def show + respond_to do |format| + format.json do + render json: private_data_finder.for(params[:resources].to_s.split(",")).map { |resource| present(resource) } + end + format.all do + render :show + end + end + end + + def destroy_private_data + if private_data && private_data.total.to_i.positive? + Decidim::ActionLogger.log("destroy_private_data", current_user, resource, nil, count: private_data.total) + + Lock.new(current_organization).get!(resource) + DestroyPrivateDataJob.set(wait: 1.second).perform_later(resource) + end + redirect_to decidim_admin_decidim_awesome.maintenance_path("private_data"), + notice: I18n.t("destroying_private_data", scope: "decidim.decidim_awesome.admin.maintenance.private_data", title: present_private_data(resource).name) + end + + private + + def resource + @resource ||= Component.find_by(id: params[:resource_id]) + end + + def private_data + @private_data ||= present_private_data(resource) if resource + end + + def collection + filtered_collection + end + + def base_query + private_data_finder.query + end + + def present(resource) + present_private_data(resource) + end + + def private_data_finder + @private_data ||= PrivateDataFinder.new + end + + def time_ago + @time_ago ||= time_ago_in_words(Time.current - Decidim::DecidimAwesome.private_data_expiration_time) + end + end + end + end +end diff --git a/app/controllers/decidim/decidim_awesome/admin/menu_hacks_controller.rb b/app/controllers/decidim/decidim_awesome/admin/menu_hacks_controller.rb index 652d7bf7a..2437601fc 100644 --- a/app/controllers/decidim/decidim_awesome/admin/menu_hacks_controller.rb +++ b/app/controllers/decidim/decidim_awesome/admin/menu_hacks_controller.rb @@ -8,8 +8,6 @@ class MenuHacksController < DecidimAwesome::Admin::ApplicationController include NeedsAwesomeConfig include ConfigConstraintsHelpers - layout "decidim/decidim_awesome/admin/application" - helper ConfigConstraintsHelpers helper_method :current_items, :visibility_options, :target_options @@ -23,6 +21,10 @@ def new @form = form(MenuForm).instance end + def edit + @form = form(MenuForm).from_model(menu_item) + end + def create @form = form(MenuForm).from_params(params) CreateMenuHack.call(@form, current_menu_name) do @@ -38,10 +40,6 @@ def create end end - def edit - @form = form(MenuForm).from_model(menu_item) - end - def update @form = form(MenuForm).from_params(params) UpdateMenuHack.call(@form, current_menu_name) do @@ -63,7 +61,7 @@ def destroy flash[:notice] = I18n.t("menu_hacks.destroy.success", scope: "decidim.decidim_awesome.admin") end on(:invalid) do |error| - flash[:alert] = I18n.t("menu_hacks.destroy.error", scope: "decidim.decidim_awesome.admin", error: error) + flash[:alert] = I18n.t("menu_hacks.destroy.error", scope: "decidim.decidim_awesome.admin", error:) end end redirect_to decidim_admin_decidim_awesome.menu_hacks_path @@ -71,7 +69,6 @@ def destroy private - # rubocop:disable Style/OpenStructUse def menu_item item = current_items.find { |i| md5(i.url) == params[:id] } raise ActiveRecord::RecordNotFound unless item @@ -85,7 +82,6 @@ def menu_item native?: !item.respond_to?(:overrided?) ) end - # rubocop:enable Style/OpenStructUse def current_items @current_items ||= current_menu.items(include_invisible: true) @@ -96,7 +92,7 @@ def current_menu end def current_menu_name - :menu + params[:menu_id].to_sym end def visibility_options diff --git a/app/controllers/decidim/decidim_awesome/admin/proposal_custom_fields_controller.rb b/app/controllers/decidim/decidim_awesome/admin/proposal_custom_fields_controller.rb index b20ee7864..04db6bb13 100644 --- a/app/controllers/decidim/decidim_awesome/admin/proposal_custom_fields_controller.rb +++ b/app/controllers/decidim/decidim_awesome/admin/proposal_custom_fields_controller.rb @@ -6,9 +6,9 @@ module Admin # Global configuration controller class ProposalCustomFieldsController < DecidimAwesome::Admin::ConfigController def create - CreateProposalCustomField.call(current_organization) do + CreateProposalCustomField.call(current_organization, config_var) do on(:ok) do |key| - flash[:notice] = I18n.t("config.create_proposal_custom_field.success", key: key, scope: "decidim.decidim_awesome.admin") + flash[:notice] = I18n.t("config.create_proposal_custom_field.success", key:, scope: "decidim.decidim_awesome.admin") end on(:invalid) do |message| @@ -16,13 +16,13 @@ def create end end - redirect_to decidim_admin_decidim_awesome.config_path(:proposal_custom_fields) + redirect_to decidim_admin_decidim_awesome.config_path(config_var) end def destroy - DestroyProposalCustomField.call(params[:key], current_organization) do + DestroyProposalCustomField.call(params[:key], current_organization, config_var) do on(:ok) do |key| - flash[:notice] = I18n.t("config.destroy_proposal_custom_field.success", key: key, scope: "decidim.decidim_awesome.admin") + flash[:notice] = I18n.t("config.destroy_proposal_custom_field.success", key:, scope: "decidim.decidim_awesome.admin") end on(:invalid) do |message| @@ -30,7 +30,15 @@ def destroy end end - redirect_to decidim_admin_decidim_awesome.config_path(:proposal_custom_fields) + redirect_to decidim_admin_decidim_awesome.config_path(config_var) + end + + private + + def config_var + return :proposal_private_custom_fields if params[:private] == "true" + + :proposal_custom_fields end end end diff --git a/app/controllers/decidim/decidim_awesome/admin/scoped_admins_controller.rb b/app/controllers/decidim/decidim_awesome/admin/scoped_admins_controller.rb index 474acbb3c..d9d82cd4c 100644 --- a/app/controllers/decidim/decidim_awesome/admin/scoped_admins_controller.rb +++ b/app/controllers/decidim/decidim_awesome/admin/scoped_admins_controller.rb @@ -8,7 +8,7 @@ class ScopedAdminsController < DecidimAwesome::Admin::ConfigController def create CreateScopedAdmin.call(current_organization) do on(:ok) do |key| - flash[:notice] = I18n.t("config.create_scoped_admin.success", key: key, scope: "decidim.decidim_awesome.admin") + flash[:notice] = I18n.t("config.create_scoped_admin.success", key:, scope: "decidim.decidim_awesome.admin") end on(:invalid) do |message| @@ -22,7 +22,7 @@ def create def destroy DestroyScopedAdmin.call(params[:key], current_organization) do on(:ok) do |key| - flash[:notice] = I18n.t("config.destroy_scoped_admin.success", key: key, scope: "decidim.decidim_awesome.admin") + flash[:notice] = I18n.t("config.destroy_scoped_admin.success", key:, scope: "decidim.decidim_awesome.admin") end on(:invalid) do |message| diff --git a/app/controllers/decidim/decidim_awesome/admin/scoped_styles_controller.rb b/app/controllers/decidim/decidim_awesome/admin/scoped_styles_controller.rb index 46700075a..82919d062 100644 --- a/app/controllers/decidim/decidim_awesome/admin/scoped_styles_controller.rb +++ b/app/controllers/decidim/decidim_awesome/admin/scoped_styles_controller.rb @@ -8,7 +8,7 @@ class ScopedStylesController < DecidimAwesome::Admin::ConfigController def create CreateScopedStyle.call(current_organization) do on(:ok) do |key| - flash[:notice] = I18n.t("config.create_scoped_style.success", key: key, scope: "decidim.decidim_awesome.admin") + flash[:notice] = I18n.t("config.create_scoped_style.success", key:, scope: "decidim.decidim_awesome.admin") end on(:invalid) do |message| @@ -22,7 +22,7 @@ def create def destroy DestroyScopedStyle.call(params[:key], current_organization) do on(:ok) do |key| - flash[:notice] = I18n.t("config.destroy_scoped_style.success", key: key, scope: "decidim.decidim_awesome.admin") + flash[:notice] = I18n.t("config.destroy_scoped_style.success", key:, scope: "decidim.decidim_awesome.admin") end on(:invalid) do |message| diff --git a/app/controllers/decidim/decidim_awesome/blank_component_controller.rb b/app/controllers/decidim/decidim_awesome/blank_component_controller.rb index 2845f9249..53da3eeaa 100644 --- a/app/controllers/decidim/decidim_awesome/blank_component_controller.rb +++ b/app/controllers/decidim/decidim_awesome/blank_component_controller.rb @@ -6,13 +6,18 @@ module DecidimAwesome class BlankComponentController < Decidim::Components::BaseController # just redirects to settings def settings - redirect_to EngineRouter.admin_proxy(component.participatory_space).edit_component_path(id: component) + redirect_to EngineRouter.admin_proxy(current_component.participatory_space).edit_component_path(id: current_component) end private - def component - Decidim::Component.find(params[:component_id]) + def set_component_breadcrumb_item + context_breadcrumb_items << { + label: current_component.name, + url: EngineRouter.admin_proxy(current_component.participatory_space).edit_component_path(id: current_component), + active: false, + resource: current_component + } end end end diff --git a/app/controllers/decidim/decidim_awesome/editor_images_controller.rb b/app/controllers/decidim/decidim_awesome/editor_images_controller.rb index d6b6b5a5e..18019f8df 100644 --- a/app/controllers/decidim/decidim_awesome/editor_images_controller.rb +++ b/app/controllers/decidim/decidim_awesome/editor_images_controller.rb @@ -2,7 +2,7 @@ module Decidim module DecidimAwesome - # This controller handles image uploads for the hacked Quill editor + # This controller handles image uploads for the Tiptap editor class EditorImagesController < DecidimAwesome::ApplicationController include FormFactory include NeedsAwesomeConfig @@ -11,14 +11,14 @@ class EditorImagesController < DecidimAwesome::ApplicationController rescue_from Decidim::ActionForbidden, with: :ajax_user_has_no_permission def create - enforce_permission_to :create, :editor_image, awesome_config: awesome_config + enforce_permission_to(:create, :editor_image, awesome_config:) @form = form(EditorImageForm).from_params(form_values) CreateEditorImage.call(@form) do on(:ok) do |image| url = image.attached_uploader(:file).path url = "#{request.base_url}#{url}" unless url&.start_with?("http") - render json: { url: url, message: I18n.t("decidim_awesome.editor_images.create.success", scope: "decidim") } + render json: { url:, message: I18n.t("decidim_awesome.editor_images.create.success", scope: "decidim") } end on(:invalid) do |_message| diff --git a/app/controllers/decidim/decidim_awesome/iframe_component/iframe_controller.rb b/app/controllers/decidim/decidim_awesome/iframe_component/iframe_controller.rb index 9aba3bab4..5db06c54f 100644 --- a/app/controllers/decidim/decidim_awesome/iframe_component/iframe_controller.rb +++ b/app/controllers/decidim/decidim_awesome/iframe_component/iframe_controller.rb @@ -5,7 +5,8 @@ module DecidimAwesome module IframeComponent class IframeController < DecidimAwesome::BlankComponentController ALLOWED_ATTRIBUTES = %w(src width height frameborder title allow allowpaymentrequest name referrerpolicy sandbox srcdoc allowfullscreen).freeze - helper_method :iframe, :remove_margins?, :viewport_width? + helper_method :iframe, :viewport_width? + before_action :add_additional_csp_directives, only: :show def show; end @@ -27,13 +28,18 @@ def sanitize(html) document.to_s end - def remove_margins? - current_component.settings.no_margins - end - def viewport_width? current_component.settings.viewport_width end + + def add_additional_csp_directives + iframe_urls = Nokogiri::HTML::DocumentFragment.parse(iframe).children.select { |x| x.name == "iframe" }.filter_map { |x| x.attribute("src")&.value } + return if iframe_urls.blank? + + iframe_urls.each do |url| + content_security_policy.append_csp_directive("frame-src", url) + end + end end end end diff --git a/app/forms/concerns/decidim/decidim_awesome/proposals/proposal_form_override.rb b/app/forms/concerns/decidim/decidim_awesome/proposals/proposal_form_override.rb new file mode 100644 index 000000000..fcae0e19f --- /dev/null +++ b/app/forms/concerns/decidim/decidim_awesome/proposals/proposal_form_override.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Decidim + module DecidimAwesome + module Proposals + module ProposalFormOverride + extend ActiveSupport::Concern + + included do + alias_method :decidim_original_map_model, :map_model + attribute :private_body, String + + def map_model(model) + decidim_original_map_model(model) + self.private_body = model.private_body + end + end + end + end + end +end diff --git a/app/forms/decidim/decidim_awesome/proposals/proposal_wizard_create_step_form_override.rb b/app/forms/concerns/decidim/decidim_awesome/proposals/proposal_wizard_create_step_form_override.rb similarity index 95% rename from app/forms/decidim/decidim_awesome/proposals/proposal_wizard_create_step_form_override.rb rename to app/forms/concerns/decidim/decidim_awesome/proposals/proposal_wizard_create_step_form_override.rb index 87ba70ce7..0d6fdaf6b 100644 --- a/app/forms/decidim/decidim_awesome/proposals/proposal_wizard_create_step_form_override.rb +++ b/app/forms/concerns/decidim/decidim_awesome/proposals/proposal_wizard_create_step_form_override.rb @@ -15,6 +15,7 @@ module ProposalWizardCreateStepFormOverride minimum: ->(form) { form.minimum_title_length }, maximum: 150 } + validates :body, presence: true, unless: ->(form) { form.override_validations? || form.minimum_body_length.zero? } validates :body, etiquette: true, unless: ->(form) { form.override_validations? } validates :body, proposal_length: { @@ -27,7 +28,7 @@ module ProposalWizardCreateStepFormOverride def override_validations? return false if context.current_component.settings.participatory_texts_enabled - custom_fields.present? || awesome_config.enabled_in_context?(:use_markdown_editor) + custom_fields.present? end def minimum_title_length diff --git a/app/forms/decidim/decidim_awesome/admin/config_form.rb b/app/forms/decidim/decidim_awesome/admin/config_form.rb index 72a8ba05a..0fd268e1a 100644 --- a/app/forms/decidim/decidim_awesome/admin/config_form.rb +++ b/app/forms/decidim/decidim_awesome/admin/config_form.rb @@ -8,16 +8,15 @@ module Admin class ConfigForm < Decidim::Form include ActionView::Helpers::SanitizeHelper - attribute :allow_images_in_full_editor, Boolean - attribute :allow_images_in_small_editor, Boolean + attribute :allow_images_in_editors, Boolean + attribute :allow_videos_in_editors, Boolean attribute :allow_images_in_proposals, Boolean - attribute :use_markdown_editor, Boolean - attribute :allow_images_in_markdown_editor, Boolean attribute :auto_save_forms, Boolean attribute :scoped_styles, Hash attribute :proposal_custom_fields, Hash + attribute :proposal_private_custom_fields, Hash attribute :scoped_admins, Hash - attribute :menu, Array[MenuForm] + attribute :menu, [MenuForm] attribute :intergram_for_admins, Boolean attribute :intergram_for_admins_settings, IntergramForm attribute :intergram_for_public, Boolean @@ -36,7 +35,8 @@ class ConfigForm < Decidim::Form attr_accessor :valid_keys validate :css_syntax, if: ->(form) { form.scoped_styles.present? } - validate :json_syntax, if: ->(form) { form.proposal_custom_fields.present? } + validate :json_syntax + validates :validate_title_min_length, presence: true, numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: 100 } validates :validate_title_max_caps_percent, presence: true, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 100 } validates :validate_title_max_marks_together, presence: true, numericality: { greater_than_or_equal_to: 1 } @@ -65,26 +65,27 @@ def css_syntax SassC::Engine.new(code).render rescue SassC::SyntaxError => e - errors.add(:scoped_styles, I18n.t("config.form.errors.incorrect_css", key: key, scope: "decidim.decidim_awesome.admin")) + errors.add(:scoped_styles, I18n.t("config.form.errors.incorrect_css", key:, scope: "decidim.decidim_awesome.admin")) errors.add(key.to_sym, e.message) end end def json_syntax - proposal_custom_fields.each do |key, code| - next unless code + fields = {} + fields.merge!(proposal_custom_fields: proposal_custom_fields.values) if proposal_custom_fields.present? + fields.merge!(proposal_private_custom_fields: proposal_private_custom_fields.values) if proposal_private_custom_fields.present? + fields.each do |key, values| + next if values.blank? - JSON.parse(code) - rescue JSON::ParserError => e - errors.add(:scoped_styles, I18n.t("config.form.errors.incorrect_json", key: key, scope: "decidim.decidim_awesome.admin")) + values.each { |code| JSON.parse(code) } + rescue JSON::JSONError => e + errors.add(key, I18n.t("config.form.errors.incorrect_json", key:, scope: "decidim.decidim_awesome.admin")) errors.add(key.to_sym, e.message) end end # formBuilder has a bug and do not sanitize text if users copy/paste text with format in the label input def sanitize_labels! - return unless proposal_custom_fields - proposal_custom_fields.transform_values! do |code| next unless code @@ -97,6 +98,19 @@ def sanitize_labels! rescue JSON::ParserError code end + + proposal_private_custom_fields.transform_values! do |code| + next unless code + + json = JSON.parse(code) + json.map! do |item| + item["label"] = strip_tags(item["label"]) + item + end + JSON.generate(json) + rescue JSON::ParserError + code + end end end end diff --git a/app/forms/decidim/decidim_awesome/admin/custom_redirect_form.rb b/app/forms/decidim/decidim_awesome/admin/custom_redirect_form.rb index 8c3c7ae87..948a1741a 100644 --- a/app/forms/decidim/decidim_awesome/admin/custom_redirect_form.rb +++ b/app/forms/decidim/decidim_awesome/admin/custom_redirect_form.rb @@ -18,8 +18,8 @@ def to_params sanitize_url(origin), { destination: sanitize_url(destination, strip_host: false), - active: active, - pass_query: pass_query + active:, + pass_query: } ] end diff --git a/app/forms/decidim/decidim_awesome/admin/intergram_form.rb b/app/forms/decidim/decidim_awesome/admin/intergram_form.rb index 3eced5994..310b642c8 100644 --- a/app/forms/decidim/decidim_awesome/admin/intergram_form.rb +++ b/app/forms/decidim/decidim_awesome/admin/intergram_form.rb @@ -13,6 +13,10 @@ class IntergramForm < Decidim::Form attribute :intro_message, String attribute :auto_response, String attribute :auto_no_response, String + + def color + super || current_organization.colors["secondary"] || "#E91E63" + end end end end diff --git a/app/forms/decidim/decidim_awesome/admin/menu_form.rb b/app/forms/decidim/decidim_awesome/admin/menu_form.rb index 8f1660b45..f12bea76c 100644 --- a/app/forms/decidim/decidim_awesome/admin/menu_form.rb +++ b/app/forms/decidim/decidim_awesome/admin/menu_form.rb @@ -27,10 +27,10 @@ def map_model(model) def to_params { label: raw_label, - position: position, - url: url, - target: target, - visibility: visibility + position:, + url:, + target:, + visibility: } end end diff --git a/app/helpers/concerns/decidim/decidim_awesome/amendments_helper_override.rb b/app/helpers/concerns/decidim/decidim_awesome/amendments_helper_override.rb new file mode 100644 index 000000000..c6b7fbb7e --- /dev/null +++ b/app/helpers/concerns/decidim/decidim_awesome/amendments_helper_override.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +module Decidim + module DecidimAwesome + module AmendmentsHelperOverride + extend ActiveSupport::Concern + + included do + # original method + alias_method :decidim_amendments_form_field_for, :amendments_form_field_for + + # override with custom fields if scope applies + def amendments_form_field_for(attribute, form, original_resource) + custom_fields, custom_private_fields = awesome_custom_fields(attribute, form) + content = if custom_fields.blank? + decidim_amendments_form_field_for(attribute, form, original_resource) + else + render_amendment_custom_fields_override(custom_fields, attribute, form, original_resource) + end + if custom_private_fields.present? + content = content_tag("div", content) + content += content_tag("div", render_amendment_custom_fields_override(custom_private_fields, :private_body, form, original_resource)) + end + content + end + + private + + def render_amendment_custom_fields_override(custom_fields, attribute, form, original_resource) + # ensure decidim_editor is available as it is only required if the original FormBuilder is called + append_stylesheet_pack_tag "decidim_editor" + append_javascript_pack_tag "decidim_editor", defer: false + + custom_fields.translate! + body = amendments_form_fields_value(original_resource, attribute) + custom_fields.apply_xml(body) if body.present? + # TODO: find a way to add errors as form is not the parent form + # form.object.errors.add(attribute, custom_fields.errors) if custom_fields.errors + + editor_image = Decidim::EditorImage.new + editor_options = form.send(:editor_options, editor_image, { context: "participant", lines: 10 }) + editor_upload = form.send(:editor_upload, editor_image, editor_options[:upload]) + render partial: "decidim/decidim_awesome/custom_fields/form_render", locals: { spec: custom_fields.to_json, editor_options:, editor_upload:, form:, name: attribute } + end + + # Amendments don't use a URL specifying participatory space and component + # context for awesome config constraints must be obtained from the resource + def awesome_custom_fields(attribute, _form) + return unless attribute == :body + + component = amendable.try(:component) + return unless component + return if component.settings.participatory_texts_enabled? + + awesome_config = Decidim::DecidimAwesome::Config.new(component.organization) + awesome_config.context_from_component(component) + + pub = awesome_config.collect_sub_configs_values("proposal_custom_field") + priv = awesome_config.collect_sub_configs_values("proposal_private_custom_field") + [pub.presence && Decidim::DecidimAwesome::CustomFields.new(pub), priv.presence && Decidim::DecidimAwesome::CustomFields.new(priv)] + end + end + end + end +end diff --git a/app/helpers/concerns/decidim/decidim_awesome/breadcrumb_helper_override.rb b/app/helpers/concerns/decidim/decidim_awesome/breadcrumb_helper_override.rb new file mode 100644 index 000000000..8e5e43fbf --- /dev/null +++ b/app/helpers/concerns/decidim/decidim_awesome/breadcrumb_helper_override.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Decidim + module DecidimAwesome + module BreadcrumbHelperOverride + extend ActiveSupport::Concern + + included do + def active_breadcrumb_item(target_menu) + active_item = ::Decidim::MenuPresenter.new(target_menu, self).active_item_for_breadcrumb + + return if active_item.blank? + + { + label: active_item.label, + url: active_item.url, + active: active_item.active? + } + end + end + end + end +end diff --git a/app/helpers/decidim/decidim_awesome/proposals/application_helper_override.rb b/app/helpers/concerns/decidim/decidim_awesome/proposals/application_helper_override.rb similarity index 51% rename from app/helpers/decidim/decidim_awesome/proposals/application_helper_override.rb rename to app/helpers/concerns/decidim/decidim_awesome/proposals/application_helper_override.rb index 61894601b..5a3885934 100644 --- a/app/helpers/decidim/decidim_awesome/proposals/application_helper_override.rb +++ b/app/helpers/concerns/decidim/decidim_awesome/proposals/application_helper_override.rb @@ -12,7 +12,7 @@ module ApplicationHelperOverride # If the content is safe, HTML tags are sanitized, otherwise, they are stripped. def render_proposal_body(proposal) - if awesome_proposal_custom_fields.present? || awesome_config[:allow_images_in_full_editor] || awesome_config[:allow_images_in_small_editor] + if awesome_proposal_custom_fields.present? || awesome_config[:allow_images_in_editors] content = present(proposal).body(links: true, strip_tags: false) sanitized = decidim_sanitize_editor_admin(content, {}) Decidim::ContentProcessor.render_without_format(sanitized).html_safe @@ -23,38 +23,44 @@ def render_proposal_body(proposal) # replace normal method to draw the editor def text_editor_for_proposal_body(form) - custom_fields = awesome_proposal_custom_fields - - return decidim_text_editor_for_proposal_body(form) if custom_fields.blank? - - render_proposal_custom_fields_override(custom_fields, form, :body) + custom_fields = awesome_proposal_custom_fields_for(:body) + custom_private_fields = awesome_proposal_custom_fields_for(:private_body) + + content = if custom_fields.empty? + decidim_text_editor_for_proposal_body(form) + else + render_proposal_custom_fields_override(custom_fields, form, :body) + end + + unless custom_private_fields.empty? + content = content_tag("div", content) + content += content_tag("div", render_proposal_custom_fields_override(custom_private_fields, form, :private_body)) + end + content end # replace admin method to draw the editor (multi lang) def admin_editor_for_proposal_body(form) - custom_fields = awesome_proposal_custom_fields + custom_fields = awesome_proposal_custom_fields_for(:body) - return form.translated(:editor, :body, hashtaggable: true) if custom_fields.blank? + return if custom_fields.empty? locales = form.send(:locales) - return render_proposal_custom_fields_override(custom_fields, form, "body_#{locales.first}", locales.first) if locales.length == 1 tabs_id = form.send(:sanitize_tabs_selector, form.options[:tabs_id] || "#{form.object_name}-body-tabs") label_tabs = form.content_tag(:div, class: "label--tabs") do - field_label = form.send(:label_i18n, "body", form.label_for("proposal_custom_fields")) - language_selector = "".html_safe language_selector = form.create_language_selector(locales, tabs_id, "body") if form.options[:label] != false - safe_join [field_label, language_selector] + safe_join [content_tag("label"), language_selector] end tabs_content = form.content_tag(:div, class: "tabs-content", data: { tabs_content: tabs_id }) do locales.each_with_index.inject("".html_safe) do |string, (locale, index)| tab_content_id = "#{tabs_id}-body-panel-#{index}" - string + content_tag(:div, class: form.send(:tab_element_class_for, "panel", index), id: tab_content_id) do + string + content_tag(:div, class: form.send(:tab_element_class_for, "panel", index), id: tab_content_id, "aria-hidden": index.zero? ? "false" : "true") do render_proposal_custom_fields_override(custom_fields, form, "body_#{locale}", locale) end end @@ -63,19 +69,39 @@ def admin_editor_for_proposal_body(form) safe_join [label_tabs, tabs_content] end - def render_proposal_custom_fields_override(fields, form, name, locale = nil) - custom_fields = Decidim::DecidimAwesome::CustomFields.new(fields) + def render_proposal_custom_fields_override(custom_fields, form, name, locale = nil) + # ensure decidim_editor is available as it is only required if the original FormBuilder is called + append_stylesheet_pack_tag "decidim_editor" + append_javascript_pack_tag "decidim_editor", defer: false + custom_fields.translate! - body = if form_presenter.proposal.body.is_a?(Hash) && locale.present? - form_presenter.body(extras: false, all_locales: true).with_indifferent_access[locale] + body = if name == :private_body + if form_presenter.proposal.private_body.is_a?(Hash) && locale.present? + form_presenter.private_body(extras: false, all_locales: locale.present?).with_indifferent_access[locale] + else + form_presenter.private_body(extras: false) + end + elsif form_presenter.proposal.body.is_a?(Hash) && locale.present? + form_presenter.body(extras: false, all_locales: locale.present?).with_indifferent_access[locale] else form_presenter.body(extras: false) end custom_fields.apply_xml(body) if body.present? form.object.errors.add(name, custom_fields.errors) if custom_fields.errors - render partial: "decidim/decidim_awesome/custom_fields/form_render", locals: { spec: custom_fields.to_json, form: form, name: name } + editor_image = Decidim::EditorImage.new + editor_options = form.send(:editor_options, editor_image, { context: "participant", lines: 10 }) + editor_upload = form.send(:editor_upload, editor_image, editor_options[:upload]) + render partial: "decidim/decidim_awesome/custom_fields/form_render", locals: { spec: custom_fields.to_json, editor_options:, editor_upload:, form:, name: } + end + + def awesome_proposal_custom_fields_for(name) + if name == :private_body + Decidim::DecidimAwesome::CustomFields.new(awesome_proposal_private_custom_fields) + else + Decidim::DecidimAwesome::CustomFields.new(awesome_proposal_custom_fields) + end end end end diff --git a/app/helpers/decidim/decidim_awesome/admin/config_constraints_helpers.rb b/app/helpers/decidim/decidim_awesome/admin/config_constraints_helpers.rb index d372fc4ab..65972edaa 100644 --- a/app/helpers/decidim/decidim_awesome/admin/config_constraints_helpers.rb +++ b/app/helpers/decidim/decidim_awesome/admin/config_constraints_helpers.rb @@ -8,40 +8,19 @@ module ConfigConstraintsHelpers include Decidim::TranslatableAttributes - def check(status) - content_tag(:span, icon(status ? "check" : "x", class: "icon", aria_label: status, role: "img"), class: "text-#{status ? "success" : "alert"}") - end + delegate :menus, :config_enabled?, to: "Decidim::DecidimAwesome::Menu" - def menus - @menus ||= { - editors: config_enabled?([:allow_images_in_full_editor, :allow_images_in_small_editor, :use_markdown_editor, :allow_images_in_markdown_editor]), - proposals: config_enabled?([:allow_images_in_proposals, - :validate_title_min_length, :validate_title_max_caps_percent, - :validate_title_max_marks_together, :validate_title_start_with_caps, - :validate_body_min_length, :validate_body_max_caps_percent, - :validate_body_max_marks_together, :validate_body_start_with_caps]), - surveys: config_enabled?(:auto_save_forms), - styles: config_enabled?(:scoped_styles), - proposal_custom_fields: config_enabled?(:proposal_custom_fields), - admins: config_enabled?(:scoped_admins), - menu_hacks: config_enabled?(:menu), - custom_redirects: config_enabled?(:custom_redirects), - livechat: config_enabled?([:intergram_for_admins, :intergram_for_public]) - } + def check(status) + content_tag(:span, icon(status ? "check-line" : "close-line", class: "inline-block", aria_label: status, role: "img"), class: "fill-#{status ? "success" : "alert"}") end # returns only non :disabled vars in config def enabled_configs(vars) - vars.filter do |var| - config_enabled? var + vars.filter do |conf| + config_enabled?(conf) end end - # ensure boolean value - def config_enabled?(var) - DecidimAwesome.enabled?(var) - end - def participatory_space_manifests manifests = OTHER_MANIFESTS.index_with { |m| I18n.t("decidim.decidim_awesome.admin.config.#{m}") } Decidim.participatory_space_manifests.pluck(:name).each do |name| @@ -71,7 +50,7 @@ def components_list(manifest, slug) space = model_for_manifest(manifest) return {} unless space&.column_names&.include? "slug" - components = Component.where(participatory_space: space.find_by(slug: slug)) + components = Component.where(participatory_space: space.find_by(slug:)) components.to_h do |item| [item.id, "#{item.id}: #{translated_attribute(item.name)}"] end diff --git a/app/helpers/decidim/decidim_awesome/amendments_helper_override.rb b/app/helpers/decidim/decidim_awesome/amendments_helper_override.rb deleted file mode 100644 index 009452759..000000000 --- a/app/helpers/decidim/decidim_awesome/amendments_helper_override.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -module Decidim - module DecidimAwesome - module AmendmentsHelperOverride - extend ActiveSupport::Concern - - included do - # original method - alias_method :decidim_amendments_form_field_for, :amendments_form_field_for - - # override with custom fields if scope applies - def amendments_form_field_for(attribute, form, original_resource) - custom_fields = awesome_custom_fields(attribute, form) - return decidim_amendments_form_field_for(attribute, form, original_resource) if custom_fields.blank? - - render_amendment_custom_fields_override(custom_fields, attribute, form, original_resource) - end - - private - - def render_amendment_custom_fields_override(fields, attribute, form, original_resource) - custom_fields = Decidim::DecidimAwesome::CustomFields.new(fields) - custom_fields.translate! - body = amendments_form_fields_value(original_resource, attribute) - custom_fields.apply_xml(body) if body.present? - # TODO: find a way to add errors as form is not the parent form - # form.object.errors.add(attribute, custom_fields.errors) if custom_fields.errors - render partial: "decidim/decidim_awesome/custom_fields/form_render", locals: { spec: custom_fields.to_json, form: form, name: attribute } - end - - # Amendments don't use a URL specifying participatory space and component - # context for awesome config constraints must be obtained from the resource - def awesome_custom_fields(attribute, _form) - return unless attribute == :body - - component = amendable.try(:component) - return unless component - return if component.settings.participatory_texts_enabled? - - awesome_config = Decidim::DecidimAwesome::Config.new(component.organization) - awesome_config.context_from_component(component) - awesome_config.collect_sub_configs_values("proposal_custom_field") - end - end - end - end -end diff --git a/app/helpers/decidim/decidim_awesome/map_helper.rb b/app/helpers/decidim/decidim_awesome/map_helper.rb index dac20acff..15367ebe7 100644 --- a/app/helpers/decidim/decidim_awesome/map_helper.rb +++ b/app/helpers/decidim/decidim_awesome/map_helper.rb @@ -11,14 +11,12 @@ def api_ready? # rubocop:disable Metrics/CyclomaticComplexity # rubocop:disable Metrics/PerceivedComplexity: - def awesome_map_for(components, &block) + def awesome_map_for(components, &) return unless map_utility_dynamic - map = awesome_builder.map_element({ class: "google-map", id: "awesome-map-container" }, &block) - help = content_tag(:div, class: "map__help") do - sr_content = content_tag(:p, t("screen_reader_explanation", scope: "decidim.map.dynamic"), class: "show-for-sr") - - sr_content + map = awesome_builder.map_element({ class: "dynamic-map", id: "awesome-map-container" }, &) + help = content_tag(:div, class: "map__skip-container") do + content_tag(:p, t("screen_reader_explanation", scope: "decidim.map.dynamic"), class: "sr-only") end html_options = { @@ -30,7 +28,7 @@ def awesome_map_for(components, &block) type: component.manifest.name, name: translated_attribute(component.name), url: Decidim::EngineRouter.main_proxy(component).root_path, - amendments: component.manifest.name == :proposals ? Decidim::Proposals::Proposal.where(component: component).only_emendations.count : 0 + amendments: component.manifest.name == :proposals ? Decidim::Proposals::Proposal.where(component:).only_emendations.count : 0 } end.to_json, "data-hide-controls" => settings_source.try(:hide_controls), @@ -51,7 +49,7 @@ def awesome_map_for(components, &block) } content_tag(:div, html_options) do - content_tag :div, class: "row column" do + content_tag :div, class: "w-full" do help + map end end @@ -71,7 +69,6 @@ def settings_source try(:current_component) || self end - # rubocop:disable Rails/HelperInstanceVariable def current_categories(categories) return @current_categories if @current_categories @@ -97,26 +94,12 @@ def awesome_builder } builder = map_utility_dynamic.create_builder(self, options) - # We need awesome map listeners before initialize the official map - unless snippets.any?(:awesome_map_styles) || snippets.any?(:awesome_map_scripts) - snippets.add(:awesome_map_styles, stylesheet_pack_tag("decidim_decidim_awesome_map")) - snippets.add(:awesome_map_scripts, javascript_pack_tag("decidim_decidim_awesome_map", defer: false)) - snippets.add(:head, snippets.for(:awesome_map_styles)) - snippets.add(DecidimAwesome.legacy_version? ? :head : :foot, snippets.for(:awesome_map_scripts)) - end - - unless snippets.any?(:map_styles) || snippets.any?(:map_scripts) - snippets.add(:map_styles, builder.stylesheet_snippets) - snippets.add(:map_scripts, builder.javascript_snippets) - - snippets.add(:head, snippets.for(:map_styles)) - snippets.add(DecidimAwesome.legacy_version? ? :head : :foot, snippets.for(:map_scripts)) - end + append_stylesheet_pack_tag("decidim_decidim_awesome_map") + append_javascript_pack_tag("decidim_decidim_awesome_map") builder end - # rubocop:disable Style/FormatStringToken def append_category(category) @h += @golden_ratio_conjugate @h %= 1 @@ -129,8 +112,6 @@ def append_category(category) color: format("#%02x%02x%02x", r, g, b) ) end - # rubocop:enable Style/FormatStringToken - # rubocop:enable Rails/HelperInstanceVariable # HSV values in [0..1[ # returns [r, g, b] values from 0 to 255 diff --git a/app/jobs/decidim/decidim_awesome/destroy_private_data_job.rb b/app/jobs/decidim/decidim_awesome/destroy_private_data_job.rb new file mode 100644 index 000000000..f63afefdd --- /dev/null +++ b/app/jobs/decidim/decidim_awesome/destroy_private_data_job.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Decidim + module DecidimAwesome + class DestroyPrivateDataJob < ApplicationJob + queue_as :default + + # Destroys private data associated with the resource + def perform(resource) + extra_fields = Decidim::DecidimAwesome::ProposalExtraField.where( + proposal: Decidim::Proposals::Proposal.where(component: resource) + ).where("private_body_updated_at < ?", DecidimAwesome.private_data_expiration_time.ago) + + extra_fields.find_each do |extra_field| + extra_field.update(private_body: nil) + end + + Lock.new(resource.organization).release!(resource) + end + end + end +end diff --git a/app/jobs/decidim/decidim_awesome/migrate_legacy_images_job.rb b/app/jobs/decidim/decidim_awesome/migrate_legacy_images_job.rb index 1ba13a58e..f9e3629a6 100644 --- a/app/jobs/decidim/decidim_awesome/migrate_legacy_images_job.rb +++ b/app/jobs/decidim/decidim_awesome/migrate_legacy_images_job.rb @@ -25,7 +25,7 @@ def migrate_all! cw_uploader: Decidim::Cw::DecidimAwesome::ImageUploader, as_attribute: "file", logger: @logger, - routes_mappings: routes_mappings + routes_mappings: ) end @@ -34,11 +34,11 @@ def transform_images_urls klass, id = mapping[:instance].split("#") next unless klass == "Decidim::DecidimAwesome::EditorImage" - instance = Decidim::DecidimAwesome::EditorImage.find_by(id: id) + instance = Decidim::DecidimAwesome::EditorImage.find_by(id:) next if instance.blank? - mapping.merge!(instance: instance) + mapping.merge!(instance:) end.compact editor_images_available_attributes.each do |model, attributes| @@ -75,7 +75,8 @@ def editor_images_available_attributes "Decidim::Assembly" => %w(short_description description purpose_of_action composition internal_organisation announcement closing_date_reason special_features), "Decidim::Forms::Questionnaire" => %w(description tos), "Decidim::Forms::Question" => %w(description), - "Decidim::Organization" => %w(welcome_notification_body admin_terms_of_use_body description highlighted_content_banner_short_description id_documents_explanation_text), + "Decidim::Organization" => %w(welcome_notification_body admin_terms_of_service_body description highlighted_content_banner_short_description + id_documents_explanation_text), "Decidim::StaticPage" => %w(content), "Decidim::ContextualHelpSection" => %w(content), "Decidim::Category" => %w(description), diff --git a/app/middleware/decidim/decidim_awesome/current_config.rb b/app/middleware/decidim/decidim_awesome/current_config.rb index d6826d613..93e135d9e 100644 --- a/app/middleware/decidim/decidim_awesome/current_config.rb +++ b/app/middleware/decidim/decidim_awesome/current_config.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -# rubocop:disable Style/OpenStructUse module Decidim module DecidimAwesome # A middleware that stores the current awesome context by parsing the request @@ -101,7 +100,7 @@ def processable_path? end def safe_get_route? - return unless @request.get? + return false unless @request.get? case @request.path when "/" @@ -116,7 +115,7 @@ def safe_get_route? end def safe_post_route? - return unless @request.post? || @request.put? || @request.patch? + return false unless @request.post? || @request.put? || @request.patch? case @request.path when %r{^/admin/admin_terms} @@ -171,7 +170,7 @@ def additional_post_constraints(constraints) next unless model settings["participatory_space_slug"] = model.find_by(slug: settings["participatory_space_slug"])&.id - OpenStruct.new(settings: settings) if settings["participatory_space_slug"] + OpenStruct.new(settings:) if settings["participatory_space_slug"] end end # rubocop:enable Metrics/CyclomaticComplexity @@ -183,4 +182,3 @@ def scoped_admins_active? end end end -# rubocop:enable Style/OpenStructUse diff --git a/app/models/concerns/decidim/decidim_awesome/has_proposal_extra_fields.rb b/app/models/concerns/decidim/decidim_awesome/has_proposal_extra_fields.rb index 626ce988d..b89ca64fe 100644 --- a/app/models/concerns/decidim/decidim_awesome/has_proposal_extra_fields.rb +++ b/app/models/concerns/decidim/decidim_awesome/has_proposal_extra_fields.rb @@ -6,10 +6,31 @@ module HasProposalExtraFields extend ActiveSupport::Concern included do - has_one :extra_fields, foreign_key: "decidim_proposal_id", class_name: "Decidim::DecidimAwesome::ProposalExtraField", dependent: :destroy + has_one :extra_fields, as: :proposal, foreign_key: "decidim_proposal_id", foreign_type: "decidim_proposal_type", class_name: "Decidim::DecidimAwesome::ProposalExtraField", + dependent: :destroy + + after_save do |proposal| + if proposal.extra_fields && proposal.extra_fields.changed? + proposal.extra_fields.save + proposal.update_vote_weights + proposal.reload + end + end + + delegate :private_body=, to: :safe_extra_fields + + def private_body + extra_fields.private_body if extra_fields + end + + def update_private_body!(private_body) + safe_extra_fields.private_body = private_body + safe_extra_fields.save! + self.extra_fields = safe_extra_fields + end def weight_count(weight) - (extra_fields && extra_fields.vote_weight_totals[weight.to_s]) || 0 + safe_extra_fields.vote_weight_totals[weight.to_s] || 0 end def vote_weights @@ -24,24 +45,32 @@ def all_vote_weights @all_vote_weights ||= self.class.all_vote_weights_for(component) end - def update_vote_weights! - extra_fields ||= Decidim::DecidimAwesome::ProposalExtraField.find_or_initialize_by(proposal: self) - extra_fields.vote_weight_totals = {} + def update_vote_weights + votes = Decidim::Proposals::ProposalVote.where(proposal: self) + safe_extra_fields.vote_weight_totals = {} votes.each do |vote| - extra_fields.vote_weight_totals[vote.weight] ||= 0 - extra_fields.vote_weight_totals[vote.weight] += 1 + safe_extra_fields.vote_weight_totals[vote.weight] ||= 0 + safe_extra_fields.vote_weight_totals[vote.weight] += 1 end - extra_fields.save! - self.extra_fields = extra_fields @vote_weights = nil @all_vote_weights = nil end + def update_vote_weights! + update_vote_weights + safe_extra_fields.save! + self.extra_fields = safe_extra_fields + end + + def safe_extra_fields + @safe_extra_fields ||= (persisted? && reload.extra_fields) || build_extra_fields(vote_weight_totals: {}) + end + # collects all different weights stored along the different proposals in a different component def self.all_vote_weights_for(component) Decidim::DecidimAwesome::VoteWeight.where( proposal_vote_id: Decidim::Proposals::ProposalVote.where( - proposal: Decidim::Proposals::Proposal.where(component: component) + proposal: Decidim::Proposals::Proposal.where(component:) ) ).pluck(:weight) end diff --git a/app/models/decidim/decidim_awesome/awesome_config.rb b/app/models/decidim/decidim_awesome/awesome_config.rb index 51a0b4caf..962b528d4 100644 --- a/app/models/decidim/decidim_awesome/awesome_config.rb +++ b/app/models/decidim/decidim_awesome/awesome_config.rb @@ -25,7 +25,7 @@ def add_constraints(constraints) end def self.for_organization(organization) - where(organization: organization) + where(organization:) end # use this instead of "constraints" to evaluate dynamically added constants diff --git a/app/models/decidim/decidim_awesome/paper_trail_version.rb b/app/models/decidim/decidim_awesome/paper_trail_version.rb index e2f38544e..ba5d9e146 100644 --- a/app/models/decidim/decidim_awesome/paper_trail_version.rb +++ b/app/models/decidim/decidim_awesome/paper_trail_version.rb @@ -12,7 +12,7 @@ def self.safe_user_roles scope :space_role_actions, lambda { |organization| role_changes = where(item_type: PaperTrailVersion.safe_user_roles, event: "create") user_ids_from_object_changes = role_changes.pluck(:object_changes).map { |change| change.match(/decidim_user_id:\n- ?\n- (\d+)/)[1].to_i } - relevant_user_ids = Decidim::User.select(:id).where(id: user_ids_from_object_changes, organization: organization).pluck(:id) + relevant_user_ids = Decidim::User.select(:id).where(id: user_ids_from_object_changes, organization:).pluck(:id) # add users that might have been completly destroyed in any organization relevant_user_ids += user_ids_from_object_changes - Decidim::User.select("id").where(id: user_ids_from_object_changes).pluck(:id) @@ -39,9 +39,9 @@ def self.admin_role_actions(filter = nil) def present(html: true) @present ||= if item_type == "Decidim::UserBaseEntity" - UserEntityPresenter.new(self, html: html) + UserEntityPresenter.new(self, html:) elsif item_type.in?(PaperTrailVersion.safe_user_roles) - ParticipatorySpaceRolePresenter.new(self, html: html) + ParticipatorySpaceRolePresenter.new(self, html:) else self end diff --git a/app/models/decidim/decidim_awesome/proposal_extra_field.rb b/app/models/decidim/decidim_awesome/proposal_extra_field.rb index d64eb9208..e174d12e7 100644 --- a/app/models/decidim/decidim_awesome/proposal_extra_field.rb +++ b/app/models/decidim/decidim_awesome/proposal_extra_field.rb @@ -2,10 +2,44 @@ module Decidim module DecidimAwesome + # This class adds some attributes to the proposal model in a different table + # in particular, it adds a private_body field that should be encrypted + # private_body is not translatable, nor is intended to be as it won't be shown to the public class ProposalExtraField < ApplicationRecord + include Decidim::RecordEncryptor + self.table_name = "decidim_awesome_proposal_extra_fields" - belongs_to :proposal, foreign_key: "decidim_proposal_id", class_name: "Decidim::Proposals::Proposal" + belongs_to :proposal, foreign_key: "decidim_proposal_id", foreign_type: "decidim_proposal_type", polymorphic: true + + encrypt_attribute :private_body, type: :string + + after_initialize :store_private_body + before_save :update_private_body_updated_at + + # validate not more than one extra field can be associated to a proposal + # validates :proposal, uniqueness: true + validate :no_more_than_one_extra_field + + private + + def store_private_body + @initial_private_body = private_body + end + + # using private_body_changed? does not sufice as the encrypted value is always updated on saving + def update_private_body_updated_at + if private_body != @initial_private_body + self.private_body_updated_at = Time.current + @initial_private_body = private_body + end + end + + def no_more_than_one_extra_field + return unless ProposalExtraField.where(proposal:).where.not(id:).exists? + + errors.add(:proposal, :invalid) + end end end end diff --git a/app/models/decidim/decidim_awesome/vote_weight.rb b/app/models/decidim/decidim_awesome/vote_weight.rb index 9f3f26b8b..ad0497e5c 100644 --- a/app/models/decidim/decidim_awesome/vote_weight.rb +++ b/app/models/decidim/decidim_awesome/vote_weight.rb @@ -12,7 +12,7 @@ class VoteWeight < ApplicationRecord after_save :update_vote_weight_totals! def update_vote_weight_totals! - extra = Decidim::DecidimAwesome::ProposalExtraField.find_or_initialize_by(proposal: proposal) + extra = Decidim::DecidimAwesome::ProposalExtraField.find_or_initialize_by(proposal:) extra.vote_weight_totals = extra.vote_weight_totals || {} prev = weight_previous_change&.first @@ -20,7 +20,7 @@ def update_vote_weight_totals! extra.vote_weight_totals[prev.to_s] = Decidim::DecidimAwesome::VoteWeight.where(vote: proposal.votes, weight: prev).count extra.vote_weight_totals.delete(prev.to_s) if extra.vote_weight_totals[prev.to_s].zero? end - extra.vote_weight_totals[weight.to_s] = Decidim::DecidimAwesome::VoteWeight.where(vote: proposal.votes, weight: weight).count + extra.vote_weight_totals[weight.to_s] = Decidim::DecidimAwesome::VoteWeight.where(vote: proposal.votes, weight:).count extra.vote_weight_totals.delete(weight.to_s) if extra.vote_weight_totals[weight.to_s].zero? extra.weight_total = extra.vote_weight_totals.inject(0) { |sum, (weight, count)| sum + (weight.to_i * count) } extra.save! diff --git a/app/overrides/decidim/proposals/admin/proposals/show/add_private_body.html.erb.deface b/app/overrides/decidim/proposals/admin/proposals/show/add_private_body.html.erb.deface new file mode 100644 index 000000000..b88a31139 --- /dev/null +++ b/app/overrides/decidim/proposals/admin/proposals/show/add_private_body.html.erb.deface @@ -0,0 +1,7 @@ + + +<% if awesome_proposal_custom_fields.blank? %> + <%= simple_format(present(proposal).body(strip_tags: true)) %> +<% else %> + <%= render_proposal_body(proposal) %> +<% end %> diff --git a/app/overrides/decidim/proposals/admin/proposals/show/replace_body.html.erb.deface b/app/overrides/decidim/proposals/admin/proposals/show/replace_body.html.erb.deface new file mode 100644 index 000000000..8240b3b89 --- /dev/null +++ b/app/overrides/decidim/proposals/admin/proposals/show/replace_body.html.erb.deface @@ -0,0 +1,5 @@ + + +<% unless awesome_proposal_private_custom_fields.blank? %> + <%= render partial: "decidim/decidim_awesome/admin/proposals/private_body", locals: { proposal: proposal } %> +<% end %> diff --git a/app/overrides/decidim/proposals/proposals/show/limit_amendments_modal.html.erb.deface b/app/overrides/decidim/proposals/proposals/_proposal_aside/limit_amendments_modal.html.erb.deface similarity index 53% rename from app/overrides/decidim/proposals/proposals/show/limit_amendments_modal.html.erb.deface rename to app/overrides/decidim/proposals/proposals/_proposal_aside/limit_amendments_modal.html.erb.deface index 01dfdc58f..4a7095323 100644 --- a/app/overrides/decidim/proposals/proposals/show/limit_amendments_modal.html.erb.deface +++ b/app/overrides/decidim/proposals/proposals/_proposal_aside/limit_amendments_modal.html.erb.deface @@ -2,4 +2,4 @@ <%= amend_button_for @proposal %> -<%= render "decidim/decidim_awesome/amendments/modal" if @proposal.emendations.not_hidden.where(decidim_amendments: { state: :evaluating }).count.positive? && @proposal.amendable? %> +<%= render "decidim/decidim_awesome/amendments/modal" if @proposal.emendations.not_hidden.where(decidim_amendments: { state: Decidim::Amendment.states["evaluating"] }).count.positive? && @proposal.amendable? %> diff --git a/app/overrides/decidim/proposals/proposals/_vote_button/replace_vote_button.html.erb.deface b/app/overrides/decidim/proposals/proposals/_vote_button/replace_vote_button.html.erb.deface index 4119f07cc..cc4746546 100644 --- a/app/overrides/decidim/proposals/proposals/_vote_button/replace_vote_button.html.erb.deface +++ b/app/overrides/decidim/proposals/proposals/_vote_button/replace_vote_button.html.erb.deface @@ -1,4 +1,4 @@ - + <% if view = awesome_voting_manifest_for(current_component)&.show_vote_button_view %> <%= render view, local_assigns if view.present? %> diff --git a/app/overrides/layouts/decidim/_decidim_javascript/add_awesome_tags.html.erb.deface b/app/overrides/layouts/decidim/_decidim_javascript/add_awesome_tags.html.erb.deface new file mode 100644 index 000000000..763b4265f --- /dev/null +++ b/app/overrides/layouts/decidim/_decidim_javascript/add_awesome_tags.html.erb.deface @@ -0,0 +1,6 @@ + + +<%= render partial: "layouts/decidim/decidim_awesome/awesome_config" %> +<% if show_public_intergram? %> + <%= render partial: "layouts/decidim/decidim_awesome/intergram_widget", locals: { settings: organization_awesome_config[:intergram_for_public_settings] } %> +<% end %> diff --git a/app/overrides/layouts/decidim/_head/add_awesome_legacy_scripts.rb b/app/overrides/layouts/decidim/_head/add_awesome_legacy_scripts.rb deleted file mode 100644 index 2e92086eb..000000000 --- a/app/overrides/layouts/decidim/_head/add_awesome_legacy_scripts.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -Deface::Override.new(virtual_path: "layouts/decidim/_head", - name: "legacy_awesome_scripts", - insert_after: 'erb[loud]:contains(\'javascript_pack_tag "decidim_core"\')') do - # NOTE THAT THIS ONLY APPLIES TO DECIDIM 0.26. This will be ignored in 0.27 - ' - <%= javascript_pack_tag "decidim_decidim_awesome", defer: false %> - <%= javascript_pack_tag("decidim_decidim_awesome_custom_fields") if Decidim::DecidimAwesome.enabled?(:proposal_custom_fields) %> - <% if show_public_intergram? %> - <%= render partial: "layouts/decidim/decidim_awesome/intergram_widget", locals: { settings: organization_awesome_config[:intergram_for_public_settings] } %> - <% end %> - ' -end diff --git a/app/overrides/layouts/decidim/_head/add_awesome_tags.html.erb.deface b/app/overrides/layouts/decidim/_head/add_awesome_tags.html.erb.deface index 0a1f0cfc7..c0238a825 100644 --- a/app/overrides/layouts/decidim/_head/add_awesome_tags.html.erb.deface +++ b/app/overrides/layouts/decidim/_head/add_awesome_tags.html.erb.deface @@ -1,14 +1,7 @@ - + -<%= stylesheet_pack_tag "decidim_decidim_awesome", media: "all" %> +<% append_stylesheet_pack_tag "decidim_decidim_awesome", media: "all" %> <%= render(partial: "layouts/decidim/decidim_awesome/custom_styles") if awesome_custom_styles.present? %> -<%= render partial: "layouts/decidim/decidim_awesome/awesome_config" %> -<%# This will render scripts only on Decidim 0.27 %> -<% content_for :js_content do %> - <%= javascript_pack_tag "decidim_decidim_awesome", defer: false %> - <%= javascript_pack_tag("decidim_decidim_awesome_custom_fields") if Decidim::DecidimAwesome.enabled?(:proposal_custom_fields) %> - <% if show_public_intergram? %> - <%= render partial: "layouts/decidim/decidim_awesome/intergram_widget", locals: { settings: organization_awesome_config[:intergram_for_public_settings] } %> - <% end %> -<% end %> +<% append_javascript_pack_tag "decidim_decidim_awesome", defer: false %> +<% append_javascript_pack_tag("decidim_decidim_awesome_custom_fields") if Decidim::DecidimAwesome.enabled?(:proposal_custom_fields) %> diff --git a/app/overrides/layouts/decidim/admin/_header/replace_scripts.html.erb.deface b/app/overrides/layouts/decidim/admin/_header/add_awesome_tags.html.erb.deface similarity index 55% rename from app/overrides/layouts/decidim/admin/_header/replace_scripts.html.erb.deface rename to app/overrides/layouts/decidim/admin/_header/add_awesome_tags.html.erb.deface index 1ae6cc68f..fcf72870c 100644 --- a/app/overrides/layouts/decidim/admin/_header/replace_scripts.html.erb.deface +++ b/app/overrides/layouts/decidim/admin/_header/add_awesome_tags.html.erb.deface @@ -1,9 +1,9 @@ - + <%= render partial: "layouts/decidim/decidim_awesome/awesome_config" %> - -<%= javascript_pack_tag "decidim_admin_decidim_awesome", defer: false %> -<%= javascript_pack_tag("decidim_admin_decidim_awesome_custom_fields") if Decidim::DecidimAwesome.enabled?(:proposal_custom_fields) %> +<% append_stylesheet_pack_tag("decidim_admin_decidim_awesome_global") %> +<% append_javascript_pack_tag("decidim_admin_decidim_awesome_global") %> +<% append_javascript_pack_tag("decidim_decidim_awesome_custom_fields") if Decidim::DecidimAwesome.enabled?(:proposal_custom_fields) %> <% if awesome_config[:intergram_for_admins] %> <%= render partial: "layouts/decidim/decidim_awesome/intergram_widget", locals: { settings: organization_awesome_config[:intergram_for_admins_settings] } %> <% end %> diff --git a/app/overrides/layouts/decidim/admin/_header/replace_styles.html.erb.deface b/app/overrides/layouts/decidim/admin/_header/replace_styles.html.erb.deface deleted file mode 100644 index d17326a5d..000000000 --- a/app/overrides/layouts/decidim/admin/_header/replace_styles.html.erb.deface +++ /dev/null @@ -1,3 +0,0 @@ - - -<%= stylesheet_pack_tag "decidim_admin_decidim_awesome", media: "all" %> diff --git a/app/packs/entrypoints/decidim_admin_decidim_awesome.js b/app/packs/entrypoints/decidim_admin_decidim_awesome.js index c88a74714..27ad6e4a0 100644 --- a/app/packs/entrypoints/decidim_admin_decidim_awesome.js +++ b/app/packs/entrypoints/decidim_admin_decidim_awesome.js @@ -1,6 +1,5 @@ +// DO NOT include any javascript file here, but inside the following file instead import "src/decidim/decidim_awesome/awesome_admin"; -// This is needed by custom fields builder but if loader there duplicates the jQuery inclusion -import "jquery-ui/ui/widgets/sortable"; // CSS import "entrypoints/decidim_admin_decidim_awesome.scss"; diff --git a/app/packs/entrypoints/decidim_admin_decidim_awesome_custom_fields.js b/app/packs/entrypoints/decidim_admin_decidim_awesome_custom_fields.js deleted file mode 100644 index c20b35daa..000000000 --- a/app/packs/entrypoints/decidim_admin_decidim_awesome_custom_fields.js +++ /dev/null @@ -1,2 +0,0 @@ -import "src/decidim/decidim_awesome/proposals/custom_fields" -import "src/decidim/decidim_awesome/admin/custom_fields_builder" diff --git a/app/packs/entrypoints/decidim_admin_decidim_awesome_global.js b/app/packs/entrypoints/decidim_admin_decidim_awesome_global.js new file mode 100644 index 000000000..c1d71f2c6 --- /dev/null +++ b/app/packs/entrypoints/decidim_admin_decidim_awesome_global.js @@ -0,0 +1,4 @@ +import "src/decidim/decidim_awesome/awesome_admin_global"; + +// CSS +import "entrypoints/decidim_admin_decidim_awesome_global.scss"; diff --git a/app/packs/entrypoints/decidim_admin_decidim_awesome_global.scss b/app/packs/entrypoints/decidim_admin_decidim_awesome_global.scss new file mode 100644 index 000000000..8c90745b6 --- /dev/null +++ b/app/packs/entrypoints/decidim_admin_decidim_awesome_global.scss @@ -0,0 +1 @@ +@import "stylesheets/decidim/decidim_awesome/awesome_admin_global"; diff --git a/app/packs/entrypoints/decidim_decidim_awesome.js b/app/packs/entrypoints/decidim_decidim_awesome.js index 54de74ed5..d936aef2b 100644 --- a/app/packs/entrypoints/decidim_decidim_awesome.js +++ b/app/packs/entrypoints/decidim_decidim_awesome.js @@ -1,9 +1,9 @@ import "src/decidim/decidim_awesome/awesome_application.js" -// Images +// // Images require.context("../images", true) -// CSS +// // CSS import "entrypoints/decidim_decidim_awesome.scss"; diff --git a/app/packs/entrypoints/decidim_decidim_awesome_admin_form_exit_warn.js b/app/packs/entrypoints/decidim_decidim_awesome_admin_form_exit_warn.js deleted file mode 100644 index 0b95fc9cf..000000000 --- a/app/packs/entrypoints/decidim_decidim_awesome_admin_form_exit_warn.js +++ /dev/null @@ -1 +0,0 @@ -import "src/decidim/decidim_awesome/admin/form_exit_warn" diff --git a/app/packs/entrypoints/decidim_decidim_awesome_map.scss b/app/packs/entrypoints/decidim_decidim_awesome_map.scss index 004601b61..965a157e1 100644 --- a/app/packs/entrypoints/decidim_decidim_awesome_map.scss +++ b/app/packs/entrypoints/decidim_decidim_awesome_map.scss @@ -1 +1 @@ -@import "stylesheets/decidim/decidim_awesome/awesome_map/map" +@import "stylesheets/decidim/decidim_awesome/awesome_map/map"; diff --git a/app/packs/entrypoints/decidim_editor.js b/app/packs/entrypoints/decidim_editor.js new file mode 100644 index 000000000..83935a131 --- /dev/null +++ b/app/packs/entrypoints/decidim_editor.js @@ -0,0 +1,14 @@ +import createEditor from "src/decidim/decidim_awesome/editor"; +import DecidimKit from "src/decidim/editor/extensions/decidim_kit"; + +// CSS +import "stylesheets/decidim/editor.scss" + +window.DecidimKit = DecidimKit; +window.currentEditors = window.currentEditors || []; + +window.createEditor = (container) => { + let editor = createEditor(container); + window.currentEditors.push(editor); + return editor; +} diff --git a/app/packs/src/decidim/decidim_awesome/admin/auto_edit.js b/app/packs/src/decidim/decidim_awesome/admin/auto_edit.js index faa9fc668..ecc5f89c9 100644 --- a/app/packs/src/decidim/decidim_awesome/admin/auto_edit.js +++ b/app/packs/src/decidim/decidim_awesome/admin/auto_edit.js @@ -1,82 +1,109 @@ -$(() => { +document.addEventListener("DOMContentLoaded", () => { let CustomFieldsBuilders = window.CustomFieldsBuilders || []; - $("body").on("click", "a.awesome-auto-edit", (ev) => { - ev.preventDefault(); - const $link = $(ev.currentTarget); - const scope = $link.data("scope"); - const $target = $(`span.awesome-auto-edit[data-scope="${scope}"]`); - const $constraints = $(`.constraints-editor[data-key="${scope}"]`); - if ($target.length === 0) { - return; - } + document.querySelectorAll("a.awesome-auto-edit").forEach((link) => { + link.addEventListener("click", (ev) => { + ev.preventDefault(); + const scope = link.dataset.scope; + const target = document.querySelector(`span.awesome-auto-edit[data-scope="${scope}"]`); + const constraints = document.querySelector(`.constraints-editor[data-key="${scope}"]`); - const key = $target.data("key"); - const attribute = $target.data("var"); - const $hidden = $(`[name="config[${attribute}][${key}]"]`); - const $multiple = $(`[name="config[${attribute}][${key}][]"]`); - const $container = $(`.${attribute}_container[data-key="${key}"]`); - const $delete = $(".delete-box", $container); - - const rebuildLabel = (text, withScope) => { - $target.text(text); - $target.attr("data-key", text); - $target.data("key", text); - if (withScope) { - $target.attr("data-scope", withScope); - $target.data("scope", withScope); - $link.attr("data-scope", withScope); - $link.data("scope", withScope); + if (!target) { + return; } - $link.show(); - }; - const rebuildHmtl = (result) => { - rebuildLabel(result.key, result.scope); - $constraints.replaceWith(result.html); - // update hidden input if exists - $hidden.attr("name", `config[${attribute}][${result.key}]`); - $multiple.attr("name", `config[${attribute}][${result.key}][]`); - $container.data("key", result.key); - $container.attr("data-key", result.key); - $delete.attr("href", $delete.attr("href").replace(`key=${key}`, `key=${result.key}`)) - CustomFieldsBuilders.forEach((builder) => { - if (builder.key === key) { - builder.key = result.key; + const key = target.dataset.key; + const attribute = target.dataset.var; + const inputField = document.querySelector(`[name="config[${attribute}][${key}]"]`); + const multipleField = document.querySelector(`[name="config[${attribute}][${key}][]"]`); + const container = document.querySelector(`.js-box-container[data-key="${key}"]`); + const deleteBox = container.querySelector(".awesome-auto-delete"); + + const rebuildLabel = (text, withScope) => { + target.innerText = text; + target.dataset.key = text; + if (withScope) { + target.dataset.scope = withScope; + link.dataset.scope = withScope; } - }); - }; + link.style.display = ""; + }; + + const rebuildHtml = (result) => { + rebuildLabel(result.key, result.scope); + constraints.outerHTML = result.html; + if (inputField) { + inputField.setAttribute("name", `config[${attribute}][${result.key}]`); + } + if (multipleField) { + multipleField.setAttribute("name", `config[${attribute}][${result.key}][]`); + } + container.dataset.key = result.key; + container.setAttribute("data-key", result.key); + deleteBox.setAttribute("href", deleteBox.getAttribute("href").replace(`key=${key}`, `key=${result.key}`)); + CustomFieldsBuilders.forEach((builder) => { + if (builder.key === key) { + builder.key = result.key; + } + }); + // Reinitialize Decidim DOM events + // console.log("Reinitializing Decidim DOM events", "constraints", constraints, "container", container); + // Remove existing dialogs + Reflect.deleteProperty(window.Decidim.currentDialogs, `edit-modal-${scope}`); + Reflect.deleteProperty(window.Decidim.currentDialogs, `new-modal-${scope}`); + const editModal = document.getElementById(`edit-modal-${result.scope}`); + const newModal = document.getElementById(`new-modal-${result.scope}`); + if (container) { + // reloads dialogs (modals) + document.dispatchEvent(new CustomEvent("ajax:loaded", { detail: container })); + } + // Rebuild the manual handling of remote modals + document.dispatchEvent(new CustomEvent("ajax:loaded:modals", { detail: [editModal, newModal] })); + }; - $target.html(``); - const $input = $(`input.awesome-auto-edit[data-scope="${scope}"]`); - $link.hide(); - $input.select(); - $input.on("keypress", (evt) => { - if (evt.code === "Enter" || evt.code === "13" || evt.code === "10") { - evt.preventDefault(); - $.ajax( - { - type: "POST", - url: window.DecidimAwesome.rename_scope_label_path, - dataType: "json", + target.innerHTML = ``; + const input = target.querySelector(`input.awesome-auto-edit[data-scope="${scope}"]`); + link.style.display = "none"; + input.focus(); + let config = {}; + config[attribute] = true; + let token = document.querySelector('meta[name="csrf-token"]'); + input.addEventListener("keypress", (evt) => { + if (evt.key === "Enter" || evt.keyCode === 13 || evt.keyCode === 10) { + if (key === input.value) { + rebuildLabel(key); + return; + } + // console.log("Saving key", key, "to", input.value, "with scope", scope); + evt.preventDefault(); + fetch(window.DecidimAwesome.renameScopeLabelPath, { + method: "POST", headers: { - "X-CSRF-Token": $("meta[name=csrf-token]").attr("content") + "Accept": "application/json, text/plain, */*", + "Content-Type": "application/json", + "X-CSRF-Token": token && token.getAttribute("content") }, - data: { key: key, - scope: scope, - attribute: attribute, - text: $input.val() - } + body: JSON.stringify({ key: key, scope: scope, attribute: attribute, text: input.value, config: config }) }). - done((result) => rebuildHmtl(result)). - fail((err) => { - console.error("Error saving key", key, "ERR:", err); - rebuildLabel(key); - }); - } - }); - $input.on("blur", () => { - rebuildLabel(key); + then((response) => { + if (!response.ok) { + throw response; + } + return response.json() + }). + then((result) => { + rebuildHtml(result) + }). + catch((err) => { + console.error("Error saving key", key, "ERR:", err); + rebuildLabel(key); + }); + } + }); + + input.addEventListener("blur", () => { + rebuildLabel(key); + }); }); }); }); diff --git a/app/packs/src/decidim/decidim_awesome/admin/check_redirections.js b/app/packs/src/decidim/decidim_awesome/admin/check_redirections.js index 97a11b66a..1bc906180 100644 --- a/app/packs/src/decidim/decidim_awesome/admin/check_redirections.js +++ b/app/packs/src/decidim/decidim_awesome/admin/check_redirections.js @@ -1,48 +1,53 @@ -$(() => { - $(".check-custom-redirections").on("click", (evt) => { - evt.preventDefault(); - - if ($(evt.target).hasClass("disabled")) { - return; - } - - $(evt.target).addClass("disabled"); - - const getReport = (tr, response) => { - const item = $(tr).data("item"); - const $td = $(tr).find(".redirect-status"); - - let type = response.type; - let status = response.status; - if (response.type === "opaqueredirect") { - type = "redirect"; - status = "302"; +document.addEventListener("DOMContentLoaded", () => { + const checkCustomRedirections = document.querySelector(".check-custom-redirections"); + + if (checkCustomRedirections) { + checkCustomRedirections.addEventListener("click", (evt) => { + evt.preventDefault(); + + if (evt.target.classList.contains("disabled")) { + return; } - if (item.active) { - if (type === "redirect") { - $td.addClass("success"); + evt.target.classList.add("disabled"); + + const getReport = (tr, response) => { + const item = JSON.parse(tr.dataset.item); + const td = tr.querySelector(".redirect-status"); + + let type = response.type; + let status = response.status; + if (response.type === "opaqueredirect") { + type = "redirect"; + status = "302"; + } + + if (item.active) { + if (type === "redirect") { + td.classList.add("text-success"); + } else { + td.classList.add("text-alert"); + } } else { - $td.addClass("alert"); + td.classList.add("text-gray"); } - } else { - $td.addClass("muted"); - } - return `${type} (${status})`; - }; - - $("tr.custom-redirection").each((index, tr) => { - const $td = $(tr).find(".redirect-status"); - $td.html(''); - fetch($(tr).data("origin"), {method: "HEAD", redirect: "manual"}). - then((response) => { - $td.html(getReport(tr, response)) - }). - catch((error) => { - console.error("ERROR", error) - $td.removeClass("loading"); - }); + return `${type} (${status})`; + }; + + document.querySelectorAll("tr.custom-redirection").forEach((tr) => { + const td = tr.querySelector(".redirect-status"); + td.innerHTML = ''; + + fetch(tr.dataset.origin, { method: "HEAD", redirect: "manual" }). + then((response) => { + td.innerHTML = getReport(tr, response); + }). + catch((error) => { + console.error("ERROR", error); + td.classList.remove("loading"); + }); + }); }); - }); + } }); diff --git a/app/packs/src/decidim/decidim_awesome/admin/codemirror.js b/app/packs/src/decidim/decidim_awesome/admin/codemirror.js index 6bcb20566..fea63f5ae 100644 --- a/app/packs/src/decidim/decidim_awesome/admin/codemirror.js +++ b/app/packs/src/decidim/decidim_awesome/admin/codemirror.js @@ -1,10 +1,10 @@ import CodeMirror from "codemirror" import "codemirror/mode/css/css" import "codemirror/keymap/sublime" -import "stylesheets/decidim/decidim_awesome/admin/codemirror.scss"; +import "codemirror/lib/codemirror.css"; -$(() => { - $(".awesome-edit-config .scoped_styles_container textarea").each((_idx, el) => { +document.addEventListener("DOMContentLoaded", () => { + document.querySelectorAll(".awesome-edit-config .scoped_styles_container textarea").forEach((el) => { CodeMirror.fromTextArea(el, { lineNumbers: true, mode: "css", diff --git a/app/packs/src/decidim/decidim_awesome/admin/constraint_form_events.js b/app/packs/src/decidim/decidim_awesome/admin/constraint_form_events.js new file mode 100644 index 000000000..46c1dd423 --- /dev/null +++ b/app/packs/src/decidim/decidim_awesome/admin/constraint_form_events.js @@ -0,0 +1,115 @@ +/* eslint no-use-before-define: "off" */ + +// This script manually handles the "edit" button from the constraints editor by loading the content into the modal after the modal (operated by Dialog) is opened. +// We don't use the RemoteModal class because it chaches the modal content and the "fetch" operation does not specify the "no-cache" headers. + +const fetchConstraints = (url, callback = () => {}) => { + fetch(url, { cache: "no-cache" }). + then((res) => { + if (!res.ok) { + throw res; + } + return res.text(); + }). + then((text) => { + callback(text); + }). + catch((err) => { + console.error("dialog open failed", err); + }); +}; + +const renderModal = (element, html) => { + element.innerHTML = html; + bindModalEvents(element) +}; + +const constraintChange = (modalId, data) => { + const modal = window.Decidim.currentDialogs[modalId]; + const constraintsUrl = modal.openingTrigger.dataset.constraintsUrl; + + // Prepare parameters to request the modal content again, but updated based on the user selections + const vars = data.map((setting) => `${setting.key}=${setting.value}`); + const url = `${constraintsUrl}&${vars.join("&")}`; + + fetchConstraints(url, (res) => renderModal(modal.dialog, res)); +}; + +const bindModalEvents = (detail) => { + const div = detail.querySelector("[id^=constraint-form]"); + const spaceManifest = div.getElementsByTagName("select")[0]; + const spaceSlug = div.getElementsByTagName("select")[1]; + const componentManifest = div.getElementsByTagName("select")[2]; + const componentId = div.getElementsByTagName("select")[3]; + + spaceManifest.addEventListener("change", (event) => { + constraintChange(detail.id, [{ + key: "participatory_space_manifest", + value: event.target.value + }]) + }); + + spaceSlug.addEventListener("change", (event) => { + constraintChange(detail.id, [{ + key: "participatory_space_manifest", + value: spaceManifest.value + }, { + key: "participatory_space_slug", + value: event.target.value + }]) + }); + + // Component manfiest and component id are mutually exclusive + componentManifest.addEventListener("change", (event) => { + if (event.target.value) { + componentId.value = ""; + } + }); + + componentId.addEventListener("change", (event) => { + if (event.target.value) { + componentManifest.value = ""; + } + }); +}; + + +const initializeDialog = (dialog) => { + dialog.addEventListener("open.dialog", async (el) => { + const currentDialog = window.Decidim.currentDialogs[el.target.id]; + const button = currentDialog && currentDialog.openingTrigger; + const url = button.dataset.constraintsUrl; + // console.log("open.dialog", el, url, "currentDialog",currentDialog); + fetchConstraints(url, (res) => renderModal(el.target, res)); + }); +}; + +document.addEventListener("DOMContentLoaded", () => { + document.querySelectorAll("[data-constraint][data-dialog]").forEach((dialog) => { + initializeDialog(dialog); + }); +}); + +document.addEventListener("ajax:loaded:modals", (event) => { + event.detail.forEach((modal) => initializeDialog(modal)); +}); + +// Rails AJAX events, this will update the parent page constrains +document.body.addEventListener("ajax:error", (responseText) => { + // console.log("ajax:error", responseText) + const container = document.querySelector(`.constraints-editor[data-key="${responseText.detail[0].key}"]`); + const callout = container.querySelector(".flash"); + callout.hidden = false; + callout.classList.add("alert"); + callout.getElementsByTagName("p")[0].innerHTML = `${responseText.detail[0].message}: ${responseText.detail[0].error}`; +}); + +document.body.addEventListener("ajax:success", (responseText) => { + // console.log("ajax:success", responseText) + let container = document.querySelector(`.constraints-editor[data-key="${responseText.detail[0].key}"]`); + const callout = container.querySelector(".flash"); + callout.hidden = false; + callout.classList.add("success"); + callout.getElementsByTagName("p")[0].innerHTML = responseText.detail[0].message; + container.outerHTML = responseText.detail[0].html; +}); diff --git a/app/packs/src/decidim/decidim_awesome/admin/constraints.js b/app/packs/src/decidim/decidim_awesome/admin/constraints.js deleted file mode 100644 index d8c88ce05..000000000 --- a/app/packs/src/decidim/decidim_awesome/admin/constraints.js +++ /dev/null @@ -1,55 +0,0 @@ -$(() => { - const $modal = $("#constraintsModal"); - if (!$modal.length) { - return; - } - - $(".decidim_awesome-form").on("click", ".constraints-editor .add-condition,.constraints-editor .edit-condition", (evt) => { - evt.preventDefault(); - const $this = $(evt.target) - const url = $this.attr("href"); - const $callout = $this.closest(".constraints-editor").find(".callout"); - $callout.hide(); - $callout.removeClass("alert success"); - $modal.find(".modal-content").html(""); - $modal.addClass("loading"); - $modal.data("url", url); - $modal.foundation("open"); - $modal.find(".modal-content").load(url, () => { - $modal.removeClass("loading"); - }); - }); - - // Custom event listener to reload the modal if needed - document.body.addEventListener("constraint:change", (evt) => { - const vars = evt.detail.map((setting) => `${setting.key}=${setting.value}`); - const url = `${$modal.data("url")}&${vars.join("&")}`; - // console.log("constraint:change vars:", vars, "url:", url) - $modal.addClass("loading"); - $modal.find(".modal-content").load(url, () => { - $modal.removeClass("loading"); - }); - }); - - // Rails AJAX events - document.body.addEventListener("ajax:error", (responseText) => { - // console.log("ajax:error", responseText) - const $container = $(`.constraints-editor[data-key="${responseText.detail[0].key}"]`) - const $callout = $container.find(".callout"); - $callout.show(); - $callout.contents("p").html(`${responseText.detail[0].message}: ${responseText.detail[0].error}`); - $callout.addClass("alert"); - }); - - document.body.addEventListener("ajax:success", (responseText) => { - // console.log("ajax:success", responseText) - const $container = $(`.constraints-editor[data-key="${responseText.detail[0].key}"]`) - const $callout = $container.find(".callout"); - $modal.foundation("close"); - $callout.show(); - $callout.contents("p").html(responseText.detail[0].message); - $callout.addClass("success"); - // reconstruct list - $container.replaceWith(responseText.detail[0].html); - }); -}); diff --git a/app/packs/src/decidim/decidim_awesome/admin/custom_fields_builder.js b/app/packs/src/decidim/decidim_awesome/admin/custom_fields_builder.js index 2356aeb9b..c06c8cc6b 100644 --- a/app/packs/src/decidim/decidim_awesome/admin/custom_fields_builder.js +++ b/app/packs/src/decidim/decidim_awesome/admin/custom_fields_builder.js @@ -1,21 +1,26 @@ -require("formBuilder/dist/form-builder.min.js") +import "formBuilder/dist/form-builder.min.js"; import "src/decidim/decidim_awesome/forms/rich_text_plugin" +// formBuilder uses jquery-ui-sortable which is a very dirty npm package with no neat source code available, and causes problems with the webpacker configuration of Decidim. +// For the moment, we'll remove the sortable functionality with a dummy jQuery plugin until we find another sortable plugin (or keep it disabled for good) +jQuery.fn.sortable = () => {} window.CustomFieldsBuilders = window.CustomFieldsBuilders || []; $(() => { $(".awesome-edit-config .proposal_custom_fields_editor").each((_idx, el) => { const key = $(el).closest(".proposal_custom_fields_container").data("key"); + const configVar = $(el).closest(".proposal_custom_fields_container").data("var"); // DOCS: https://formbuilder.online/docs window.CustomFieldsBuilders.push({ el: el, key: key, + var: configVar, config: { i18n: { locale: "en-US", location: "https://cdn.jsdelivr.net/npm/formbuilder-languages@1.1.0/" }, - formData: $(`input[name="config[proposal_custom_fields][${key}]"]`).val(), + formData: $(`input[name="config[${configVar}][${key}]"]`).val(), disableFields: ["button", "file"], disabledActionButtons: ["save", "data", "clear"], disabledAttrs: [ @@ -37,7 +42,7 @@ $(() => { ], disabledSubtypes: { // default color as it generate hashtags in decidim (TODO: fix hashtag generator with this) - text: ["color"], + text: ["color"], // disable default wysiwyg editors as they present problems textarea: ["tinymce", "quill"] } @@ -71,8 +76,7 @@ $(() => { $("form.awesome-edit-config").on("submit", () => { window.CustomFieldsBuilders.forEach((builder) => { - $(`input[name="config[proposal_custom_fields][${builder.key}]"]`).val(builder.instance.actions.getData("json")); + $(`input[name="config[${builder.var}][${builder.key}]"]`).val(builder.instance.actions.getData("json")); }); }); }); - diff --git a/app/packs/src/decidim/decidim_awesome/admin/form_exit_warn.js b/app/packs/src/decidim/decidim_awesome/admin/form_exit_warn.js index f8810f245..83b1064ec 100644 --- a/app/packs/src/decidim/decidim_awesome/admin/form_exit_warn.js +++ b/app/packs/src/decidim/decidim_awesome/admin/form_exit_warn.js @@ -1,21 +1,32 @@ -$(() => { - const $form = $("form.awesome-edit-config"); - if ($form.length > 0) { - $form.find("input, textarea, select").on("change", () => { - $form.data("changed", true); +document.addEventListener("DOMContentLoaded", () => { + const form = document.querySelector("form.awesome-edit-config"); + if (form) { + form.querySelectorAll("input, textarea, select").forEach((el) => { + el.addEventListener("change", () => { + form.dataset.changed = true; + }); }); - const safePath = $form.data("safe-path").split("?")[0]; - $(document).on("click", "a", (event) => { - window.exitUrl = event.currentTarget.href; + const safePath = form.dataset.safePath.split("?")[0]; + document.querySelectorAll("a").forEach((el) => { + el.addEventListener("click", () => { + window.exitUrl = el.href; + }); }); - $(document).on("submit", "form", (event) => { - window.exitUrl = event.currentTarget.action; + document.querySelectorAll("form").forEach((el) => { + el.addEventListener("submit", () => { + window.exitUrl = el.action; + }); + }); + document.querySelectorAll('[type="submit"]').forEach((el) => { + el.addEventListener("click", () => { + window.exitUrl = el.form.action; + }); }); window.addEventListener("beforeunload", (event) => { const exitUrl = window.exitUrl; - const hasChanged = $form.data("changed"); + const hasChanged = form.dataset.changed; window.exitUrl = null; if (!hasChanged || (exitUrl && exitUrl.includes(safePath))) { diff --git a/app/packs/src/decidim/decidim_awesome/admin/proposal_sortings.js b/app/packs/src/decidim/decidim_awesome/admin/proposal_sortings.js index d0b909506..48177a90e 100644 --- a/app/packs/src/decidim/decidim_awesome/admin/proposal_sortings.js +++ b/app/packs/src/decidim/decidim_awesome/admin/proposal_sortings.js @@ -1,13 +1,31 @@ -import "select2" +/* eslint-disable no-new */ -$(() => { - const $select = $("#config_additional_proposal_sortings"); - $select.select2({ - theme: "foundation" +import TomSelect from "tom-select/dist/cjs/tom-select.popular"; + +document.addEventListener("DOMContentLoaded", () => { + const selectContainer = document.getElementById("config_additional_proposal_sortings"); + + if (!selectContainer) { + return; + } + + new TomSelect(selectContainer, { + plugins: ["remove_button", "dropdown_input"], + create: false, + render: { + option: function (data, escape) { + return `
${escape(data.text)}
`; + }, + item: function (data, escape) { + return Boolean(data.is_admin) || data.isAdmin === "true" + ? `
${escape(data.text)}
` + : `
${escape(data.text)}
`; + } + } }); - $("#additional_proposal_sortings-enable-all").on("click", (evt) => { + + document.getElementById("additional_proposal_sortings-enable-all").addEventListener("click", (evt) => { evt.preventDefault(); - $select.find("option").prop("selected", true); - $select.trigger("change"); + selectContainer.tomselect.setValue(Array.from(document.getElementById("config_additional_proposal_sortings").children).map((el) => el.value)) }); }); diff --git a/app/packs/src/decidim/decidim_awesome/admin/tabs_change.js b/app/packs/src/decidim/decidim_awesome/admin/tabs_change.js new file mode 100644 index 000000000..00bd57473 --- /dev/null +++ b/app/packs/src/decidim/decidim_awesome/admin/tabs_change.js @@ -0,0 +1,31 @@ +/** + * When switching tabs in i18n fields, autofocus on the markdown if exists + */ +$(() => { + const reloadCustomFields = ($input) => { + // saves current data to the hidden field for the lang + window.DecidimAwesome.CustomFieldsRenderer.storeData(); + // init the current language + window.DecidimAwesome.CustomFieldsRenderer.init($input); + }; + + // Event launched by foundation using jQuery (still used in the admin in v0.28, probably removed in the future) + $("[data-tabs]").on("change.zf.tabs", (event) => { + const $container = $(event.target).closest(".label--tabs").next(".tabs-content").find(".tabs-panel.is-active"); + // fix custom fields if present + const $input = $container.find(".proposal_custom_field:first"); + if ($input.length > 0) { + reloadCustomFields($input); + } + }); + + // if more than 3 languages, a select is used + $("#proposal-body-tabs").on("change", (event) => { + const $container = $($(event.target).val()); + // fix custom fields if present + const $input = $container.find(".proposal_custom_field:first"); + if ($input.length > 0) { + reloadCustomFields($input); + } + }); +}); diff --git a/app/packs/src/decidim/decidim_awesome/admin/user_picker.js b/app/packs/src/decidim/decidim_awesome/admin/user_picker.js index 9f3a1dfcd..be2ccbff8 100644 --- a/app/packs/src/decidim/decidim_awesome/admin/user_picker.js +++ b/app/packs/src/decidim/decidim_awesome/admin/user_picker.js @@ -1,25 +1,38 @@ -/* eslint-disable no-invalid-this */ -import "select2" -import "stylesheets/decidim/decidim_awesome/admin/user_picker.scss" +import TomSelect from "tom-select/dist/cjs/tom-select.popular"; -$(() => { - $("select.multiusers-select").each(function() { - const url = $(this).attr("data-url"); - $(this).select2({ - ajax: { - url: url, - delay: 100, - dataType: "json", - processResults: (data) => { - return { - results: data - } - } +document.addEventListener("DOMContentLoaded", () => { + const tagContainers = document.querySelectorAll(".multiusers-select"); + const config = (element) => ({ + plugins: ["remove_button", "dropdown_input"], + create: false, + render: { + option: function (data, escape) { + return `
${escape(data.text)}
`; }, - escapeMarkup: (markup) => markup, - templateSelection: (item) => `${item.text}`, - minimumInputLength: 1, - theme: "foundation" - }); + item: function (data, escape) { + return Boolean(data.is_admin) || data.isAdmin === "true" + ? `
${escape(data.text)}
` + : `
${escape(data.text)}
`; + } + }, + shouldLoad: function (query) { + return query.length > 1; + }, + load: function (query, callback) { + const { url } = element.dataset; + const join = url.includes("?") + ? "&" + : "?"; + const params = new URLSearchParams({ + term: query + }); + + fetch(`${url}${join}${params}`). + then((response) => response.json()). + then((json) => callback(json)). + catch(() => callback()); + } }); + + tagContainers.forEach((container) => new TomSelect(container, config(container))); }); diff --git a/app/packs/src/decidim/decidim_awesome/amendments/show_modal_on_limits.js b/app/packs/src/decidim/decidim_awesome/amendments/show_modal_on_limits.js index 3b18bc470..a9b4b23a5 100644 --- a/app/packs/src/decidim/decidim_awesome/amendments/show_modal_on_limits.js +++ b/app/packs/src/decidim/decidim_awesome/amendments/show_modal_on_limits.js @@ -1,30 +1,28 @@ -$(() => { - const $modal = $("#LimitAmendmentsModal"); - if ($modal.length === 0 || $(".sign-out-link").length === 0) { +document.addEventListener("DOMContentLoaded", () => { + const modalId = "LimitAmendmentsModal"; + const modalEl = document.getElementById(modalId); + const amendButton = document.getElementById("amend-button"); + const limitAmendments = modalEl && JSON.parse(modalEl.dataset.limitAmendments); + + if (!amendButton || !limitAmendments || document.querySelector('a[href^="/users/sign_in"]')) { return; } - const showModal = () => { - if ($modal.is(":visible")) { - return false; - } - - if ($modal.data("limitAmendments")) { - return true; - } - - return false; - }; - - $modal.find("a").on("click", () => { - $modal.foundation("close"); + modalEl.querySelectorAll("a").forEach((aEl) => { + aEl.addEventListener("click", () => { + window.Decidim.currentDialogs[modalId].close(); + }); }); - $(".card__amend-button").on("click", ".amend_button_card_cell", (evt) => { - if (showModal()) { - evt.preventDefault(); - evt.stopPropagation(); - $modal.foundation("open"); + /** + * Determines if the modal should be displayed based on its current state and data attributes. + */ + amendButton.addEventListener("click", (event) => { + const modal = window.Decidim.currentDialogs[modalId]; + if (modal) { + event.preventDefault(); + event.stopPropagation(); + modal.open(); } }); }); diff --git a/app/packs/src/decidim/decidim_awesome/awesome_admin.js b/app/packs/src/decidim/decidim_awesome/awesome_admin.js index e29d4ffb0..448147378 100644 --- a/app/packs/src/decidim/decidim_awesome/awesome_admin.js +++ b/app/packs/src/decidim/decidim_awesome/awesome_admin.js @@ -1,24 +1,13 @@ -// Webpack seems to "forgget" about certain libraries already being loaded -// if javascript_pack_tag is called two times, let's include the whole Decidim admin here instead -import "entrypoints/decidim_admin" // Custom scripts for awesome -import "src/decidim/decidim_awesome/admin/constraints" +import "src/decidim/decidim_awesome/admin/constraint_form_events" import "src/decidim/decidim_awesome/admin/auto_edit" import "src/decidim/decidim_awesome/admin/user_picker" import "src/decidim/decidim_awesome/admin/proposal_sortings" -import "src/decidim/decidim_awesome/editors/tabs_focus" import "src/decidim/decidim_awesome/admin/codemirror" import "src/decidim/decidim_awesome/admin/check_redirections" +import "src/decidim/decidim_awesome/admin/form_exit_warn" -import {destroyQuillEditor, createQuillEditor, createMarkdownEditor} from "src/decidim/decidim_awesome/editors/editor" +import "src/decidim/decidim_awesome/proposals/custom_fields" +import "src/decidim/decidim_awesome/admin/custom_fields_builder" -$(() => { - $(".editor-container").each((_idx, container) => { - destroyQuillEditor(container); - if (window.DecidimAwesome.use_markdown_editor) { - createMarkdownEditor(container); - } else { - createQuillEditor(container); - } - }); -}); +window.DecidimAwesome = window.DecidimAwesome || {}; diff --git a/app/packs/src/decidim/decidim_awesome/awesome_admin_global.js b/app/packs/src/decidim/decidim_awesome/awesome_admin_global.js new file mode 100644 index 000000000..04ad8afcb --- /dev/null +++ b/app/packs/src/decidim/decidim_awesome/awesome_admin_global.js @@ -0,0 +1 @@ +import "src/decidim/decidim_awesome/admin/tabs_change" diff --git a/app/packs/src/decidim/decidim_awesome/awesome_application.js b/app/packs/src/decidim/decidim_awesome/awesome_application.js index 713508f9b..457d56eaa 100644 --- a/app/packs/src/decidim/decidim_awesome/awesome_application.js +++ b/app/packs/src/decidim/decidim_awesome/awesome_application.js @@ -2,18 +2,3 @@ import "src/decidim/decidim_awesome/proposals/images" import "src/decidim/decidim_awesome/forms/autosave" import "src/decidim/decidim_awesome/voting/voting_cards" import "src/decidim/decidim_awesome/amendments/show_modal_on_limits" -import {destroyQuillEditor, createQuillEditor, createMarkdownEditor} from "src/decidim/decidim_awesome/editors/editor" - -$(() => { - // rebuild editors - if (window.DecidimAwesome.allow_images_in_full_editor || window.DecidimAwesome.allow_images_in_small_editor || window.DecidimAwesome.use_markdown_editor) { - $(".editor-container").each((_idx, container) => { - destroyQuillEditor(container); - if (window.DecidimAwesome.use_markdown_editor) { - createMarkdownEditor(container); - } else { - createQuillEditor(container); - } - }); - } -}); diff --git a/app/packs/src/decidim/decidim_awesome/awesome_map/api/fetcher.js b/app/packs/src/decidim/decidim_awesome/awesome_map/api/fetcher.js index c73fbb05d..148dfe61a 100644 --- a/app/packs/src/decidim/decidim_awesome/awesome_map/api/fetcher.js +++ b/app/packs/src/decidim/decidim_awesome/awesome_map/api/fetcher.js @@ -30,7 +30,7 @@ export default class Fetcher { api.fetchAll((result) => { if (result) { const collection = result.component[this.collection]; - // console.log("collection",collection) + // console.log("collection", collection) collection.edges.forEach((element) => { let node = element.node; @@ -56,14 +56,15 @@ export default class Fetcher { } decorateNode(node) { - const body = this.findTranslation(node.body.translations); + const body = this.findTranslation(node.body.translations) const title = this.findTranslation(node.title.translations); node.hashtags = this.collectHashtags(title); node.hashtags = node.hashtags.concat(this.collectHashtags(body)); // hashtags in the title look ugly, lets replace the gid:... structure with the tag #name node.title.translation = this.replaceHashtags(title, node.hashtags); - node.body.translation = this.appendHtmlHashtags(this.truncate(this.removeHashtags(body)).replace(/\n/g, "
"), node.hashtags); - node.link = `${this.controller.component.url}/${this.collection}/${node.id}`; + node.body.translation = this.appendHtmlHashtags(this.truncate(this.removeHashtags(body)), node.hashtags); + // console.log("decorateNode", node.title.translation, "BODY", body, "translation", node.body.translation, node.hashtags) + node.link = `${this.controller.component.url}/${node.id}`; } findTranslation(translations) { @@ -83,7 +84,7 @@ export default class Fetcher { collectHashtags(text) { let tags = []; if (text) { - const gids = text.match(/gid:\/\/[^\s<&]+/g) + const gids = text.match(/gid:\/\/[^\s<&,;]+/g) if (gids) { tags = gids.filter((gid) => gid.indexOf("/Decidim::Hashtag/") !== -1).map((gid) => { const parts = gid.split("/"); @@ -117,18 +118,39 @@ export default class Fetcher { } removeHashtags(text) { - return text.replace(/gid:\/\/[^\s<&]+/g, ""); + return text.replace(/gid:\/\/[^\s<&,;]+/g, ""); } appendHtmlHashtags(txt, tags) { - let text = txt; - tags.forEach((tag) => { - text += ` ${tag.html}`; - }); - return text; + let string = tags.reduce((accumulator, tag) => (accumulator + ? `${accumulator} ${tag.html}` + : tag.html), ""); + if (string) { + return `${txt}

${string}

`; + } + return txt; + } truncate(html) { return $.truncate(html, this.config); } + + formatDateRange(startDate, endDate) { + // Check if either startDate or endDate is blank + if (!startDate || !endDate) { + return ""; + } + + // Convert startDate and endDate to JavaScript Date objects + const start = new Date(startDate); + const end = new Date(endDate); + + const date = Intl.DateTimeFormat(window.DecidimAwesome.currentLocale, { // eslint-disable-line new-cap + year: "numeric", + month: "short", + day: "numeric" + }); + return date.formatRange(start, end); + } } diff --git a/app/packs/src/decidim/decidim_awesome/awesome_map/api/meetings_fetcher.js b/app/packs/src/decidim/decidim_awesome/awesome_map/api/meetings_fetcher.js index 71588c8a6..81a4c2048 100644 --- a/app/packs/src/decidim/decidim_awesome/awesome_map/api/meetings_fetcher.js +++ b/app/packs/src/decidim/decidim_awesome/awesome_map/api/meetings_fetcher.js @@ -29,23 +29,13 @@ export default class MeetingsFetcher extends Fetcher { } } startTime - location { - translations { - text - locale - } - } + endTime address - locationHints { - translations { - text - locale - } - } coordinates { latitude longitude } + typeOfMeeting category { id } @@ -56,4 +46,11 @@ export default class MeetingsFetcher extends Fetcher { } }`; } + + decorateNode(node) { + super.decorateNode(node); + node.icon = window.AwesomeMapMeetingTypes[node.typeOfMeeting]; + node.meetingType = window.AwesomeMapMeetingTexts[node.typeOfMeeting]; + node.dateRange = this.formatDateRange(node.startTime, node.endTime); + } } diff --git a/app/packs/src/decidim/decidim_awesome/awesome_map/api/proposals_fetcher.js b/app/packs/src/decidim/decidim_awesome/awesome_map/api/proposals_fetcher.js index b8e9caa28..e268bcbe7 100644 --- a/app/packs/src/decidim/decidim_awesome/awesome_map/api/proposals_fetcher.js +++ b/app/packs/src/decidim/decidim_awesome/awesome_map/api/proposals_fetcher.js @@ -23,12 +23,18 @@ export default class ProposalsFetcher extends Fetcher { locale } } + author { + id + name + } body { translations { text locale } } + totalCommentsCount + endorsementsCount address coordinates { latitude @@ -49,4 +55,26 @@ export default class ProposalsFetcher extends Fetcher { } }`; } + + decorateNode(node) { + super.decorateNode(node); + node.authorName = node.author && node.author.name || window.DecidimAwesome.texts.officialAuthor; + node.humanState = window.AwesomeMapProposalTexts[node.state]; + switch (node.state) { + case "accepted": + node.stateClass = "success"; + break; + case "rejected": + case "withdrawn": + node.stateClass = "alert"; + break; + case "evaluating": + node.stateClass = "warning"; + break; + default: + node.stateClass = "muted"; + } + + node.isAmendment = () => (Boolean(this.controller.amendments[node.id])); + } } diff --git a/app/packs/src/decidim/decidim_awesome/awesome_map/awesome_map.js b/app/packs/src/decidim/decidim_awesome/awesome_map/awesome_map.js index 689874b36..7ca5b1790 100644 --- a/app/packs/src/decidim/decidim_awesome/awesome_map/awesome_map.js +++ b/app/packs/src/decidim/decidim_awesome/awesome_map/awesome_map.js @@ -1,11 +1,10 @@ import * as L from "leaflet"; // comes with Decidim -import "src/decidim/map/icon.js" import "src/decidim/vendor/leaflet-tilelayer-here" // Comes with Decidim -import "leaflet.markercluster"; +import "leaflet.markercluster"; // included in this package.json -import "leaflet.featuregroup.subgroup" +import "leaflet.featuregroup.subgroup" import "src/vendor/jquery.truncate" import "jsrender" @@ -64,7 +63,7 @@ export default class AwesomeMap { this.autoResize(); if (this.loading.length === 0) { - this.controls.$loading.hide(); + this.controls.loading.style.display = "none"; // call trigger as all loads are finished this.onFinished(); } diff --git a/app/packs/src/decidim/decidim_awesome/awesome_map/controllers/controller.js b/app/packs/src/decidim/decidim_awesome/awesome_map/controllers/controller.js index 8f723bd36..621a4cb0c 100644 --- a/app/packs/src/decidim/decidim_awesome/awesome_map/controllers/controller.js +++ b/app/packs/src/decidim/decidim_awesome/awesome_map/controllers/controller.js @@ -14,8 +14,8 @@ export default class Controller { } getLabel() { - let text = this.awesomeMap.config.menu.mergeComponents || this.component.name; - if (!text) { + let text = this.component.name; + if (!text || this.awesomeMap.config.menu.mergeComponents) { text = window.DecidimAwesome.texts[this.component.type]; } return `${text}` @@ -96,6 +96,7 @@ export default class Controller { }).setLatLng(marker.getLatLng()).setContent(dom); this.awesomeMap.map.addLayer(pop); + // console.log("marker click", node, "pop", pop, "marker", marker, "dom", dom, "templateId", this.templateId) }); node.marker = marker; node.component = this.component; @@ -110,7 +111,7 @@ export default class Controller { this.awesomeMap.layers[cat.id].group.addLayer(marker); this.awesomeMap.controls.showCategory(cat); } catch (evt) { - console.error("Failed category marker assignation", marker, evt.message); + console.error("Failed category marker assignation. category:", category, "marker:", marker, evt.message); } } } @@ -121,7 +122,7 @@ export default class Controller { try { this.awesomeMap.controls.addHashtagsControls(hashtags, marker); } catch (evt) { - console.error("Failed hashtags marker assignation", marker, evt.message); + console.error("Failed hashtags marker assignation. hashtags:", hashtags, "marker:", marker, evt.message); } } } @@ -132,14 +133,13 @@ export default class Controller { this.onFinished(); } - createIcon(Builder, color) { - return new Builder({ - color: "#000000", - fillColor: color, - circleFillColor: color, - weight: 1, - stroke: color, - fillOpacity: 0.9 + createIcon(color) { + const size = 36; + return L.divIcon({ + html: ` + `, + iconAnchor: [0.5 * size, size], + popupAnchor: [0, -0.5 * size] }); } } diff --git a/app/packs/src/decidim/decidim_awesome/awesome_map/controllers/meetings_controller.js b/app/packs/src/decidim/decidim_awesome/awesome_map/controllers/meetings_controller.js index 061c1fe5e..007404047 100644 --- a/app/packs/src/decidim/decidim_awesome/awesome_map/controllers/meetings_controller.js +++ b/app/packs/src/decidim/decidim_awesome/awesome_map/controllers/meetings_controller.js @@ -2,12 +2,6 @@ import * as L from "leaflet"; import Controller from "src/decidim/decidim_awesome/awesome_map/controllers/controller"; import MeetingsFetcher from "src/decidim/decidim_awesome/awesome_map/api/meetings_fetcher"; -const MeetingIcon = L.DivIcon.SVGIcon.DecidimIcon.extend({ - _createPathDescription: function() { - return "M 15.991543,4 C 7.3956015,4 2.9250351,10.5 3.000951,16.999999 3.1063486,26.460968 12.747693,30.000004 15.991543,43 19.242091,30.000004 29,26.255134 29,16.999999 29,10.5 23.951131,4 15.996007,4 m -0.153508,2.6000001 a 2.1720294,2.1076698 0 0 1 2.330514,2.1124998 2.177008,2.1125006 0 0 1 -4.354016,0 2.1720294,2.1076698 0 0 1 2.023502,-2.1124998 m -2.651707,4.8056679 h 5.610202 l 3.935584,7.569899 -1.926038,0.934266 -2.009546,-3.859265 v 14.557403 h -2.484243 v -9.126003 h -0.642162 v 9.126003 H 13.190347 V 16.050568 l -2.009545,3.859265 -1.926036,-0.934266 3.935581,-7.569899"; - } -}); - export default class MeetingsController extends Controller { constructor(awesomeMap, component) { super(awesomeMap, component) @@ -19,7 +13,7 @@ export default class MeetingsController extends Controller { // for each meeting, create a marker with an associated popup this.fetcher.onNode = (meeting) => { let marker = new L.Marker([meeting.coordinates.latitude, meeting.coordinates.longitude], { - icon: this.createIcon(MeetingIcon, this.awesomeMap.getCategory(meeting.category).color), + icon: this.createIcon(this.awesomeMap.getCategory(meeting.category).color), title: meeting.title.translation }); // console.log("new meeting", meeting, marker) @@ -28,4 +22,14 @@ export default class MeetingsController extends Controller { this.fetcher.fetch(); } + + createIcon(color) { + const size = 36; + return L.divIcon({ + html: ` + `, + iconAnchor: [0.5 * size, size], + popupAnchor: [0, -0.5 * size] + }); + } } diff --git a/app/packs/src/decidim/decidim_awesome/awesome_map/controllers/proposals_controller.js b/app/packs/src/decidim/decidim_awesome/awesome_map/controllers/proposals_controller.js index d1ae68b44..e908bdb53 100644 --- a/app/packs/src/decidim/decidim_awesome/awesome_map/controllers/proposals_controller.js +++ b/app/packs/src/decidim/decidim_awesome/awesome_map/controllers/proposals_controller.js @@ -2,14 +2,6 @@ import * as L from "leaflet"; import Controller from "src/decidim/decidim_awesome/awesome_map/controllers/controller"; import ProposalsFetcher from "src/decidim/decidim_awesome/awesome_map/api/proposals_fetcher"; -const ProposalIcon = L.DivIcon.SVGIcon.DecidimIcon.extend({ - options: { - fillColor: "#ef604d", - fillOpacity: 0.8, - strokeWidth: 1, - strokeOpcacity: 1 - } -}); export default class ProposalsController extends Controller { constructor(awesomeMap, component) { super(awesomeMap, component) @@ -36,12 +28,13 @@ export default class ProposalsController extends Controller { // for each proposal, create a marker with an associated popup this.fetcher.onNode = (proposal) => { let marker = new L.Marker([proposal.coordinates.latitude, proposal.coordinates.longitude], { - icon: this.createIcon(ProposalIcon, this.awesomeMap.getCategory(proposal.category).color), + icon: this.createIcon(this.awesomeMap.getCategory(proposal.category).color), title: proposal.title.translation }); // Check if it has amendments, add it to a list // also assign parent's proposal categories to it + // console.log("onNode proposal", proposal, "amendment:", proposal.amendments) if (proposal.amendments && proposal.amendments.length) { proposal.amendments.forEach((amendment) => { this.amendments[amendment.emendation.id] = proposal; @@ -67,16 +60,16 @@ export default class ProposalsController extends Controller { // console.log("marker", marker, "parent proposal", parent) // add marker to amendments layers and remove it from proposals if (marker) { - try { - marker.marker.removeFrom(this.controls.group) - } catch (evt) { + try { + marker.marker.removeFrom(this.controls.group) + } catch (evt) { console.error("error removeFrom marker", marker, "layer", this.controls.group, evt); } if (this.awesomeMap.config.menu.amendments) { marker.marker.addTo(this.awesomeMap.layers.amendments.group); // mimic parent category (amendments doesn't have categories) if (parent.category) { - marker.marker.setIcon(this.createIcon(ProposalIcon, this.awesomeMap.getCategory(parent.category).color)); + marker.marker.setIcon(this.createIcon("text-secondary")); this.addMarkerCategory(marker.marker, parent.category) } } diff --git a/app/packs/src/decidim/decidim_awesome/awesome_map/controls_ui.js b/app/packs/src/decidim/decidim_awesome/awesome_map/controls_ui.js index d4d8a358c..2b2cc986d 100644 --- a/app/packs/src/decidim/decidim_awesome/awesome_map/controls_ui.js +++ b/app/packs/src/decidim/decidim_awesome/awesome_map/controls_ui.js @@ -14,10 +14,10 @@ export default class ControlsUI { }); if (this.awesomeMap.config.hideControls) { - $(this.main.getContainer()).hide(); + this.main.getContainer().style.display = "none"; } - this.$loading = $("#awesome-map .loading-spinner"); + this.loading = document.querySelector("#awesome-map .loading-spinner"); this.onHashtag = this._orderHashtags; this.awesomeMap.map.on("overlayadd", () => { @@ -38,8 +38,14 @@ export default class ControlsUI { $("#awesome-map").on("click", ".awesome_map-title-control", (evt) => { evt.preventDefault(); evt.stopPropagation(); - $("#awesome_map-categories-control").toggleClass("active"); - $("#awesome_map-hashtags-control").toggleClass("active"); + const categories = document.getElementById("awesome_map-categories-control"); + const hashtags = document.getElementById("awesome_map-hashtags-control"); + if (categories) { + categories.classList.toggle("active"); + } + if (hashtags) { + hashtags.classList.toggle("active"); + } }); // hashtag events @@ -60,11 +66,27 @@ export default class ControlsUI { $("#awesome-map .awesome_map-hashtags-selector").prop("checked", $("#awesome-map .awesome_map-hashtags-selector:checked").length < $("#awesome-map .awesome_map-hashtags-selector").length); this.updateHashtagLayers(); }); + + this.awesomeMap.map.on("popupopen", () => { + // console.log("popup open"); + // hide Controls + document.querySelector(".leaflet-control-layers.leaflet-control").style.display = "none"; + }); + this.awesomeMap.map.on("popupclose", () => { + // console.log("popup close"); + // restore controls + document.querySelector(".leaflet-control-layers.leaflet-control").style.display = "block"; + }); } addSearchControls() { - $(this.main.getContainer()).contents("form").append(`
${window.DecidimAwesome.texts.categories}
-
${window.DecidimAwesome.texts.hashtags}
${window.DecidimAwesome.texts.select_deselect_all}
`); + const section = this.main.getContainer().querySelector(".leaflet-control-layers-list"); + if (section) { + section.insertAdjacentHTML("beforeend", `
${window.DecidimAwesome.texts.categories}
+
${window.DecidimAwesome.texts.hashtags}
${window.DecidimAwesome.texts.selectDeselectAll}
`); + } else { + console.error("Can't find the section to insert the controls"); + } } addCategoriesControls() { @@ -76,7 +98,12 @@ export default class ControlsUI { group: new L.FeatureGroup.SubGroup(this.awesomeMap.cluster) }; this.awesomeMap.layers[category.id].group.addTo(this.awesomeMap.map); - $("#awesome_map-categories-control .categories-container").append(``); + const categories = document.querySelector("#awesome_map-categories-control .categories-container"); + if (categories) { + categories.insertAdjacentHTML("beforeend", ``); + } else { + console.error("Can't find the section to insert the categories"); + } }) // category events @@ -114,7 +141,7 @@ export default class ControlsUI { addHashtagsControls(hashtags, marker) { // show hashtag layer if (hashtags && hashtags.length) { - $("#awesome_map-hashtags-control").show(); + document.getElementById("awesome_map-hashtags-control").style.display = "block"; hashtags.forEach((hashtag) => { // Add layer if not exists, otherwise just add the marker to the group if (!this.awesomeMap.layers[hashtag.tag]) { @@ -123,30 +150,34 @@ export default class ControlsUI { group: new L.FeatureGroup.SubGroup(this.awesomeMap.cluster) }; this.awesomeMap.map.addLayer(this.awesomeMap.layers[hashtag.tag].group); - $("#awesome_map-hashtags-control .hashtags-container").append(``); + document.querySelector("#awesome_map-hashtags-control .hashtags-container").insertAdjacentHTML("beforeend", ``); // Call a trigger, might be in service for customizations this.onHashtag(hashtag, $("#awesome_map-hashtags-control .hashtags-container")); } this.awesomeMap.layers[hashtag.tag].group.addLayer(marker); - const $label = $(`label.awesome_map-hashtag-${hashtag.tag}`); + const label = document.querySelector(`label.awesome_map-hashtag-${hashtag.tag}`); // update number of items - $label.attr("title", `${parseInt($label.attr("title") || 0, 10) + 1} ${window.DecidimAwesome.texts.items}`); + label.setAttribute("title", `${parseInt(label.title || 0, 10) + 1} ${window.DecidimAwesome.texts.items}`); }); } } showCategory(cat) { - $("#awesome_map-categories-control").show(); + document.getElementById("awesome_map-categories-control").style.display = "block"; // show category if hidden - const $label = $(`label.awesome_map-category-${cat.id}`); - const $parent = $(`label.awesome_map-category-${cat.parent}`); - $label.show(); - // update number of items - $label.attr("title", `${parseInt($label.attr("title") || 0, 10) + 1} ${window.DecidimAwesome.texts.items}`); - // show parent if apply - $parent.show(); - $parent.attr("title", `${parseInt($parent.attr("title") || 0, 10) + 1} ${window.DecidimAwesome.texts.items}`); + const label = document.querySelector(`label.awesome_map-category-${cat.id}`); + const parent = document.querySelector(`label.awesome_map-category-${cat.parent}`); + if (label) { + label.style.display = "block"; + // update number of items + label.setAttribute("title", `${parseInt(label.title || 0, 10) + 1} ${window.DecidimAwesome.texts.items}`); + } + if (parent) { + // show parent if apply + parent.style.display = "block" + parent.setAttribute("title", `${parseInt(parent.title || 0, 10) + 1} ${window.DecidimAwesome.texts.items}`); + } } removeHiddenComponents() { diff --git a/app/packs/src/decidim/decidim_awesome/awesome_map/load_map.js b/app/packs/src/decidim/decidim_awesome/awesome_map/load_map.js index a3fd7a1cc..5f4495721 100644 --- a/app/packs/src/decidim/decidim_awesome/awesome_map/load_map.js +++ b/app/packs/src/decidim/decidim_awesome/awesome_map/load_map.js @@ -1,6 +1,6 @@ import AwesomeMap from "src/decidim/decidim_awesome/awesome_map/awesome_map" -$(() => { +document.addEventListener("DOMContentLoaded", () => { const sanitizeCenter = (string) => { if (string) { const parts = string.split(",") @@ -15,31 +15,40 @@ $(() => { return null; }; + const parse = (string) => { + if (!string) { + return null; + } + return JSON.parse(string); + } + + const dataset = document.getElementById("awesome-map").dataset; const config = { - length: $("#awesome-map").data("truncate") || 254, - center: sanitizeCenter($("#awesome-map").data("map-center")), - zoom: $("#awesome-map").data("map-zoom"), + length: parse(dataset.truncate) || 254, + center: sanitizeCenter(parse(dataset.mapCenter)), + zoom: parse(dataset.mapZoom), menu: { - amendments: $("#awesome-map").data("menu-amendments"), - meetings: $("#awesome-map").data("menu-meetings"), - categories: $("#awesome-map").data("menu-categories"), - hashtags: $("#awesome-map").data("menu-hashtags"), - mergeComponents: $("#awesome-map").data("menu-merge-components") + amendments: parse(dataset.menuAmendments), + meetings: parse(dataset.menuMeetings), + categories: parse(dataset.menuCategories), + hashtags: parse(dataset.menuHashtags), + mergeComponents: parse(dataset.menuMergeComponents) }, show: { - withdrawn: $("#awesome-map").data("show-withdrawn"), - accepted: $("#awesome-map").data("show-accepted"), - evaluating: $("#awesome-map").data("show-evaluating"), - notAnswered: $("#awesome-map").data("show-not-answered"), - rejected: $("#awesome-map").data("show-rejected") + withdrawn: parse(dataset.showWithdrawn), + accepted: parse(dataset.showAccepted), + evaluating: parse(dataset.showEvaluating), + notAnswered: parse(dataset.showNotAnswered), + rejected: parse(dataset.showRejected) }, - hideControls: $("#awesome-map").data("hide-controls"), - collapsedMenu: $("#awesome-map").data("collapsed"), - components: $("#awesome-map").data("components") + hideControls: parse(dataset.hideCcontrols), + collapsedMenu: parse(dataset.collapsed), + components: parse(dataset.components) }; // build awesome map (if exist) - $("#awesome-map .google-map").on("ready.decidim", (evt, map) => { + // This event is still launched using JQuery in version 0.28 + $("#awesome-map .dynamic-map").on("ready.decidim", (evt, map) => { // bindPopup doesn't work for some unknown cause and these handler neither so we're cancelling them map.off("popupopen"); map.off("popupclose"); diff --git a/app/packs/src/decidim/decidim_awesome/editor/index.js b/app/packs/src/decidim/decidim_awesome/editor/index.js new file mode 100644 index 000000000..c7d023ca7 --- /dev/null +++ b/app/packs/src/decidim/decidim_awesome/editor/index.js @@ -0,0 +1,94 @@ +/* eslint-disable require-jsdoc */ + +import { Editor } from "@tiptap/core"; + +import DecidimKit from "src/decidim/editor/extensions/decidim_kit"; + +import createEditorToolbar from "src/decidim/editor/toolbar"; +import { uniqueId } from "src/decidim/editor/common/helpers"; + +/** + * Creates a new rich text editor instance and takes into account Awesome configuration for it (so user can upload images if configured) + * + * @param {HTMLElement} container The element that contains the editor. + * @return {Editor} The rich text editor instance. + */ + +export default function createEditor(container) { + const DecidimAwesome = window.DecidimAwesome || {}; + console.log("Using DecidimAwesome createEditor") + const input = container.parentElement.querySelector("input[type=hidden]"); + const label = container.parentElement.querySelector("label"); + const editorContainer = container.querySelector(".editor-input"); + + const editorAttributes = { role: "textbox", "aria-multiline": true }; + if (label) { + const labelId = uniqueId("editorlabel"); + label.setAttribute("id", labelId); + editorAttributes["aria-labelledby"] = labelId; + } + + /** + * Toolbar features can be one of: + * + * - basic = only basic controls without headings + * - content = basic + headings + * - full = basic + headings + image + video + */ + const features = container.dataset?.toolbar || "basic"; + const options = JSON.parse(container.dataset.options); + // console.log("options for this editor", options, "container", container, "editorContainer", editorContainer, "input", input, "label", label) + const { context, contentTypes } = options; + + const decidimOptions = {}; + + if (context !== "participant") { + decidimOptions.link = { allowTargetControl: true }; + } + + if (input.hasAttribute("maxlength")) { + decidimOptions.characterCount = { limit: parseInt(input.getAttribute("maxlength"), 10) }; + } + + if (features === "basic") { + decidimOptions.heading = false; + } + + if (features === "full" || DecidimAwesome.allow_videos_in_editors) { + decidimOptions.videoEmbed = true; + } + + if (features === "full" || DecidimAwesome.allow_images_in_editors) { + const { uploadImagesPath, uploadDialogSelector } = options; + decidimOptions.image = { + uploadDialogSelector, + contentTypes: contentTypes.image, + uploadImagesPath + }; + } + + if (container.classList.contains("js-hashtags")) { + decidimOptions.hashtag = true; + } + if (container.classList.contains("js-mentions")) { + decidimOptions.mention = true; + } + if (container.classList.contains("js-emojis")) { + decidimOptions.emoji = true; + } + + const editor = new Editor({ + element: editorContainer, + editorProps: { attributes: editorAttributes }, + content: input.value, + editable: !input.disabled, + extensions: [DecidimKit.configure(decidimOptions)] + }); + + const toolbar = createEditorToolbar(editor); + container.insertBefore(toolbar, editorContainer); + + editor.on("update", () => (input.value = editor.getHTML())); + + return editor; +} diff --git a/app/packs/src/decidim/decidim_awesome/editors/editor.js b/app/packs/src/decidim/decidim_awesome/editors/editor.js deleted file mode 100644 index 06eca090e..000000000 --- a/app/packs/src/decidim/decidim_awesome/editors/editor.js +++ /dev/null @@ -1,218 +0,0 @@ -/* eslint-disable require-jsdoc, func-style */ - -/* -* Since version 0.25 we follow a different strategy and opt to destroy and override completely the original editor -* That's because editors are instantiated directly instead of creating a global function to instantiate them -*/ - -import lineBreakButtonHandler from "src/decidim/editor/linebreak_module" -import InscrybMDE from "inscrybmde" -import Europa from "europa" -import "inline-attachment/src/inline-attachment"; -import "inline-attachment/src/codemirror-4.inline-attachment"; -import "inline-attachment/src/jquery.inline-attachment"; -import hljs from "highlight.js"; -import "highlight.js/styles/github.css"; -import "src/decidim/editor/clipboard_override" -import "src/decidim/vendor/image-resize.min" -import "src/decidim/vendor/image-upload.min" -import { marked } from "marked"; - -const DecidimAwesome = window.DecidimAwesome || {}; -const quillFormats = ["bold", "italic", "link", "underline", "header", "list", "video", "image", "alt", "break", "width", "style", "code", "blockquote", "indent"]; - -// A tricky way to destroy the quill editor -export function destroyQuillEditor(container) { - if (container) { - const content = $(container).find(".ql-editor").html(); - $(container).html(content); - $(container).siblings(".ql-toolbar").remove(); - $(container).find("*[class*='ql-']").removeClass((index, className) => (className.match(/(^|\s)ql-\S+/g) || []).join(" ")); - $(container).removeClass((index, className) => (className.match(/(^|\s)ql-\S+/g) || []).join(" ")); - if ($(container).next().is("p.help-text")) { - $(container).next().remove(); - } - } - else { - console.error(`editor [${container}] not exists`); - } -} - -export function createQuillEditor(container) { - const toolbar = $(container).data("toolbar"); - const disabled = $(container).data("disabled"); - const allowedEmptyContentSelector = "iframe"; - - let quillToolbar = [ - ["bold", "italic", "underline", "linebreak"], - [{ list: "ordered" }, { list: "bullet" }], - ["link", "clean"], - ["code", "blockquote"], - [{ "indent": "-1"}, { "indent": "+1" }] - ]; - - let addImage = false; - - if (toolbar === "full") { - quillToolbar = [ - [{ header: [2, 3, 4, 5, 6, false] }], - ...quillToolbar - ]; - if (DecidimAwesome.allow_images_in_full_editor) { - quillToolbar.push(["video", "image"]); - addImage = true; - } else { - quillToolbar.push(["video"]); - } - } else if (toolbar === "basic") { - if (DecidimAwesome.allow_images_in_small_editor) { - quillToolbar.push(["video", "image"]); - addImage = true; - } else { - quillToolbar.push(["video"]); - } - } else if (DecidimAwesome.allow_images_in_small_editor) { - quillToolbar.push(["image"]); - addImage = true; - } - - let modules = { - linebreak: {}, - toolbar: { - container: quillToolbar, - handlers: { - "linebreak": lineBreakButtonHandler - } - } - }; - - const $input = $(container).siblings('input[type="hidden"]'); - container.innerHTML = $input.val() || ""; - const token = $('meta[name="csrf-token"]').attr("content"); - if (addImage) { - modules.imageResize = { - modules: ["Resize", "DisplaySize"] - } - modules.imageUpload = { - url: DecidimAwesome.editor_uploader_path, - method: "POST", - name: "image", - withCredentials: false, - headers: { "X-CSRF-Token": token }, - callbackOK: (serverResponse, next) => { - $("div.ql-toolbar").last().removeClass("editor-loading") - next(serverResponse.url); - }, - callbackKO: (serverError) => { - $("div.ql-toolbar").last().removeClass("editor-loading") - let msg = serverError && serverError.body; - try { - msg = JSON.parse(msg).message; - } catch (evt) { console.error("Parsing error", evt); } - console.error(`Image upload error: ${msg}`); - let $p = $(`

${msg}

`); - $(container).after($p) - setTimeout(() => { - $p.fadeOut(1000, () => { - $p.destroy(); - }); - }, 3000); - }, - checkBeforeSend: (file, next) => { - $("div.ql-toolbar").last().addClass("editor-loading") - next(file); - } - } - } - const quill = new Quill(container, { - modules: modules, - formats: quillFormats, - theme: "snow" - }); - - if (disabled) { - quill.disable(); - } - - quill.on("text-change", () => { - const text = quill.getText(); - - // Triggers CustomEvent with the cursor position - // It is required in input_mentions.js - let event = new CustomEvent("quill-position", { - detail: quill.getSelection() - }); - container.dispatchEvent(event); - - if (text === "\n" || text === "\n\n") { - $input.val(""); - } else { - $input.val(quill.root.innerHTML); - } - if ((text === "\n" || text === "\n\n") && quill.root.querySelectorAll(allowedEmptyContentSelector).length === 0) { - $input.val(""); - } else { - const emptyParagraph = "


"; - const cleanHTML = quill.root.innerHTML.replace( - new RegExp(`^${emptyParagraph}|${emptyParagraph}$`, "g"), - "" - ); - $input.val(cleanHTML); - } - }); - // After editor is ready, linebreak_module deletes two extraneous new lines - quill.emitter.emit("editor-ready"); - - if (addImage) { - const text = $(container).data("dragAndDropHelpText") || DecidimAwesome.texts.drag_and_drop_image; - $(container).after(`

${text}

`); - } - - // After editor is ready, linebreak_module deletes two extraneous new lines - quill.emitter.emit("editor-ready"); - - return quill; -} - -export function createMarkdownEditor(container) { - const text = DecidimAwesome.texts.drag_and_drop_image; - const token = $('meta[name="csrf-token"]').attr("content"); - const $input = $(container).siblings('input[type="hidden"]'); - const $faker = $('