decidim-plans 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (182) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-AGPLv3.txt +661 -0
  3. data/README.md +157 -0
  4. data/Rakefile +48 -0
  5. data/app/assets/config/admin/decidim_plans_manifest.js +3 -0
  6. data/app/assets/config/decidim_plans_manifest.css +4 -0
  7. data/app/assets/config/decidim_plans_manifest.js +2 -0
  8. data/app/assets/images/decidim/plans/icon.svg +3 -0
  9. data/app/assets/javascripts/decidim/plans/admin/plans.js.es6 +3 -0
  10. data/app/assets/javascripts/decidim/plans/admin/sections.js.es6 +78 -0
  11. data/app/assets/javascripts/decidim/plans/multifield/component.js.es6 +101 -0
  12. data/app/assets/javascripts/decidim/plans/multifield.js.es6 +5 -0
  13. data/app/assets/javascripts/decidim/plans/plans_form.js.es6 +9 -0
  14. data/app/assets/javascripts/decidim/plans/proposal_picker.js.es6 +41 -0
  15. data/app/assets/javascripts/decidim/plans/remaining_characters.es6 +42 -0
  16. data/app/assets/javascripts/decidim/plans/social_share.js +2 -0
  17. data/app/assets/javascripts/decidim/plans/tab_focus.es6 +20 -0
  18. data/app/assets/stylesheets/decidim/plans/plans_form.scss +26 -0
  19. data/app/assets/stylesheets/decidim/plans/proposal_picker.scss +3 -0
  20. data/app/assets/stylesheets/decidim/plans/social_share.css.scss +22 -0
  21. data/app/cells/decidim/plans/author/flag.erb +5 -0
  22. data/app/cells/decidim/plans/author/withdraw.erb +6 -0
  23. data/app/cells/decidim/plans/author_cell.rb +9 -0
  24. data/app/cells/decidim/plans/coauthorships_cell.rb +23 -0
  25. data/app/cells/decidim/plans/collapsible_authors/show.erb +17 -0
  26. data/app/cells/decidim/plans/collapsible_authors_cell.rb +9 -0
  27. data/app/cells/decidim/plans/plan_activity_cell.rb +19 -0
  28. data/app/cells/decidim/plans/plan_cell.rb +59 -0
  29. data/app/cells/decidim/plans/plan_m/footer.erb +8 -0
  30. data/app/cells/decidim/plans/plan_m_cell.rb +62 -0
  31. data/app/commands/decidim/plans/accept_access_to_plan.rb +53 -0
  32. data/app/commands/decidim/plans/admin/answer_plan.rb +82 -0
  33. data/app/commands/decidim/plans/admin/create_plan.rb +102 -0
  34. data/app/commands/decidim/plans/admin/update_plan.rb +98 -0
  35. data/app/commands/decidim/plans/admin/update_sections.rb +54 -0
  36. data/app/commands/decidim/plans/attachment_methods.rb +78 -0
  37. data/app/commands/decidim/plans/create_plan.rb +90 -0
  38. data/app/commands/decidim/plans/destroy_plan.rb +33 -0
  39. data/app/commands/decidim/plans/nested_updater.rb +26 -0
  40. data/app/commands/decidim/plans/publish_plan.rb +79 -0
  41. data/app/commands/decidim/plans/reject_access_to_plan.rb +46 -0
  42. data/app/commands/decidim/plans/request_access_to_plan.rb +50 -0
  43. data/app/commands/decidim/plans/respond_to_access_request.rb +76 -0
  44. data/app/commands/decidim/plans/update_plan.rb +90 -0
  45. data/app/commands/decidim/plans/withdraw_plan.rb +50 -0
  46. data/app/controllers/concerns/decidim/plans/orderable.rb +53 -0
  47. data/app/controllers/decidim/plans/admin/application_controller.rb +16 -0
  48. data/app/controllers/decidim/plans/admin/plan_answers_controller.rb +40 -0
  49. data/app/controllers/decidim/plans/admin/plans_controller.rb +80 -0
  50. data/app/controllers/decidim/plans/admin/sections_controller.rb +75 -0
  51. data/app/controllers/decidim/plans/application_controller.rb +30 -0
  52. data/app/controllers/decidim/plans/plan_collaborator_requests_controller.rb +53 -0
  53. data/app/controllers/decidim/plans/plan_widgets_controller.rb +19 -0
  54. data/app/controllers/decidim/plans/plans_controller.rb +192 -0
  55. data/app/controllers/decidim/plans/versions_controller.rb +51 -0
  56. data/app/events/decidim/plans/accepted_plan_event.rb +9 -0
  57. data/app/events/decidim/plans/evaluating_plan_event.rb +8 -0
  58. data/app/events/decidim/plans/plan_access_accepted_event.rb +8 -0
  59. data/app/events/decidim/plans/plan_access_rejected_event.rb +8 -0
  60. data/app/events/decidim/plans/plan_access_request_event.rb +27 -0
  61. data/app/events/decidim/plans/plan_access_requested_event.rb +8 -0
  62. data/app/events/decidim/plans/plan_access_requester_accepted_event.rb +8 -0
  63. data/app/events/decidim/plans/plan_access_requester_rejected_event.rb +8 -0
  64. data/app/events/decidim/plans/publish_plan_event.rb +25 -0
  65. data/app/events/decidim/plans/rejected_plan_event.rb +9 -0
  66. data/app/forms/decidim/plans/accept_access_to_plan_form.rb +10 -0
  67. data/app/forms/decidim/plans/access_to_plan_form.rb +35 -0
  68. data/app/forms/decidim/plans/admin/plan_answer_form.rb +19 -0
  69. data/app/forms/decidim/plans/admin/plan_form.rb +88 -0
  70. data/app/forms/decidim/plans/admin/plan_sections_form.rb +20 -0
  71. data/app/forms/decidim/plans/admin/section_form.rb +34 -0
  72. data/app/forms/decidim/plans/attachment_form.rb +20 -0
  73. data/app/forms/decidim/plans/content_form.rb +58 -0
  74. data/app/forms/decidim/plans/plan_form.rb +85 -0
  75. data/app/forms/decidim/plans/reject_access_to_plan_form.rb +9 -0
  76. data/app/forms/decidim/plans/request_access_to_plan_form.rb +20 -0
  77. data/app/helpers/decidim/plans/admin/application_helper.rb +19 -0
  78. data/app/helpers/decidim/plans/application_helper.rb +78 -0
  79. data/app/helpers/decidim/plans/attached_proposals_helper.rb +44 -0
  80. data/app/helpers/decidim/plans/attachments_helper.rb +59 -0
  81. data/app/helpers/decidim/plans/cells_helper.rb +26 -0
  82. data/app/helpers/decidim/plans/plan_cells_helper.rb +67 -0
  83. data/app/helpers/decidim/plans/remaining_characters_helper.rb +32 -0
  84. data/app/helpers/decidim/plans/traceability_helper.rb +79 -0
  85. data/app/helpers/decidim/plans/user_group_helper.rb +23 -0
  86. data/app/models/concerns/decidim/plans/traceable.rb +48 -0
  87. data/app/models/decidim/plans/application_record.rb +10 -0
  88. data/app/models/decidim/plans/attached_proposal.rb +12 -0
  89. data/app/models/decidim/plans/content.rb +21 -0
  90. data/app/models/decidim/plans/paper_trail/version.rb +26 -0
  91. data/app/models/decidim/plans/paper_trail/version_association.rb +14 -0
  92. data/app/models/decidim/plans/plan.rb +206 -0
  93. data/app/models/decidim/plans/plan_collaborator_request.rb +13 -0
  94. data/app/models/decidim/plans/section.rb +16 -0
  95. data/app/permissions/decidim/plans/admin/permissions.rb +44 -0
  96. data/app/permissions/decidim/plans/permissions.rb +67 -0
  97. data/app/presenters/decidim/plans/admin_log/plan_presenter.rb +49 -0
  98. data/app/presenters/decidim/plans/admin_log/value_types/plan_state_presenter.rb +16 -0
  99. data/app/presenters/decidim/plans/content_presenter.rb +25 -0
  100. data/app/presenters/decidim/plans/log/resource_presenter.rb +18 -0
  101. data/app/presenters/decidim/plans/official_author_presenter.rb +38 -0
  102. data/app/presenters/decidim/plans/plan_presenter.rb +48 -0
  103. data/app/queries/decidim/plans/filtered_plans.rb +37 -0
  104. data/app/services/decidim/plans/diff_renderer/base.rb +108 -0
  105. data/app/services/decidim/plans/diff_renderer/categorization.rb +23 -0
  106. data/app/services/decidim/plans/diff_renderer/content.rb +33 -0
  107. data/app/services/decidim/plans/diff_renderer/plan.rb +25 -0
  108. data/app/services/decidim/plans/loggability.rb +19 -0
  109. data/app/services/decidim/plans/plan_builder.rb +68 -0
  110. data/app/services/decidim/plans/plan_search.rb +95 -0
  111. data/app/services/decidim/plans/tracer.rb +29 -0
  112. data/app/views/decidim/plans/admin/plan_answers/edit.html.erb +22 -0
  113. data/app/views/decidim/plans/admin/plans/_bulk-actions.html.erb +9 -0
  114. data/app/views/decidim/plans/admin/plans/_form.html.erb +62 -0
  115. data/app/views/decidim/plans/admin/plans/_js-callout.html.erb +6 -0
  116. data/app/views/decidim/plans/admin/plans/_plan-tr.html.erb +53 -0
  117. data/app/views/decidim/plans/admin/plans/edit.html.erb +7 -0
  118. data/app/views/decidim/plans/admin/plans/index.html.erb +64 -0
  119. data/app/views/decidim/plans/admin/plans/new.html.erb +7 -0
  120. data/app/views/decidim/plans/admin/sections/_bulk-actions.html.erb +7 -0
  121. data/app/views/decidim/plans/admin/sections/_form.html.erb +27 -0
  122. data/app/views/decidim/plans/admin/sections/_section.html.erb +66 -0
  123. data/app/views/decidim/plans/admin/sections/index.html.erb +7 -0
  124. data/app/views/decidim/plans/admin/shared/_info_plan.html.erb +17 -0
  125. data/app/views/decidim/plans/attached_proposals/_proposals.html.erb +12 -0
  126. data/app/views/decidim/plans/plan_widgets/show.html.erb +3 -0
  127. data/app/views/decidim/plans/plans/_accept_request_access_form.html.erb +8 -0
  128. data/app/views/decidim/plans/plans/_attached_proposals.html.erb +10 -0
  129. data/app/views/decidim/plans/plans/_collaborator_requests.html.erb +20 -0
  130. data/app/views/decidim/plans/plans/_content.html.erb +6 -0
  131. data/app/views/decidim/plans/plans/_contents.html.erb +8 -0
  132. data/app/views/decidim/plans/plans/_count.html.erb +1 -0
  133. data/app/views/decidim/plans/plans/_filters.html.erb +32 -0
  134. data/app/views/decidim/plans/plans/_filters_small_view.html.erb +18 -0
  135. data/app/views/decidim/plans/plans/_form.html.erb +57 -0
  136. data/app/views/decidim/plans/plans/_new_plan_button.html.erb +11 -0
  137. data/app/views/decidim/plans/plans/_plan.html.erb +1 -0
  138. data/app/views/decidim/plans/plans/_plan_preview.html.erb +1 -0
  139. data/app/views/decidim/plans/plans/_plans.html.erb +14 -0
  140. data/app/views/decidim/plans/plans/_reject_request_access_form.html.erb +10 -0
  141. data/app/views/decidim/plans/plans/_request_access_form.html.erb +7 -0
  142. data/app/views/decidim/plans/plans/edit.html.erb +21 -0
  143. data/app/views/decidim/plans/plans/index.html.erb +28 -0
  144. data/app/views/decidim/plans/plans/index.js.erb +10 -0
  145. data/app/views/decidim/plans/plans/new.html.erb +15 -0
  146. data/app/views/decidim/plans/plans/preview.html.erb +15 -0
  147. data/app/views/decidim/plans/plans/show.html.erb +133 -0
  148. data/app/views/decidim/plans/shared/_attachment_fields.html.erb +45 -0
  149. data/app/views/decidim/plans/shared/_attachments.html.erb +23 -0
  150. data/app/views/decidim/plans/shared/_remaining_characters_container.html.erb +12 -0
  151. data/app/views/decidim/plans/shared/_section_content_field.html.erb +39 -0
  152. data/app/views/decidim/plans/versions/_version.html.erb +20 -0
  153. data/app/views/decidim/plans/versions/index.html.erb +34 -0
  154. data/app/views/decidim/plans/versions/show.html.erb +41 -0
  155. data/config/locales/en.yml +341 -0
  156. data/config/locales/fi.yml +341 -0
  157. data/config/locales/sv.yml +341 -0
  158. data/db/migrate/20181230110225_enable_pg_trgm_extension_for_plans.rb +18 -0
  159. data/db/migrate/20181230110848_create_decidim_plans.rb +20 -0
  160. data/db/migrate/20181230111731_create_decidim_plans_sections.rb +13 -0
  161. data/db/migrate/20181230111931_create_decidim_plans_plan_contents.rb +14 -0
  162. data/db/migrate/20181230122035_create_decidim_plans_plan_collaborator_requests.rb +12 -0
  163. data/db/migrate/20190110125855_create_decidim_plans_attached_proposals.rb +12 -0
  164. data/db/migrate/20190129140441_add_help_to_decidim_plans_sections.rb +7 -0
  165. data/db/migrate/20190129143607_add_mandatory_to_decidim_plans_sections.rb +7 -0
  166. data/db/migrate/20190129172040_add_section_type_to_decidim_plans_sections.rb +12 -0
  167. data/db/migrate/20190129231439_add_answer_length_to_decidim_plans_sections.rb +11 -0
  168. data/db/migrate/20190202123044_create_version_associations.rb +25 -0
  169. data/db/migrate/20190202123045_add_transaction_id_column_to_versions.rb +15 -0
  170. data/db/migrate/20190202200716_add_update_token_to_decidim_plans.rb +7 -0
  171. data/lib/decidim/plans/admin.rb +10 -0
  172. data/lib/decidim/plans/admin_engine.rb +29 -0
  173. data/lib/decidim/plans/component.rb +163 -0
  174. data/lib/decidim/plans/engine.rb +43 -0
  175. data/lib/decidim/plans/locale_aware.rb +32 -0
  176. data/lib/decidim/plans/optionally_translatable_attributes.rb +85 -0
  177. data/lib/decidim/plans/paper_trail/record_trail.rb +24 -0
  178. data/lib/decidim/plans/paper_trail.rb +10 -0
  179. data/lib/decidim/plans/test/factories.rb +130 -0
  180. data/lib/decidim/plans/version.rb +8 -0
  181. data/lib/decidim/plans.rb +24 -0
  182. metadata +323 -0
data/README.md ADDED
@@ -0,0 +1,157 @@
1
+ # Decidim::Plans
2
+
3
+ A [Decidim](https://github.com/decidim/decidim) module that provides a new
4
+ component that can be added to any participatory space in Decidim. The component
5
+ allows users to write plans together that link to specific proposals. Further on
6
+ these plans can be converted to budgeting projects once the process moves to
7
+ budgeting.
8
+
9
+ The idea of plans is for the people to plan together the proposals further
10
+ before a budgeting voting is started. During the planning process people should
11
+ come up with a more specific plan for the project based on which the budget can
12
+ be evaluated.
13
+
14
+ The plans component is based on the Decidim's own proposals component with the
15
+ following differences:
16
+
17
+ - Plans are multilingual so that they can be copied over to budgeting projects.
18
+ - Plans are collaborative by default, borrowing ideas from collaborative drafts
19
+ in the proposals component.
20
+ - Plans can be linked to proposals in the same participatory space.
21
+ - The format of the plan form can be customized, so the fields that users need
22
+ to fill can be customized instead of relying on a fixed set of fields. This
23
+ allows all plans to appear in the same format.
24
+ - Plans can be later on converted into budgeting processes for the budgets
25
+ component once they are finished. When converting, the different sections are
26
+ converted to sub-headings in the budgeting project's description and the
27
+ budget needs to be specified by the user doing the conversion.
28
+
29
+ ## Installation
30
+
31
+ Add this line to your application's Gemfile:
32
+
33
+ ```ruby
34
+ gem 'decidim-plans', :git => 'git@github.com:mainio/decidim-module-plans.git'
35
+ ```
36
+
37
+ And then execute:
38
+
39
+ ```bash
40
+ $ bundle
41
+ $ bundle exec rails decidim_plans:install:migrations
42
+ $ bundle exec rails db:migrate
43
+ ```
44
+
45
+ To keep the gem up to date, you can use the commands above to also update it.
46
+
47
+ ## Usage
48
+
49
+ You can add the plans component to any participatory space. After adding, users
50
+ can start writing the plans when plan creation is enabled.
51
+
52
+ ## Contributing
53
+
54
+ For instructions how to setup your development environment for Decidim, see [Decidim](https://github.com/decidim/decidim). Also follow Decidim's general
55
+ instructions for development for this project as well.
56
+
57
+ ### Developing
58
+
59
+ To start contributing to this project, first:
60
+
61
+ - Install the basic dependencies (such as Ruby and PostgreSQL)
62
+ - Clone this repository
63
+
64
+ Decidim's main repository also provides a Docker configuration file if you
65
+ prefer to use Docker instead of installing the dependencies locally on your
66
+ machine.
67
+
68
+ You can create the development app by running the following commands after
69
+ cloning this project:
70
+
71
+ ```bash
72
+ $ bundle
73
+ $ DATABASE_USERNAME=<username> DATABASE_PASSWORD=<password> bundle exec rake development_app
74
+ $ npm i
75
+ ```
76
+
77
+ Note that the database user has to have rights to create and drop a database in
78
+ order to create the dummy test app database.
79
+
80
+ Then to test how the module works in Decidim, start the development server:
81
+
82
+ ```bash
83
+ $ cd development_app
84
+ $ DATABASE_USERNAME=<username> DATABASE_PASSWORD=<password> bundle exec rails s
85
+ ```
86
+
87
+ In case you are using [rbenv](https://github.com/rbenv/rbenv) and have the
88
+ [rbenv-vars](https://github.com/rbenv/rbenv-vars) plugin installed for it, you
89
+ can add the environment variables to the root directory of the project in a file
90
+ named `.rbenv-vars`. If these are defined for the environment, you can omit
91
+ defining these in the commands shown above.
92
+
93
+ #### Code Styling
94
+
95
+ Please follow the code styling defined by the different linters that ensure we
96
+ are all talking with the same language collaborating on the same project. This
97
+ project is set to follow the same rules that Decidim itself follows.
98
+
99
+ The following linters are used:
100
+
101
+ - Ruby, [Rubocop](https://rubocop.readthedocs.io/)
102
+ - JS/ES, [eslint](https://eslint.org/)
103
+ - CSS/SCSS, [stylelint](https://stylelint.io/)
104
+
105
+ You can run the code styling checks by running the following commands from the
106
+ console:
107
+
108
+ ```
109
+ $ bundle exec rubocop
110
+ $ npm run lint
111
+ $ npm run stylelint
112
+ ```
113
+
114
+ To ease up following the style guide, you should install the plugins to your
115
+ favorite editor, such as:
116
+
117
+ - Atom
118
+ * [linter-rubocop](https://atom.io/packages/linter-rubocop)
119
+ * [linter-eslint](https://atom.io/packages/linter-eslint)
120
+ * [linter-stylelint](https://atom.io/packages/linter-stylelint)
121
+ - Sublime Text
122
+ * [Sublime RuboCop](https://github.com/pderichs/sublime_rubocop)
123
+ * [SublimeLinter-eslint](https://github.com/SublimeLinter/SublimeLinter-eslint)
124
+ * [SublimeLinter-stylelint](https://github.com/SublimeLinter/SublimeLinter-stylelint)
125
+ - Visual Studio Code
126
+ * [Rubocop for Visual Studio Code](https://github.com/misogi/vscode-ruby-rubocop)
127
+ * [VS Code ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
128
+ * [vscode-stylelint](https://github.com/shinnn/vscode-stylelint)
129
+
130
+ ### Testing
131
+
132
+ To run the tests run the following in the gem development path:
133
+
134
+ ```bash
135
+ $ bundle
136
+ $ DATABASE_USERNAME=<username> DATABASE_PASSWORD=<password> bundle exec rake test_app
137
+ $ DATABASE_USERNAME=<username> DATABASE_PASSWORD=<password> bundle exec rspec
138
+ ```
139
+
140
+ Please read the notes regarding these commands from the development environment
141
+ setup above.
142
+
143
+ #### Test code coverage
144
+
145
+ If you want to generate the code coverage report for the tests, you can use
146
+ the `SIMPLECOV=1` environment variable in the rspec command as follows:
147
+
148
+ ```bash
149
+ SIMPLECOV=1 bundle exec rspec
150
+ ```
151
+
152
+ This will generate a folder named `coverage` in the project root which contains
153
+ the code coverage report.
154
+
155
+ ## License
156
+
157
+ See [LICENSE-AGPLv3.txt](LICENSE-AGPLv3.txt).
data/Rakefile ADDED
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "decidim/dev/common_rake"
4
+
5
+ def install_module(path)
6
+ Dir.chdir(path) do
7
+ system("bundle exec rake decidim_plans:install:migrations")
8
+ system("bundle exec rake db:migrate")
9
+ end
10
+ end
11
+
12
+ def seed_db(path)
13
+ Dir.chdir(path) do
14
+ system("bundle exec rake db:seed")
15
+ end
16
+ end
17
+
18
+ desc "Generates a dummy app for testing"
19
+ task test_app: "decidim:generate_external_test_app" do
20
+ ENV["RAILS_ENV"] = "test"
21
+ install_module("spec/decidim_dummy_app")
22
+ end
23
+
24
+ desc "Generates a development app"
25
+ task :development_app do
26
+ Bundler.with_original_env do
27
+ generate_decidim_app(
28
+ "development_app",
29
+ "--app_name",
30
+ "#{base_app_name}_development_app",
31
+ "--path",
32
+ "..",
33
+ "--recreate_db",
34
+ "--demo"
35
+ )
36
+ end
37
+
38
+ install_module("development_app")
39
+ seed_db("development_app")
40
+ end
41
+
42
+ # Run all tests, include all
43
+ RSpec::Core::RakeTask.new(:spec) do |t|
44
+ t.verbose = false
45
+ end
46
+
47
+ # Run both by default
48
+ task default: [:spec]
@@ -0,0 +1,3 @@
1
+ //= link decidim/plans/admin/sections.js
2
+ //= link decidim/plans/admin/plans.js
3
+ //= link decidim/plans/proposal_picker.js
@@ -0,0 +1,4 @@
1
+ /*
2
+ *= link decidim/plans/proposal_picker.scss
3
+ *= link decidim/plans/plans_form.scss
4
+ */
@@ -0,0 +1,2 @@
1
+ //= link decidim/plans/plans_form.js
2
+ //= link decidim/plans/proposal_picker.js
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 35 35">
2
+ <path d="M17.5 35A17.5 17.5 0 1 1 35 17.5 17.52 17.52 0 0 1 17.5 35zm0-33.06A15.56 15.56 0 1 0 33.06 17.5 15.57 15.57 0 0 0 17.5 1.94zm9.5 13.7H8a1 1 0 0 1 0-1.94h19a1 1 0 0 1 0 1.94zm0 3.68H8a1 1 0 0 1 0-1.94h19a1 1 0 0 1 0 1.94zM22.26 23H8a1 1 0 0 1 0-1.94h14.26a1 1 0 0 1 0 1.94z"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ $(() => {
2
+ // Add the needed JS initialization code here
3
+ });
@@ -0,0 +1,78 @@
1
+ ((exports) => {
2
+ const { AutoLabelByPositionComponent, AutoButtonsByPositionComponent, createDynamicFields, createSortList } = exports.DecidimAdmin;
3
+
4
+ const wrapperSelector = ".plan-sections";
5
+ const fieldSelector = ".plan-section";
6
+
7
+ const autoLabelByPosition = new AutoLabelByPositionComponent({
8
+ listSelector: ".plan-section:not(.hidden)",
9
+ labelSelector: ".card-title span:first",
10
+ onPositionComputed: (el, idx) => {
11
+ $(el).find("input[name$=\\[position\\]]").val(idx);
12
+ }
13
+ });
14
+
15
+ const autoButtonsByPosition = new AutoButtonsByPositionComponent({
16
+ listSelector: ".plan-section:not(.hidden)",
17
+ hideOnFirstSelector: ".move-up-section",
18
+ hideOnLastSelector: ".move-down-section"
19
+ });
20
+
21
+ const createSortableList = () => {
22
+ createSortList(".sections-list:not(.published)", {
23
+ handle: ".section-divider",
24
+ placeholder: '<div style="border-style: dashed; border-color: #000"></div>',
25
+ forcePlaceholderSize: true,
26
+ onSortUpdate: () => { autoLabelByPosition.run() }
27
+ });
28
+ };
29
+
30
+ const hideDeletedSection = ($target) => {
31
+ const inputDeleted = $target.find("input[name$=\\[deleted\\]]").val();
32
+
33
+ if (inputDeleted === "true") {
34
+ $target.addClass("hidden");
35
+ $target.hide();
36
+ }
37
+ }
38
+
39
+ createDynamicFields({
40
+ placeholderId: "section-id",
41
+ wrapperSelector: wrapperSelector,
42
+ containerSelector: ".sections-list",
43
+ fieldSelector: fieldSelector,
44
+ addFieldButtonSelector: ".add-section",
45
+ removeFieldButtonSelector: ".remove-section",
46
+ moveUpFieldButtonSelector: ".move-up-section",
47
+ moveDownFieldButtonSelector: ".move-down-section",
48
+ onAddField: () => {
49
+ createSortableList();
50
+
51
+ autoLabelByPosition.run();
52
+ autoButtonsByPosition.run();
53
+ },
54
+ onRemoveField: () => {
55
+ autoLabelByPosition.run();
56
+ autoButtonsByPosition.run();
57
+ },
58
+ onMoveUpField: () => {
59
+ autoLabelByPosition.run();
60
+ autoButtonsByPosition.run();
61
+ },
62
+ onMoveDownField: () => {
63
+ autoLabelByPosition.run();
64
+ autoButtonsByPosition.run();
65
+ }
66
+ });
67
+
68
+ createSortableList();
69
+
70
+ $(fieldSelector).each((idx, el) => {
71
+ const $target = $(el);
72
+
73
+ hideDeletedSection($target);
74
+ });
75
+
76
+ autoLabelByPosition.run();
77
+ autoButtonsByPosition.run();
78
+ })(window);
@@ -0,0 +1,101 @@
1
+ ((exports) => {
2
+ const { AutoLabelByPositionComponent, AutoButtonsByPositionComponent, createDynamicFields, createSortList } = exports.DecidimAdmin;
3
+
4
+ const initMultifield = ($wrapper) => {
5
+ const wrapperSelector = `#${$wrapper.attr("id")}`;
6
+ const placeholderId = $wrapper.data("placeholder-id");
7
+
8
+ const fieldSelector = ".multifield-field";
9
+
10
+ const autoLabelByPosition = new AutoLabelByPositionComponent({
11
+ listSelector: `${wrapperSelector} .multifield-field:not(.hidden)`,
12
+ labelSelector: ".card-title span:first",
13
+ onPositionComputed: (el, idx) => {
14
+ $(el).find("input.position-input").val(idx);
15
+ }
16
+ });
17
+
18
+ const autoButtonsByPosition = new AutoButtonsByPositionComponent({
19
+ listSelector: `${wrapperSelector} .multifield-field:not(.hidden)`,
20
+ hideOnFirstSelector: ".move-up-field",
21
+ hideOnLastSelector: ".move-down-field"
22
+ });
23
+
24
+ const createSortableList = () => {
25
+ createSortList(`${wrapperSelector} .fields-list:not(.published)`, {
26
+ handle: ".multifield-field-divider",
27
+ placeholder: '<div style="border-style: dashed; border-color: #000"></div>',
28
+ forcePlaceholderSize: true,
29
+ onSortUpdate: () => { autoLabelByPosition.run() }
30
+ });
31
+ };
32
+
33
+ const hideDeletedSection = ($target) => {
34
+ const inputDeleted = $target.find("input[name$=\\[deleted\\]]").val();
35
+
36
+ if (inputDeleted === "true") {
37
+ $target.addClass("hidden");
38
+ $target.hide();
39
+ }
40
+ }
41
+
42
+ createDynamicFields({
43
+ placeholderId: placeholderId,
44
+ wrapperSelector: wrapperSelector,
45
+ containerSelector: ".multifield-fields-list",
46
+ fieldSelector: fieldSelector,
47
+ addFieldButtonSelector: ".add-field",
48
+ removeFieldButtonSelector: ".remove-field",
49
+ moveUpFieldButtonSelector: ".move-up-field",
50
+ moveDownFieldButtonSelector: ".move-down-field",
51
+ onAddField: () => {
52
+ createSortableList();
53
+
54
+ autoLabelByPosition.run();
55
+ autoButtonsByPosition.run();
56
+ },
57
+ onRemoveField: () => {
58
+ autoLabelByPosition.run();
59
+ autoButtonsByPosition.run();
60
+ },
61
+ onMoveUpField: () => {
62
+ autoLabelByPosition.run();
63
+ autoButtonsByPosition.run();
64
+ },
65
+ onMoveDownField: () => {
66
+ autoLabelByPosition.run();
67
+ autoButtonsByPosition.run();
68
+ }
69
+ });
70
+
71
+ createSortableList();
72
+
73
+ $(fieldSelector).each((idx, el) => {
74
+ const $target = $(el);
75
+
76
+ hideDeletedSection($target);
77
+ });
78
+
79
+ autoLabelByPosition.run();
80
+ autoButtonsByPosition.run();
81
+ }
82
+
83
+ $.fn.multifield = function() {
84
+ $(this).each(
85
+
86
+ /**
87
+ * @this HTMLElement
88
+ * @return {void}
89
+ */
90
+ function() {
91
+ const $elem = $(this);
92
+ let id = $elem.attr("id");
93
+ if (!id || id.length < 1) {
94
+ id = `multifield-${Math.random().toString(16).slice(2)}`;
95
+ $elem.attr("id", id);
96
+ }
97
+ initMultifield($elem);
98
+ }
99
+ )
100
+ }
101
+ })(window);
@@ -0,0 +1,5 @@
1
+ // = require decidim/admin/auto_buttons_by_position.component
2
+ // = require decidim/admin/auto_label_by_position.component
3
+ // = require decidim/admin/dynamic_fields.component
4
+ // = require decidim/admin/sort_list.component
5
+ // = require decidim/plans/multifield/component
@@ -0,0 +1,9 @@
1
+ // = require ./remaining_characters
2
+ // = require ./tab_focus
3
+ // = require ./multifield
4
+ // = require_self
5
+
6
+ $(() => {
7
+ $("form.plans-form [data-remaining-characters]").remainingCharacters();
8
+ $("form.plans-form .multifield-fields").multifield();
9
+ });
@@ -0,0 +1,41 @@
1
+ // = require decidim/data_picker
2
+ // = require jquery.auto-complete
3
+
4
+ // = require_self
5
+
6
+ $(function() {
7
+ $(document).on("open.zf.reveal", "#data_picker-modal", function () {
8
+ let xhr = null;
9
+
10
+ $("#data_picker-autocomplete").autoComplete({
11
+ minChars: 2,
12
+ source: function(term, response) {
13
+ try {
14
+ xhr.abort();
15
+ } catch (exception) { xhr = null; }
16
+
17
+ let url = $("#proposal-picker-choose").attr("href");
18
+ xhr = $.getJSON(
19
+ url,
20
+ { term: term },
21
+ function(data) { response(data); }
22
+ );
23
+ },
24
+ renderItem: function (item, search) {
25
+ let sanitizedSearch = search.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
26
+ let re = new RegExp(`(${sanitizedSearch.split(" ").join("|")})`, "gi");
27
+ let title = item[0];
28
+ let modelId = item[1];
29
+ return `<div class="autocomplete-suggestion" data-model-id="${modelId}" data-val ="${title}">${title.replace(re, "<b>$1</b>")}</div>`;
30
+ },
31
+ onSelect: function(event, term, item) {
32
+ let choose = $("#proposal-picker-choose");
33
+ let modelId = item.data("modelId");
34
+ let val = item.data("val");
35
+ choose.data("picker-value", modelId);
36
+ choose.data("picker-text", val);
37
+ choose.data("picker-choose", "");
38
+ }
39
+ });
40
+ });
41
+ });
@@ -0,0 +1,42 @@
1
+ $(function() {
2
+ const COUNT_KEY = "%count%";
3
+
4
+ $.fn.remainingCharacters = function() {
5
+ return $(this).each(
6
+
7
+ /**
8
+ * @this HTMLElement
9
+ * @returns {void}
10
+ */
11
+ function() {
12
+ const $input = $(this);
13
+ const $target = $($input.data("remaining-characters"));
14
+ const maxCharacters = parseInt($(this).attr("maxlength"), 10);
15
+
16
+ if ($target.length > 0 && maxCharacters > 0) {
17
+ const messagesJson = $target.data("remaining-characters-messages");
18
+ const messages = $.extend({
19
+ one: `${COUNT_KEY} character left`,
20
+ many: `${COUNT_KEY} characters left`
21
+ }, messagesJson);
22
+
23
+ const updateStatus = function() {
24
+ const numCharacters = $input.val().length;
25
+ const remaining = maxCharacters - numCharacters;
26
+ let message = messages.many;
27
+ if (remaining === 1) {
28
+ message = messages.one;
29
+ }
30
+
31
+ $target.text(message.replace(COUNT_KEY, remaining));
32
+ };
33
+
34
+ $input.on("keyup focus", function() {
35
+ updateStatus();
36
+ });
37
+ updateStatus();
38
+ }
39
+ }
40
+ );
41
+ };
42
+ });
@@ -0,0 +1,2 @@
1
+ // = require social-share-button
2
+ // = require_self
@@ -0,0 +1,20 @@
1
+ // = require_self
2
+
3
+ /**
4
+ * When switching tabs in i18n fields, autofocus on the input to save clicks
5
+ *
6
+ * Similar to:
7
+ * decidim-admin/app/assets/javascripts/decidim/admin/tab_focus.js.es6
8
+ */
9
+ $(() => {
10
+ // Event launched by foundation
11
+ $("[data-tabs]").on("change.zf.tabs", (event) => {
12
+ const $container = $(event.target).parent().next(".tabs-content");
13
+ const $panel = $(".tabs-panel.is-active", $container);
14
+
15
+ const $content = $("input, textarea", $panel).first();
16
+ if ($content.length > 0) {
17
+ $content.focus();
18
+ }
19
+ });
20
+ });
@@ -0,0 +1,26 @@
1
+ .multifield-fields{
2
+ .multifield-field-divider{
3
+ padding-top: .8rem;
4
+ padding-bottom: .8rem;
5
+ }
6
+
7
+ .card-title{
8
+ padding-top: 0;
9
+ padding-bottom: 0;
10
+
11
+ .card-title-text{
12
+ display: inline-block;
13
+ padding-top: .2rem;
14
+ padding-bottom: .2em;
15
+ }
16
+ }
17
+
18
+ .button--title{
19
+ float: right;
20
+ margin-left: 1rem;
21
+
22
+ &:first-child{
23
+ margin-left: 0;
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,3 @@
1
+ @import "plugins/jquery.auto-complete";
2
+
3
+ div.picker-values a{ pointer-events: none; }
@@ -0,0 +1,22 @@
1
+ /*
2
+ *= require social-share-button
3
+ */
4
+
5
+ $size: 45px;
6
+
7
+ .share-link:hover{
8
+ text-decoration: underline;
9
+ cursor: pointer;
10
+ }
11
+
12
+ .social-share-button{
13
+ display: inline-block;
14
+ vertical-align: top;
15
+
16
+ .ssb-icon{
17
+ margin-right: 5px;
18
+ background-size: $size $size;
19
+ height: $size;
20
+ width: $size;
21
+ }
22
+ }
@@ -0,0 +1,5 @@
1
+ <% if flagable? %>
2
+ <button type="button" data-open="<%= current_user.present? ? "flagModal" : "loginModal" %>" title="<%= t("report", scope: "decidim.plans.plans.show") %>" aria-controls="<%= current_user.present? ? "flagModal" : "loginModal" %>" aria-haspopup="true" tabindex="0">
3
+ <%= icon "flag", aria_label: t("report", scope: "decidim.plans.plans.show"), class: "icon--small" %>
4
+ </button>
5
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <% if withdrawable? %>
2
+ <%= action_authorized_link_to :withdraw, withdraw_path, method: :put, class: "title-action__action button small hollow", title: t("withdraw_btn_hint", scope: "decidim.plans.plans.show"), data: { confirm: t("withdraw_confirmation", scope: "decidim.plans.plans.show") } do %>
3
+ <%= t("withdraw_btn", scope: "decidim.plans.plans.show") %>
4
+ <%= icon "x" %>
5
+ <% end %>
6
+ <% end %>
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ class AuthorCell < Decidim::AuthorCell
6
+ include Plans::CellsHelper
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ class CoauthorshipsCell < Decidim::CoauthorshipsCell
6
+ def show
7
+ if authorable?
8
+ cell "decidim/plans/author", presenter_for_author(model), extra_classes.merge(has_actions: has_actions?, from: model)
9
+ else
10
+ cell(
11
+ "decidim/plans/collapsible_authors",
12
+ presenters_for_identities(model),
13
+ cell_name: "decidim/plans/author",
14
+ cell_options: extra_classes,
15
+ size: size,
16
+ from: model,
17
+ has_actions: has_actions?
18
+ )
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ <div class="author--inline">
2
+ <%= cell(
3
+ "decidim/collapsible_list",
4
+ list,
5
+ cell_name: cell_name,
6
+ cell_options: options.merge(has_actions: false),
7
+ size: size
8
+ ) %>
9
+ </div>
10
+ <% if actionable? %>
11
+ <div class="author-data__extra">
12
+ <% author_cell= cell("decidim/plans/author", model, from: from_context) %>
13
+ <%= author_cell.(:date) %>
14
+ <%= author_cell.(:flag) %>
15
+ <%= author_cell.(:withdraw) %>
16
+ </div>
17
+ <% end %>
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Plans
5
+ class CollapsibleAuthorsCell < Decidim::CollapsibleAuthorsCell
6
+ include Plans::CellsHelper
7
+ end
8
+ end
9
+ end