decidim-participatory_documents 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/lint.yml +2 -2
  3. data/.github/workflows/test.yml +8 -5
  4. data/.ruby-version +1 -1
  5. data/.simplecov +15 -9
  6. data/Gemfile.lock +186 -184
  7. data/README.md +32 -28
  8. data/app/commands/decidim/participatory_documents/create_suggestion.rb +3 -1
  9. data/app/controllers/concerns/decidim/participatory_documents/needs_pdf_document.rb +8 -1
  10. data/app/controllers/decidim/participatory_documents/admin/documents_controller.rb +5 -3
  11. data/app/controllers/decidim/participatory_documents/admin/suggestions_controller.rb +21 -1
  12. data/app/controllers/decidim/participatory_documents/admin/valuation_assignments_controller.rb +5 -1
  13. data/app/controllers/decidim/participatory_documents/document_suggestions_controller.rb +14 -0
  14. data/app/controllers/decidim/participatory_documents/section_suggestions_controller.rb +4 -0
  15. data/app/forms/decidim/participatory_documents/admin/document_form.rb +2 -1
  16. data/app/jobs/decidim/participatory_documents/export_my_suggestions_job.rb +16 -0
  17. data/app/models/decidim/participatory_documents/suggestion.rb +12 -0
  18. data/app/packs/entrypoints/decidim_participatory_documents.scss +8 -0
  19. data/app/packs/entrypoints/decidim_participatory_documents_editor.js +1 -3
  20. data/app/packs/entrypoints/decidim_participatory_documents_viewer_off.js +6 -0
  21. data/app/packs/images/export_suggestions.svg +1 -0
  22. data/app/packs/src/decidim/participatory_documents/pdf/pdf_modal_manager.js +4 -1
  23. data/app/packs/src/decidim/participatory_documents/pdf/suggestion_form.js +1 -1
  24. data/app/packs/src/decidim/participatory_documents/pdf.js +46 -0
  25. data/app/packs/src/decidim/participatory_documents/pdf_off.js +39 -0
  26. data/app/packs/stylesheets/decidim/participatory_documents/pdf/admin_modals.scss +9 -5
  27. data/app/packs/stylesheets/decidim/participatory_documents/pdf/admin_tweaks.scss +0 -21
  28. data/app/packs/stylesheets/decidim/participatory_documents/{decidim_admin_classes.scss → pdf/decidim_admin_styles.scss} +0 -3
  29. data/app/packs/stylesheets/decidim/participatory_documents/pdf/modals.scss +49 -0
  30. data/app/packs/stylesheets/decidim/participatory_documents/pdf/tweaks.scss +76 -7
  31. data/app/permissions/decidim/participatory_documents/admin/permissions.rb +30 -15
  32. data/app/permissions/decidim/participatory_documents/permissions.rb +2 -0
  33. data/app/serializers/decidim/participatory_documents/my_suggestion_serializer.rb +22 -0
  34. data/app/serializers/decidim/participatory_documents/suggestion_serializer.rb +1 -1
  35. data/app/uploaders/decidim/participatory_documents/pdf_document_uploader.rb +4 -0
  36. data/app/views/decidim/participatory_documents/admin/documents/_editor_modal.html.erb +1 -1
  37. data/app/views/decidim/participatory_documents/admin/documents/pdf_viewer.html.erb +1 -1
  38. data/app/views/decidim/participatory_documents/admin/sections/_form.html.erb +0 -3
  39. data/app/views/decidim/participatory_documents/admin/suggestions/_suggestion.html.erb +1 -1
  40. data/app/views/decidim/participatory_documents/documents/_export_modal.html.erb +25 -0
  41. data/app/views/decidim/participatory_documents/documents/_pdfjs_base.html.erb +5 -2
  42. data/app/views/decidim/participatory_documents/documents/pdf_viewer.html.erb +40 -33
  43. data/config/assets.rb +1 -0
  44. data/config/locales/ca.yml +259 -0
  45. data/config/locales/de.yml +259 -0
  46. data/config/locales/en.yml +13 -0
  47. data/config/locales/es.yml +259 -0
  48. data/lib/decidim/participatory_documents/component.rb +6 -4
  49. data/lib/decidim/participatory_documents/engine.rb +5 -1
  50. data/lib/decidim/participatory_documents/version.rb +2 -2
  51. data/lib/decidim/participatory_documents.rb +5 -3
  52. metadata +12 -3
@@ -19,6 +19,31 @@
19
19
  margin: 12px;
20
20
  }
21
21
 
22
+ #toolbarViewerMiddle {
23
+ padding-left: 0;
24
+ padding-right: 0;
25
+
26
+ @media(min-width: 940px) {
27
+ transform: translateX(-75%);
28
+ }
29
+
30
+ @media(max-width: 680px) {
31
+ padding-left: 1em;
32
+ }
33
+ }
34
+
35
+ #toolbarViewerLeft {
36
+ @media(max-width: 680px) {
37
+ display: none;
38
+ }
39
+ }
40
+
41
+ #toolbarViewerRight {
42
+ @media(max-width: 380px) {
43
+ display: none;
44
+ }
45
+ }
46
+
22
47
  .secondaryToolbar,
23
48
  .findbar {
24
49
  top: 53px;
@@ -49,6 +74,18 @@
49
74
  margin-top: 8px;
50
75
  }
51
76
 
77
+ .hiddenSmallView {
78
+ @media(max-width: 800px) {
79
+ display: none;
80
+ }
81
+ }
82
+
83
+ .toolbarButtonSpacer {
84
+ @media(max-width: 980px) {
85
+ width: 0;
86
+ }
87
+ }
88
+
52
89
  .toolbarField {
53
90
  margin-top: 9px;
54
91
  }
@@ -64,13 +101,19 @@
64
101
  background: #fff;
65
102
  border: 1px solid #000;
66
103
  }
104
+
105
+ @media(max-width: 940px) {
106
+ display: none;
107
+ }
108
+
109
+ @media(min-width: 941px) {
110
+ display: block;
111
+ }
67
112
  }
68
113
 
69
114
  .decidimButton {
70
- display: inline-block;
71
- vertical-align: middle;
72
- margin: 0 0 1rem;
73
- padding: .85em 1em;
115
+ margin: 0 0 0 1rem;
116
+ padding: .65em;
74
117
  border-radius: 4px;
75
118
  transition: background-color .25s ease-out, color .25s ease-out;
76
119
  font-family: inherit;
@@ -83,16 +126,42 @@
83
126
  border: transparent solid 1px;
84
127
  color: var(--box-text-color);
85
128
  text-shadow: 0 0 0 #fff;
129
+
130
+ &:first-child {
131
+ margin-left: 0;
132
+ }
133
+
134
+ &.muted {
135
+ background: #ddd;
136
+ color: #333;
137
+ }
138
+
139
+ @media(max-width: 900px) {
140
+ margin-top: 12px;
141
+ }
86
142
  }
87
143
 
88
144
  #globalSuggestionTrigger {
89
- min-width: 160px;
90
-
91
145
  &::before {
92
146
  display: none;
93
147
  }
94
148
  }
95
149
 
150
+ #exportSuggestionsTrigger {
151
+ background-image: url(../images/export_suggestions.svg);
152
+ background-repeat: no-repeat;
153
+ background-size: 20px 20px;
154
+ background-position: 8px 9px;
155
+ padding-left: 34px;
156
+
157
+ @media(max-width: 900px) {
158
+ width: 28px;
159
+ height: 36px;
160
+ overflow: hidden;
161
+ font-size: 0;
162
+ }
163
+ }
164
+
96
165
  #fullscreenButton {
97
166
  background-image: url(../images/fullscreen.svg);
98
167
  background-repeat: no-repeat;
@@ -115,7 +184,7 @@
115
184
  background-image: url(../images/fullscreen_exit.svg);
116
185
  }
117
186
 
118
- @media(max-width: 560px) {
187
+ @media(max-width: 940px) {
119
188
  width: 28px;
120
189
  height: 28px;
121
190
  margin-right: 0;
@@ -7,38 +7,53 @@ module Decidim
7
7
  def permissions
8
8
  return permission_action if permission_action.scope != :admin
9
9
 
10
- # Valuators can only perform these actions
11
- if user_is_valuator?
12
- if valuator_assigned_to_suggestion?
13
- can_create_suggestion_note?
14
- can_create_suggestion_answer?
15
- end
16
- valuator_can_unassign_valuator_from_suggestions?
17
-
18
- return permission_action
10
+ handle_valuator_permissions if user_is_valuator?
11
+ handle_general_permissions unless user_is_valuator?
12
+
13
+ permission_action
14
+ end
15
+
16
+ private
17
+
18
+ def handle_valuator_permissions
19
+ if valuator_assigned_to_suggestion?
20
+ can_create_suggestion_note?
21
+ can_create_suggestion_answer?
22
+ allow! if action_is_show_on_suggestion?
23
+ elsif action_is_show_on_suggestion?
24
+ disallow!
19
25
  end
26
+ valuator_can_unassign_valuator_from_suggestions?
27
+ end
20
28
 
29
+ def handle_general_permissions
30
+ allow! if action_is_show_on_suggestion?
21
31
  if create_permission_action?
22
32
  can_create_suggestion_note?
23
33
  can_create_suggestion_answer?
24
34
  end
25
-
26
35
  edit_suggestion_note?
36
+ can_edit_document_or_sections? if action_is_update_on_document?
37
+ allow_default_admin_actions
38
+ end
27
39
 
28
- can_edit_document_or_sections? if permission_action.subject == :participatory_document && permission_action.action == :update
40
+ def action_is_show_on_suggestion?
41
+ permission_action.subject == :suggestion && permission_action.action == :show
42
+ end
43
+
44
+ def action_is_update_on_document?
45
+ permission_action.subject == :participatory_document && permission_action.action == :update
46
+ end
29
47
 
48
+ def allow_default_admin_actions
30
49
  allow! if permission_action.subject == :suggestion_note && permission_action.action == :create
31
50
  allow! if permission_action.subject == :suggestion_answer
32
51
  allow! if permission_action.subject == :document_section
33
52
  allow! if permission_action.subject == :document_annotations
34
53
  allow! if permission_action.subject == :participatory_document && permission_action.action == :create
35
54
  allow! if permission_action.subject == :suggestions
36
-
37
- permission_action
38
55
  end
39
56
 
40
- private
41
-
42
57
  def suggestion
43
58
  @suggestion ||= context.fetch(:suggestion, nil)
44
59
  end
@@ -6,6 +6,8 @@ module Decidim
6
6
  def permissions
7
7
  return permission_action unless user
8
8
 
9
+ allow! if permission_action.subject == :suggestion && permission_action.action == :create
10
+
9
11
  # Delegate the admin permission checks to the admin permissions class
10
12
  return Decidim::ParticipatoryDocuments::Admin::Permissions.new(user, permission_action, context).permissions if permission_action.scope == :admin
11
13
 
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module ParticipatoryDocuments
5
+ # This class serializes a Suggestion so can be exported to CSV, JSON or other
6
+ # formats.
7
+ class MySuggestionSerializer < SuggestionSerializer
8
+ # Public: Exports a hash with the serialized data for this suggestion.
9
+ def serialize
10
+ {
11
+ id: suggestion.id,
12
+ body: suggestion_body(suggestion),
13
+ author: suggestion.try(:normalized_author).try(:name),
14
+ state: humanize_suggestion_state(suggestion.state),
15
+ answer: answer_text(suggestion),
16
+ section: section(suggestion),
17
+ submitted_on: submitted_on(suggestion)
18
+ }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -24,7 +24,7 @@ module Decidim
24
24
  state: humanize_suggestion_state(suggestion.state),
25
25
  answer: answer_text(suggestion),
26
26
  section: section(suggestion),
27
- valuators: suggestion.valuation_assignments.count,
27
+ valuators: suggestion.valuation_assignments.map(&:valuator).map(&:name).join(", "),
28
28
  submitted_on: submitted_on(suggestion)
29
29
  }
30
30
  end
@@ -6,6 +6,10 @@ module Decidim
6
6
  def content_type_allowlist
7
7
  %w(application/pdf)
8
8
  end
9
+
10
+ def extension_allowlist
11
+ %w(pdf)
12
+ end
9
13
  end
10
14
  end
11
15
  end
@@ -1,3 +1,3 @@
1
1
  <div id="decidim">
2
- <%= render partial: "decidim/participatory_documents/admin/sections/form" %>
2
+ <%= render "decidim/participatory_documents/admin/sections/form" %>
3
3
  </div>
@@ -21,7 +21,7 @@
21
21
  </head>
22
22
 
23
23
  <body tabindex="1">
24
- <%= render "decidim/participatory_documents/documents/pdfjs_base", displaySave: true, globalSuggestions: false %>
24
+ <%= render "decidim/participatory_documents/documents/pdfjs_base", display_save: true %>
25
25
 
26
26
  <%= render "decidim/participatory_documents/admin/documents/editor_modal" %>
27
27
 
@@ -1,5 +1,3 @@
1
-
2
-
3
1
  <div class="reveal-overlay">
4
2
  <div class="reveal" id="editor-modal">
5
3
  <%= decidim_form_for([document, @form], html: { class: "form" }) do |form| %>
@@ -21,4 +19,3 @@
21
19
  <span aria-hidden="true">&times;</span>
22
20
  </button>
23
21
  </div>
24
- </div>
@@ -4,7 +4,7 @@
4
4
  </td>
5
5
  <td><%= suggestion.id %></td>
6
6
  <td>
7
- <%= truncate(suggestion_content(suggestion)[:text], length: 50) %>
7
+ <%= link_to truncate(suggestion_content(suggestion)[:text], length: 50), document_suggestion_path(document, suggestion) %>
8
8
  <% if suggestion_content(suggestion)[:file_link] %>
9
9
  <%= suggestion_content(suggestion)[:file_link] %>
10
10
  <% end %>
@@ -0,0 +1,25 @@
1
+ <div id="decidim">
2
+ <div class="reveal-overlay">
3
+ <div class="reveal" id="export-modal" data-reveal>
4
+ <div class="reveal__header">
5
+ <h2 class="reveal__title"><%= t(".title") %></h2>
6
+ <button class="close-button" data-close aria-label="close"
7
+ type="button">
8
+ <span aria-hidden="true">&times;</span>
9
+ </button>
10
+ </div>
11
+
12
+ <div class="content">
13
+ <div class="row">
14
+ <p><%= t(".description_html", email: current_user&.email, count: all_suggestions&.count) %></p>
15
+ </div>
16
+
17
+ <div class="row">
18
+ <div class="column flex-center">
19
+ <button type="button" class="export-button button primary expanded" data-url="<%= export_document_suggestions_path(document) %>"><%= t(".send") %></button>
20
+ </div>
21
+ </div>
22
+ </div>
23
+ </div>
24
+ </div>
25
+ </div>
@@ -305,15 +305,18 @@ See https://github.com/adobe-type-tools/cmap-resources
305
305
  </button>
306
306
  </div>
307
307
  <div id="toolbarViewerMiddle">
308
- <% if defined?(displaySave) && displaySave == true %>
308
+ <% if defined?(display_save) && display_save == true %>
309
309
  <button id="DecidimPDSaveButton" class="decidimButton" title="<%= t "save_changes", scope: "decidim.participatory_documents.document" %>">
310
310
  <%= t "save_changes", scope: "decidim.participatory_documents.document" %>
311
311
  </button>
312
312
  <% end %>
313
- <% if defined?(globalSuggestions) && globalSuggestions == true %>
313
+ <% if defined?(display_suggestions) && display_suggestions == true %>
314
314
  <button id="globalSuggestionTrigger" class="decidimButton" title="<%= t "global_suggestions", scope: "decidim.participatory_documents.document" %>">
315
315
  <%= t "global_suggestions", scope: "decidim.participatory_documents.document" %>
316
316
  </button>
317
+ <button id="exportSuggestionsTrigger" class="decidimButton muted" title="<%= t "export_my_suggestions", scope: "decidim.participatory_documents.document" %>">
318
+ <%= t "export_my_suggestions", scope: "decidim.participatory_documents.document" %>
319
+ </button>
317
320
  <% end %>
318
321
  </div>
319
322
  </div>
@@ -13,51 +13,58 @@
13
13
  <!-- This snippet is used in production (included from viewer.html) -->
14
14
  <link rel="resource" type="application/l10n" href="/pdfjs/web/locale/locale.properties">
15
15
  <script src="/pdfjs/web/viewer.js"></script>
16
- <%= javascript_pack_tag "decidim_participatory_documents_viewer" %>
17
16
  <%= stylesheet_pack_tag "decidim_participatory_documents_viewer" %>
17
+ <%= javascript_pack_tag "decidim_participatory_documents_viewer" if current_user %>
18
+ <%= javascript_pack_tag "decidim_participatory_documents_viewer_off" unless current_user %>
18
19
  <%= organization_colors %>
19
20
  <%= pdf_custom_style %>
20
21
  <%= csrf_meta_tags %>
21
22
  </head>
22
23
 
23
24
  <body tabindex="1">
24
- <%= render "decidim/participatory_documents/documents/pdfjs_base", displaySave: false, globalSuggestions: true %>
25
+ <%= render "decidim/participatory_documents/documents/pdfjs_base", display_suggestions: true %>
25
26
 
26
27
  <div id="notifications"></div>
27
28
 
28
29
  <div id="participation-modal"></div>
29
30
 
30
- <script>
31
-
32
- var I18n = <%= I18n.t("decidim.participatory_documents.ui_messages").to_json.html_safe %>;
33
- var DocumentPath = '<%= document_path(document).split("?")[0] %>';
34
- var CurrentBoxes = <%= document.annotations.collect(&:serialize).to_json.html_safe %>;
35
- var pages = {};
36
-
37
- PDFViewerApplication.initializedPromise.then(function() {
38
- var options = {
39
- i18n: I18n,
40
- documentPath: DocumentPath,
41
- globalSuggestionsButton: document.getElementById("globalSuggestionTrigger"),
42
- participationLayout: document.getElementById("participation-modal")
43
- };
44
-
45
- window.InitDocumentManagers(options);
46
-
47
- window.showInfo(I18n.startSuggesting, {delay: 5000});
48
-
49
- PDFViewerApplication.eventBus.on('annotationeditorlayerrendered', function (doc) {
50
- var page = PDFViewerApplication.pdfViewer._pages[doc.pageNumber - 1];
51
- var annotationsLayer = page.annotationEditorLayer && page.annotationEditorLayer.div;
52
- var savedPage = pages[doc.pageNumber];
53
- // Let's render the object only if not already existing.
54
- // Note that the PDF library might redraw the annotation layer, if this happens we need to re-render the areas.
55
- if(annotationsLayer && (!savedPage || savedPage.boxEditor.div !== annotationsLayer)) {
56
- page.boxEditor = window.InitPolygonViewer(annotationsLayer, CurrentBoxes.filter(box => box.page_number === doc.pageNumber), options);
57
- pages[doc.pageNumber] = page;
58
- }
31
+ <%= render "export_modal" %>
32
+
33
+ <script>
34
+
35
+ var I18n = <%= I18n.t("decidim.participatory_documents.ui_messages").to_json.html_safe %>;
36
+ var DocumentPath = '<%= document_path(document).split("?")[0] %>';
37
+ var CurrentBoxes = <%= document.annotations.collect(&:serialize).to_json.html_safe %>;
38
+ var pages = {};
39
+
40
+ PDFViewerApplication.initializedPromise.then(function() {
41
+ var options = {
42
+ i18n: I18n,
43
+ documentPath: DocumentPath,
44
+ globalSuggestionsButton: document.getElementById("globalSuggestionTrigger"),
45
+ participationLayout: document.getElementById("participation-modal"),
46
+ exportButton: document.getElementById("exportSuggestionsTrigger"),
47
+ exportModal: document.getElementById("export-modal")
48
+ };
49
+
50
+ window.InitDocumentManagers(options);
51
+
52
+ window.showInfo(I18n.startSuggesting, {delay: 5000});
53
+
54
+ PDFViewerApplication.eventBus.on('annotationeditorlayerrendered', function (doc) {
55
+ var page = PDFViewerApplication.pdfViewer._pages[doc.pageNumber - 1];
56
+ var annotationsLayer = page.annotationEditorLayer && page.annotationEditorLayer.div;
57
+ var savedPage = pages[doc.pageNumber];
58
+ // Let's render the object only if not already existing.
59
+ // Note that the PDF library might redraw the annotation layer, if this happens we need to re-render the areas.
60
+ if(annotationsLayer && (!savedPage || savedPage.boxEditor.div !== annotationsLayer)) {
61
+ page.boxEditor = window.InitPolygonViewer(annotationsLayer, CurrentBoxes.filter(box => box.page_number === doc.pageNumber), options);
62
+ pages[doc.pageNumber] = page;
63
+ }
64
+ // make all links with target blank to prevent open them inside the iframe
65
+ page.div.querySelectorAll("a[href]").forEach(anchor => anchor.target = "_blank")
66
+ });
59
67
  });
60
- });
61
- </script>
68
+ </script>
62
69
  </body>
63
70
  </html>
data/config/assets.rb CHANGED
@@ -6,6 +6,7 @@ Decidim::Webpacker.register_path("#{base_path}/app/packs")
6
6
  Decidim::Webpacker.register_entrypoints(
7
7
  decidim_participatory_documents_admin: "#{base_path}/app/packs/entrypoints/decidim_participatory_documents_admin.js",
8
8
  decidim_participatory_documents_viewer: "#{base_path}/app/packs/entrypoints/decidim_participatory_documents_viewer.js",
9
+ decidim_participatory_documents_viewer_off: "#{base_path}/app/packs/entrypoints/decidim_participatory_documents_viewer_off.js",
9
10
  decidim_participatory_documents_editor: "#{base_path}/app/packs/entrypoints/decidim_participatory_documents_editor.js",
10
11
  decidim_participatory_documents: "#{base_path}/app/packs/entrypoints/decidim_participatory_documents.js"
11
12
  )