decidim-collaborative_texts 0.31.0.rc1

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 (159) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +24 -0
  3. data/Rakefile +3 -0
  4. data/app/cells/decidim/collaborative_texts/document_cell.rb +23 -0
  5. data/app/cells/decidim/collaborative_texts/document_l_cell.rb +15 -0
  6. data/app/commands/decidim/collaborative_texts/admin/create_document.rb +25 -0
  7. data/app/commands/decidim/collaborative_texts/admin/publish_document.rb +52 -0
  8. data/app/commands/decidim/collaborative_texts/admin/unpublish_document.rb +44 -0
  9. data/app/commands/decidim/collaborative_texts/admin/update_document.rb +101 -0
  10. data/app/commands/decidim/collaborative_texts/admin/update_document_settings.rb +13 -0
  11. data/app/commands/decidim/collaborative_texts/create_suggestion.rb +28 -0
  12. data/app/commands/decidim/collaborative_texts/rollout.rb +50 -0
  13. data/app/controllers/concerns/decidim/collaborative_texts/admin/filterable.rb +23 -0
  14. data/app/controllers/decidim/collaborative_texts/admin/application_controller.rb +15 -0
  15. data/app/controllers/decidim/collaborative_texts/admin/documents_controller.rb +142 -0
  16. data/app/controllers/decidim/collaborative_texts/application_controller.rb +14 -0
  17. data/app/controllers/decidim/collaborative_texts/documents_controller.rb +57 -0
  18. data/app/controllers/decidim/collaborative_texts/suggestions_controller.rb +55 -0
  19. data/app/events/decidim/collaborative_texts/suggestion_accepted_event.rb +6 -0
  20. data/app/forms/decidim/collaborative_texts/admin/document_form.rb +29 -0
  21. data/app/forms/decidim/collaborative_texts/rollout_form.rb +26 -0
  22. data/app/forms/decidim/collaborative_texts/suggestion_form.rb +42 -0
  23. data/app/helpers/decidim/collaborative_texts/application_helper.rb +20 -0
  24. data/app/models/decidim/collaborative_texts/application_record.rb +10 -0
  25. data/app/models/decidim/collaborative_texts/document.rb +78 -0
  26. data/app/models/decidim/collaborative_texts/suggestion.rb +36 -0
  27. data/app/models/decidim/collaborative_texts/version.rb +35 -0
  28. data/app/packs/entrypoints/decidim_collaborative_texts.js +7 -0
  29. data/app/packs/images/decidim/collaborative_texts/decidim_collaborative_texts.svg +1 -0
  30. data/app/packs/src/decidim/collaborative_texts/document.js +168 -0
  31. data/app/packs/src/decidim/collaborative_texts/editor.js +80 -0
  32. data/app/packs/src/decidim/collaborative_texts/init_documents.js +27 -0
  33. data/app/packs/src/decidim/collaborative_texts/manager.js +106 -0
  34. data/app/packs/src/decidim/collaborative_texts/selection.js +106 -0
  35. data/app/packs/src/decidim/collaborative_texts/suggestion.js +243 -0
  36. data/app/packs/src/decidim/collaborative_texts/suggestions_list.js +103 -0
  37. data/app/packs/src/decidim/collaborative_texts/test/document.test.js +83 -0
  38. data/app/packs/src/decidim/collaborative_texts/test/manager.test.js +149 -0
  39. data/app/packs/src/decidim/collaborative_texts/test/selection.test.js +125 -0
  40. data/app/packs/src/decidim/collaborative_texts/test/suggestions.test.js +233 -0
  41. data/app/packs/src/decidim/collaborative_texts/test/toc.test.js +70 -0
  42. data/app/packs/src/decidim/collaborative_texts/toc.js +48 -0
  43. data/app/packs/stylesheets/decidim/collaborative_texts/collaborative_texts.scss +287 -0
  44. data/app/permissions/decidim/collaborative_texts/admin/permissions.rb +28 -0
  45. data/app/permissions/decidim/collaborative_texts/permissions.rb +36 -0
  46. data/app/presenters/decidim/collaborative_texts/admin_log/document_presenter.rb +54 -0
  47. data/app/presenters/decidim/collaborative_texts/admin_log/suggestion_presenter.rb +62 -0
  48. data/app/presenters/decidim/collaborative_texts/admin_log/suggestion_resource_presenter.rb +20 -0
  49. data/app/presenters/decidim/collaborative_texts/admin_log/version_presenter.rb +53 -0
  50. data/app/presenters/decidim/collaborative_texts/official_author_presenter.rb +11 -0
  51. data/app/presenters/decidim/collaborative_texts/suggestion_presenter.rb +57 -0
  52. data/app/views/decidim/collaborative_texts/admin/documents/_actions.html.erb +82 -0
  53. data/app/views/decidim/collaborative_texts/admin/documents/_document-tr.html.erb +15 -0
  54. data/app/views/decidim/collaborative_texts/admin/documents/_documents-thead.html.erb +7 -0
  55. data/app/views/decidim/collaborative_texts/admin/documents/_draft_options.html.erb +6 -0
  56. data/app/views/decidim/collaborative_texts/admin/documents/_form.html.erb +16 -0
  57. data/app/views/decidim/collaborative_texts/admin/documents/_non_draft_options.html.erb +9 -0
  58. data/app/views/decidim/collaborative_texts/admin/documents/_versions.html.erb +18 -0
  59. data/app/views/decidim/collaborative_texts/admin/documents/edit.html.erb +33 -0
  60. data/app/views/decidim/collaborative_texts/admin/documents/edit_settings.html.erb +18 -0
  61. data/app/views/decidim/collaborative_texts/admin/documents/index.html.erb +32 -0
  62. data/app/views/decidim/collaborative_texts/admin/documents/manage_trash.html.erb +19 -0
  63. data/app/views/decidim/collaborative_texts/admin/documents/new.html.erb +18 -0
  64. data/app/views/decidim/collaborative_texts/admin/settings/_form.html.erb +9 -0
  65. data/app/views/decidim/collaborative_texts/documents/_editor_template.html.erb +15 -0
  66. data/app/views/decidim/collaborative_texts/documents/_manager.html.erb +9 -0
  67. data/app/views/decidim/collaborative_texts/documents/_suggestions_box_item_template.html.erb +33 -0
  68. data/app/views/decidim/collaborative_texts/documents/_suggestions_box_template.html.erb +20 -0
  69. data/app/views/decidim/collaborative_texts/documents/index.html.erb +29 -0
  70. data/app/views/decidim/collaborative_texts/documents/show.html.erb +70 -0
  71. data/config/assets.rb +8 -0
  72. data/config/locales/am-ET.yml +1 -0
  73. data/config/locales/ar.yml +1 -0
  74. data/config/locales/bg.yml +1 -0
  75. data/config/locales/bn-BD.yml +1 -0
  76. data/config/locales/bs-BA.yml +1 -0
  77. data/config/locales/ca-IT.yml +154 -0
  78. data/config/locales/ca.yml +154 -0
  79. data/config/locales/cs.yml +122 -0
  80. data/config/locales/da.yml +1 -0
  81. data/config/locales/de.yml +154 -0
  82. data/config/locales/el.yml +1 -0
  83. data/config/locales/en.yml +154 -0
  84. data/config/locales/eo.yml +1 -0
  85. data/config/locales/es-MX.yml +154 -0
  86. data/config/locales/es-PY.yml +154 -0
  87. data/config/locales/es.yml +154 -0
  88. data/config/locales/et.yml +1 -0
  89. data/config/locales/eu.yml +154 -0
  90. data/config/locales/fa-IR.yml +1 -0
  91. data/config/locales/fi-plain.yml +154 -0
  92. data/config/locales/fi.yml +154 -0
  93. data/config/locales/fr-CA.yml +125 -0
  94. data/config/locales/fr.yml +125 -0
  95. data/config/locales/ga-IE.yml +1 -0
  96. data/config/locales/gl.yml +1 -0
  97. data/config/locales/gn-PY.yml +1 -0
  98. data/config/locales/he-IL.yml +1 -0
  99. data/config/locales/hr.yml +1 -0
  100. data/config/locales/hu.yml +1 -0
  101. data/config/locales/id-ID.yml +1 -0
  102. data/config/locales/is-IS.yml +1 -0
  103. data/config/locales/it.yml +1 -0
  104. data/config/locales/ja.yml +153 -0
  105. data/config/locales/ka-GE.yml +1 -0
  106. data/config/locales/kaa.yml +1 -0
  107. data/config/locales/ko.yml +1 -0
  108. data/config/locales/lb.yml +1 -0
  109. data/config/locales/lo-LA.yml +1 -0
  110. data/config/locales/lt.yml +1 -0
  111. data/config/locales/lv.yml +1 -0
  112. data/config/locales/mt.yml +1 -0
  113. data/config/locales/nl.yml +1 -0
  114. data/config/locales/no.yml +6 -0
  115. data/config/locales/oc-FR.yml +1 -0
  116. data/config/locales/om-ET.yml +1 -0
  117. data/config/locales/pl.yml +1 -0
  118. data/config/locales/pt-BR.yml +1 -0
  119. data/config/locales/pt.yml +1 -0
  120. data/config/locales/ro-RO.yml +89 -0
  121. data/config/locales/ru.yml +1 -0
  122. data/config/locales/si-LK.yml +1 -0
  123. data/config/locales/sk.yml +1 -0
  124. data/config/locales/sl.yml +1 -0
  125. data/config/locales/so-SO.yml +1 -0
  126. data/config/locales/sq-AL.yml +1 -0
  127. data/config/locales/sr-CS.yml +1 -0
  128. data/config/locales/sv.yml +117 -0
  129. data/config/locales/sw-KE.yml +1 -0
  130. data/config/locales/th-TH.yml +1 -0
  131. data/config/locales/ti-ER.yml +1 -0
  132. data/config/locales/tr-TR.yml +69 -0
  133. data/config/locales/uk.yml +1 -0
  134. data/config/locales/val-ES.yml +1 -0
  135. data/config/locales/vi.yml +1 -0
  136. data/config/locales/zh-CN.yml +1 -0
  137. data/config/locales/zh-TW.yml +1 -0
  138. data/db/migrate/20250205215038_create_decidim_collaborative_texts_documents.rb +16 -0
  139. data/db/migrate/20250213113536_create_collaborative_texts_versions.rb +13 -0
  140. data/db/migrate/20250227204839_create_collaborative_texts_suggestions.rb +13 -0
  141. data/db/migrate/20250312140133_add_counter_caches_to_collaborative_texts_documents.rb +13 -0
  142. data/db/migrate/20250408205231_add_counter_caches_to_collaborative_text_versions.rb +8 -0
  143. data/decidim-collaborative_texts.gemspec +36 -0
  144. data/lib/decidim/api/document_input_filter.rb +29 -0
  145. data/lib/decidim/api/document_input_sort.rb +14 -0
  146. data/lib/decidim/api/document_type.rb +31 -0
  147. data/lib/decidim/api/documents_type.rb +39 -0
  148. data/lib/decidim/api/suggestion_type.rb +18 -0
  149. data/lib/decidim/api/version_type.rb +21 -0
  150. data/lib/decidim/collaborative_texts/admin.rb +10 -0
  151. data/lib/decidim/collaborative_texts/admin_engine.rb +34 -0
  152. data/lib/decidim/collaborative_texts/api.rb +12 -0
  153. data/lib/decidim/collaborative_texts/component.rb +54 -0
  154. data/lib/decidim/collaborative_texts/engine.rb +28 -0
  155. data/lib/decidim/collaborative_texts/seeds.rb +117 -0
  156. data/lib/decidim/collaborative_texts/test/factories.rb +80 -0
  157. data/lib/decidim/collaborative_texts/version.rb +9 -0
  158. data/lib/decidim/collaborative_texts.rb +13 -0
  159. metadata +233 -0
@@ -0,0 +1,287 @@
1
+ .collaborative-texts {
2
+ &-toc {
3
+ @apply w-full py-8 lg:sticky top-0;
4
+
5
+ .spinner-container::after {
6
+ @apply bg-transparent;
7
+ }
8
+
9
+ ul {
10
+ @apply list-none;
11
+
12
+ li {
13
+ @apply flex items-center gap-2 text-secondary text-lg leading-8 cursor-pointer;
14
+ }
15
+ }
16
+ }
17
+
18
+ &-container {
19
+ @apply relative mb-8 md:mr-[18rem] pr-4;
20
+
21
+ h2 {
22
+ @apply mt-8;
23
+ }
24
+ }
25
+
26
+ &-hidden:not(&-highlight-shown),
27
+ &-highlight-hidden {
28
+ @apply hidden;
29
+ }
30
+
31
+ &-manager {
32
+ @apply sticky top-4 drop-shadow z-10 p-4 bg-background rounded border border-black;
33
+
34
+ > div {
35
+ @apply flex justify-between gap-2 mb-4;
36
+ }
37
+
38
+ &.hidden {
39
+ display: none;
40
+ }
41
+
42
+ &-applied,
43
+ &-pending {
44
+ @apply font-bold;
45
+ }
46
+ }
47
+
48
+ &-alert {
49
+ @apply fixed top-8 border-alert z-[40] bg-white rounded;
50
+
51
+ > div {
52
+ @apply flex items-center gap-2 p-4 bg-alert/20 rounded;
53
+ }
54
+ }
55
+
56
+ &-selection {
57
+ @apply hidden;
58
+ }
59
+
60
+ &-highlight {
61
+ @apply relative outline-dashed outline-1 outline-offset-2 outline-gray;
62
+
63
+ &:not(.applied) .collaborative-texts-hidden {
64
+ @apply block;
65
+ }
66
+ }
67
+
68
+ &-editor {
69
+ @apply bg-gray-5 mb-4 p-2 pt-1 rounded;
70
+
71
+ &-header {
72
+ @apply flex gap-2 border-b border-gray-6 justify-between items-baseline;
73
+
74
+ > svg {
75
+ @apply fill-secondary text-md;
76
+ }
77
+ }
78
+
79
+ &-profile {
80
+ @apply mt-4;
81
+
82
+ a {
83
+ @apply text-black;
84
+ }
85
+
86
+ img {
87
+ @apply my-0;
88
+ }
89
+ }
90
+
91
+ &-container {
92
+ @apply px-2 pt-2 pb-4 my-4 bg-white rounded border border-gray-2 text-md;
93
+
94
+ &:focus-visible {
95
+ @apply outline-none;
96
+ }
97
+ }
98
+
99
+ &-menu {
100
+ @apply flex justify-end;
101
+ }
102
+ }
103
+
104
+ &-menu {
105
+ @apply absolute top-0 right-0 z-[9] flex gap-2;
106
+ }
107
+
108
+ &-suggestions-box {
109
+ @apply bg-gray-5 shadow outline outline-gray-3 outline-1 text-xs rounded px-2 pb-2 pt-1 md:pt-0 md:absolute md:top-0 md:right-[-18rem] md:w-[18rem] md:transition-all ease-in-out duration-300;
110
+
111
+ .icon {
112
+ @apply inline-block text-md mr-2;
113
+
114
+ &-hover,
115
+ &-applied {
116
+ @apply hidden;
117
+ }
118
+ }
119
+
120
+ &:has([aria-hidden="false"]) {
121
+ @apply z-[9] shadow-lg;
122
+
123
+ &:has(.collaborative-texts-suggestions-box-item:hover) {
124
+ .icon-pending {
125
+ @apply hidden;
126
+ }
127
+
128
+ .icon-applied {
129
+ @apply hidden;
130
+ }
131
+
132
+ .icon-hover {
133
+ @apply inline-block;
134
+ }
135
+ }
136
+
137
+ &:has(.applied.collaborative-texts-suggestions-box-item:hover) {
138
+ .icon-hover {
139
+ @apply hidden;
140
+ }
141
+
142
+ .icon-applied {
143
+ @apply inline-block;
144
+ }
145
+ }
146
+ }
147
+
148
+ &:has(.applied) {
149
+ .icon-applied {
150
+ @apply inline-block;
151
+ }
152
+
153
+ .icon-pending {
154
+ @apply hidden;
155
+ }
156
+
157
+ .icon-hover {
158
+ @apply hidden;
159
+ }
160
+ }
161
+
162
+ &:hover {
163
+ @apply z-10;
164
+ }
165
+
166
+ &-header {
167
+ @apply flex items-center justify-between text-secondary mb-4 fill-secondary;
168
+ }
169
+
170
+ &-item {
171
+ @apply p-2 pr-0 mb-4 rounded w-full;
172
+
173
+ &:hover {
174
+ @apply bg-white drop-shadow relative z-[1];
175
+ }
176
+
177
+ .button-restore {
178
+ @apply hidden;
179
+ }
180
+
181
+ &.applied {
182
+ @apply bg-white drop-shadow;
183
+
184
+ .button-restore {
185
+ @apply flex;
186
+ }
187
+
188
+ .button-apply {
189
+ @apply hidden;
190
+ }
191
+ }
192
+
193
+ img {
194
+ @apply m-0;
195
+ }
196
+
197
+ .text {
198
+ @apply font-semibold;
199
+ }
200
+ }
201
+
202
+ &-item-header {
203
+ @apply flex items-center justify-between;
204
+ }
205
+
206
+ [data-controller="accordion"] {
207
+ @apply relative bg-gray-5;
208
+
209
+ > button {
210
+ @apply flex outline-none w-full justify-between;
211
+
212
+ svg {
213
+ @apply fill-secondary;
214
+ }
215
+ }
216
+
217
+ [id*="panel"][aria-hidden="false"] {
218
+ @apply z-10 relative;
219
+ }
220
+
221
+ [aria-expanded="false"] > svg:last-of-type,
222
+ [aria-expanded="false"] > span:last-of-type,
223
+ [aria-expanded="true"] > span:first-of-type,
224
+ [aria-expanded="true"] > svg:first-of-type {
225
+ @apply hidden;
226
+ }
227
+
228
+ [aria-expanded="true"] > svg:last-of-type,
229
+ [aria-expanded="true"] > span:last-of-type,
230
+ [aria-expanded="false"] > span:first-of-type,
231
+ [aria-expanded="false"] > svg:first-of-type {
232
+ @apply block;
233
+ }
234
+ }
235
+
236
+ /* overwrite default dropdown styles */
237
+ [id*="dropdown-trigger"] {
238
+ @apply flex pr-1;
239
+
240
+ > svg {
241
+ @apply h-3 w-3;
242
+ }
243
+ }
244
+
245
+ [id*="dropdown-menu"] {
246
+ @apply cursor-auto w-auto;
247
+
248
+ &[aria-hidden="true"] {
249
+ @apply hidden;
250
+ }
251
+
252
+ ul {
253
+ @apply list-none;
254
+ }
255
+
256
+ &[aria-hidden="false"] {
257
+ animation-name: fadeIn;
258
+ animation-duration: 300ms;
259
+ animation-fill-mode: forwards;
260
+ }
261
+
262
+ @keyframes fadeIn {
263
+ from {
264
+ opacity: 0;
265
+ visibility: hidden;
266
+ }
267
+
268
+ to {
269
+ opacity: 1;
270
+ visibility: visible;
271
+ }
272
+ }
273
+
274
+ @keyframes fadeOut {
275
+ from {
276
+ opacity: 1;
277
+ visibility: visible;
278
+ }
279
+
280
+ to {
281
+ opacity: 0;
282
+ visibility: hidden;
283
+ }
284
+ }
285
+ }
286
+ }
287
+ }
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module CollaborativeTexts
5
+ module Admin
6
+ class Permissions < Decidim::DefaultPermissions
7
+ def permissions
8
+ return permission_action if permission_action.scope != :admin
9
+
10
+ return permission_action if permission_action.subject != :collaborative_text
11
+
12
+ case permission_action.action
13
+ when :update, :read, :create, :destroy
14
+ allow!
15
+ end
16
+
17
+ permission_action
18
+ end
19
+
20
+ private
21
+
22
+ def document
23
+ @document ||= context.fetch(:document, nil)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module CollaborativeTexts
5
+ class Permissions < Decidim::DefaultPermissions
6
+ def permissions
7
+ # Delegate the admin permission checks to the admin permissions class
8
+ return Decidim::CollaborativeTexts::Admin::Permissions.new(user, permission_action, context).permissions if permission_action.scope == :admin
9
+ return permission_action if permission_action.scope != :public
10
+
11
+ return permission_action if permission_action.subject != :collaborative_text
12
+
13
+ case permission_action.action
14
+ when :suggest
15
+ allow! if user
16
+ when :rollout
17
+ allow! if user && (user.admin? || space_allows_admin_access?)
18
+ end
19
+
20
+ permission_action
21
+ end
22
+
23
+ private
24
+
25
+ def document
26
+ @document ||= context.fetch(:document, nil) || context.fetch(:resource, nil)
27
+ end
28
+
29
+ def space_allows_admin_access?
30
+ return false unless document.participatory_space.respond_to?(:admins)
31
+
32
+ document.participatory_space.admins.exists?(id: user.id)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module CollaborativeTexts
5
+ module AdminLog
6
+ # This class holds the logic to present a `Decidim::CollaborativeTexts::Document`
7
+ # for the `AdminLog` log.
8
+ class DocumentPresenter < Decidim::Log::BasePresenter
9
+ private
10
+
11
+ def action_string
12
+ case action
13
+ when "create", "delete", "update", "soft_delete", "restore", "publish", "unpublish"
14
+ "decidim.collaborative_texts.admin_log.document.#{action}"
15
+ else
16
+ super
17
+ end
18
+ end
19
+
20
+ def diff_fields_mapping
21
+ {
22
+ title: :string,
23
+ body: :string,
24
+ version_number: :integer
25
+ }
26
+ end
27
+
28
+ def i18n_labels_scope
29
+ "activemodel.attributes.collaborative_texts.document"
30
+ end
31
+
32
+ # adds the body from the version to the changeset
33
+ def changeset
34
+ Decidim::Log::DiffChangesetCalculator.new(
35
+ full_changeset,
36
+ diff_fields_mapping,
37
+ i18n_labels_scope
38
+ ).changeset
39
+ end
40
+
41
+ def full_changeset
42
+ action_log.version.changeset.tap do |changeset|
43
+ if action == "create"
44
+ changeset[:body] = [nil, action_log&.extra&.dig("extra", "body")]
45
+ changeset[:version_number] = [nil, "1"]
46
+ elsif (version_number = action_log&.extra&.dig("extra", "version_number"))
47
+ changeset[:version_number] = [nil, version_number]
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module CollaborativeTexts
5
+ module AdminLog
6
+ # This class holds the logic to present a `Decidim::CollaborativeTexts::Suggestion`
7
+ # for the `AdminLog` log.
8
+ # Note that this is only used in updates, creation is handled by the document creation.
9
+ #
10
+ class SuggestionPresenter < Decidim::Log::BasePresenter
11
+ private
12
+
13
+ def action_string
14
+ case action
15
+ when "create"
16
+ "decidim.collaborative_texts.admin_log.suggestion.#{action}"
17
+ else
18
+ super
19
+ end
20
+ end
21
+
22
+ def i18n_labels_scope
23
+ "activemodel.attributes.collaborative_texts.suggestion"
24
+ end
25
+
26
+ def diff_fields_mapping
27
+ {
28
+ original: :array,
29
+ replace: :array,
30
+ nodes: :array
31
+ }
32
+ end
33
+
34
+ # Private: Caches the object that will be responsible of presenting the newsletter.
35
+ # Overwrites the method so that we can use a custom presenter to show the correct
36
+ # path for the newsletter.
37
+ #
38
+ # Returns an object that responds to `present`.
39
+ def resource_presenter
40
+ @resource_presenter ||= Decidim::CollaborativeTexts::AdminLog::SuggestionResourcePresenter.new(action_log.resource, h, action_log.extra["resource"])
41
+ end
42
+
43
+ #
44
+ # Currently, only the action "create" exists. Update this if other
45
+ # actions are added in the future.
46
+ def changeset
47
+ changeset = action_log.version.changeset
48
+ current = changeset.delete("changeset")[1]
49
+ changeset["original"] = [nil, current["original"]]
50
+ changeset["replace"] = [nil, current["replace"]]
51
+ changeset["nodes"] = [nil, (current["firstNode"]..current["lastNode"]).to_a]
52
+
53
+ Decidim::Log::DiffChangesetCalculator.new(
54
+ changeset,
55
+ diff_fields_mapping,
56
+ i18n_labels_scope
57
+ ).changeset
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module CollaborativeTexts
5
+ module AdminLog
6
+ # This class extends the default resource presenter for logs, so that
7
+ # it can properly link to the newsletter.
8
+ class SuggestionResourcePresenter < Decidim::Log::ResourcePresenter
9
+ private
10
+
11
+ # Private: Finds the admin link for the newsletter.
12
+ #
13
+ # Returns an HTML-safe String.
14
+ def resource_path
15
+ @resource_path ||= Decidim::ResourceLocatorPresenter.new(resource.document).path(anchor: "ct-node-#{resource.changeset["firstNode"]}")
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module CollaborativeTexts
5
+ module AdminLog
6
+ # This class holds the logic to present a `Decidim::CollaborativeTexts::Version`
7
+ # for the `AdminLog` log.
8
+ # Note that this is only used in updates, creation is handled by the document creation.
9
+ #
10
+ class VersionPresenter < Decidim::Log::BasePresenter
11
+ private
12
+
13
+ def action_string
14
+ case action
15
+ when "delete", "update"
16
+ "decidim.collaborative_texts.admin_log.version.#{action}"
17
+ else
18
+ super
19
+ end
20
+ end
21
+
22
+ def i18n_labels_scope
23
+ "activemodel.attributes.collaborative_texts.version"
24
+ end
25
+
26
+ def diff_fields_mapping
27
+ {
28
+ body: :string,
29
+ draft: :boolean,
30
+ version_number: :integer
31
+ }
32
+ end
33
+
34
+ def changeset
35
+ Decidim::Log::DiffChangesetCalculator.new(
36
+ full_changeset,
37
+ diff_fields_mapping,
38
+ i18n_labels_scope
39
+ ).changeset
40
+ end
41
+
42
+ def full_changeset
43
+ action_log.version.changeset.tap do |changeset|
44
+ changeset[:version_number] = [
45
+ nil,
46
+ action_log.extra.dig("extra", "version_number")
47
+ ]
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module CollaborativeTexts
5
+ #
6
+ # A dummy presenter to abstract out the author of an official post.
7
+ #
8
+ class OfficialAuthorPresenter < Decidim::OfficialAuthorPresenter
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module CollaborativeTexts
5
+ #
6
+ # Decorator for collaborative texts suggestions.
7
+ #
8
+ class SuggestionPresenter < Decidim::ResourcePresenter
9
+ include Decidim::ApplicationHelper
10
+
11
+ delegate :changeset, :status, :created_at, :author, to: :suggestion
12
+ # A summary to print in the UI
13
+ def summary
14
+ txt = type == :remove ? original : text
15
+ @summary ||= I18n.t("decidim.collaborative_texts.suggestion.#{type}_html", text: txt.truncate(150))
16
+ end
17
+
18
+ # A text representation of the changeset. Without HTML.
19
+ def text
20
+ @text ||= ActionView::Base.full_sanitizer.sanitize(changeset["replace"]&.join(" ")&.strip).to_s
21
+ end
22
+
23
+ def original
24
+ @original ||= ActionView::Base.full_sanitizer.sanitize(changeset["original"]&.join(" ")&.strip).to_s
25
+ end
26
+
27
+ def suggestion
28
+ __getobj__
29
+ end
30
+
31
+ # Render the suggestion title
32
+ #
33
+ # Returns a String.
34
+ def title(html_escape: false, all_locales: false)
35
+ super(suggestion.document.title, html_escape, all_locales)
36
+ end
37
+
38
+ def type
39
+ return :remove if text.blank?
40
+ return :add if text.include?(original.to_s)
41
+
42
+ :replace
43
+ end
44
+
45
+ def safe_json
46
+ {
47
+ id:,
48
+ changeset:,
49
+ summary:,
50
+ status:,
51
+ type:,
52
+ createdAt: created_at
53
+ }
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,82 @@
1
+ <button type="button" data-controller="dropdown" data-target="actions-post-<%= document.id %>" aria-label="<%= t("decidim.admin.actions.actions_label", resource: document.title) %>">
2
+ <%= icon "more-fill", class: "text-secondary" %>
3
+ </button>
4
+
5
+ <div class="inline-block relative">
6
+ <ul id="actions-post-<%= document.id %>" class="dropdown dropdown__action" aria-hidden="true">
7
+ <% if view == :deleted %>
8
+ <% if allowed_to? :restore, :collaborative_text, trashable_deleted_resource: document %>
9
+ <li class="dropdown__item">
10
+ <%= link_to restore_document_path(document), method: :patch, class: "dropdown__button" do %>
11
+ <%= icon "refresh-line" %>
12
+ <%= t("actions.restore", scope: "decidim.admin") %>
13
+ <% end %>
14
+ </li>
15
+ <% end %>
16
+ <% else %>
17
+ <% if allowed_to? :update, :collaborative_text, document: document %>
18
+ <li class="dropdown__item">
19
+ <%= link_to edit_document_path(document), class: "dropdown__button" do %>
20
+ <%= icon "pencil-line" %>
21
+ <%= t("actions.edit", scope: "decidim.collaborative_texts") %>
22
+ <% end %>
23
+ </li>
24
+ <li class="dropdown__item">
25
+ <%= link_to edit_settings_document_path(document), class: "dropdown__button" do %>
26
+ <%= icon "settings-4-line" %>
27
+ <%= t("actions.manage", scope: "decidim.collaborative_texts") %>
28
+ <% end %>
29
+ </li>
30
+ <% end %>
31
+
32
+ <% if allowed_to?(:update, :collaborative_text, document: document) %>
33
+ <hr>
34
+
35
+ <% if document.published? %>
36
+ <li class="dropdown__item">
37
+ <%= link_to unpublish_document_path(document), method: :put, class: "dropdown__button" do %>
38
+ <%= icon "close-circle-line" %>
39
+ <%= t("actions.unpublish", scope: "decidim.admin") %>
40
+ <% end %>
41
+ </li>
42
+ <% else %>
43
+ <li class="dropdown__item">
44
+ <%= link_to publish_document_path(document), method: :put, class: "dropdown__button" do %>
45
+ <%= icon "check-line" %>
46
+ <%= t("actions.publish", scope: "decidim.admin") %>
47
+ <% end %>
48
+ </li>
49
+ <% end %>
50
+ <% end %>
51
+
52
+ <hr>
53
+
54
+ <li class="dropdown__item">
55
+ <%= link_to resource_locator(document).path, target: :blank, data: { "external-link": false }, class: "dropdown__button" do %>
56
+ <%= icon "eye-line" %>
57
+ <%= t("actions.preview", scope: "decidim.admin") %>
58
+ <% end %>
59
+ </li>
60
+
61
+ <hr>
62
+
63
+ <li class="dropdown__item">
64
+ <% if allowed_to? :soft_delete, :collaborative_text, trashable_deleted_resource: document %>
65
+ <%= link_to soft_delete_document_path(document), method: :patch, data: { confirm: t("actions.confirm_delete_document", scope: "decidim.collaborative_texts") }, class: "dropdown__button" do %>
66
+ <%= icon "delete-bin-line" %>
67
+ <%= t("actions.soft_delete", scope: "decidim.admin") %>
68
+ <% end %>
69
+ <% else %>
70
+ <div class="dropdown__button-disabled">
71
+ <%= with_tooltip t("tooltips.deleted_collaborative_texts_info", scope: "decidim.admin") do %>
72
+ <%= icon "delete-bin-line", class: "text-gray" %>
73
+ <span>
74
+ <%= t("actions.soft_delete", scope: "decidim.admin") %>
75
+ </span>
76
+ <% end %>
77
+ </div>
78
+ <% end %>
79
+ </li>
80
+ <% end %>
81
+ </ul>
82
+ </div>