collavre 0.5.0 → 0.7.0

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 (133) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/collavre/comment_versions.css +76 -0
  3. data/app/assets/stylesheets/collavre/comments_popup.css +347 -37
  4. data/app/assets/stylesheets/collavre/creatives.css +73 -1
  5. data/app/assets/stylesheets/collavre/org_chart.css +319 -0
  6. data/app/assets/stylesheets/collavre/popup.css +68 -1
  7. data/app/controllers/collavre/application_controller.rb +13 -0
  8. data/app/controllers/collavre/comments/versions_controller.rb +82 -0
  9. data/app/controllers/collavre/comments_controller.rb +14 -153
  10. data/app/controllers/collavre/concerns/exportable.rb +30 -0
  11. data/app/controllers/collavre/concerns/shareable.rb +28 -0
  12. data/app/controllers/collavre/concerns/slide_viewable.rb +37 -0
  13. data/app/controllers/collavre/concerns/tree_manageable.rb +141 -0
  14. data/app/controllers/collavre/creative_imports_controller.rb +6 -0
  15. data/app/controllers/collavre/creative_invitations_controller.rb +46 -0
  16. data/app/controllers/collavre/creative_plans_controller.rb +1 -1
  17. data/app/controllers/collavre/creative_shares_controller.rb +84 -14
  18. data/app/controllers/collavre/creatives_controller.rb +70 -194
  19. data/app/controllers/collavre/google_auth_controller.rb +3 -0
  20. data/app/controllers/collavre/invites_controller.rb +2 -1
  21. data/app/controllers/collavre/sessions_controller.rb +3 -0
  22. data/app/controllers/collavre/topics_controller.rb +39 -2
  23. data/app/controllers/collavre/users_controller.rb +5 -404
  24. data/app/controllers/concerns/collavre/comments/approval_actions.rb +108 -0
  25. data/app/controllers/concerns/collavre/comments/batch_operations.rb +55 -0
  26. data/app/controllers/concerns/collavre/comments/conversion.rb +46 -0
  27. data/app/controllers/concerns/collavre/users_controller/admin_operations.rb +74 -0
  28. data/app/controllers/concerns/collavre/users_controller/ai_user_management.rb +119 -0
  29. data/app/controllers/concerns/collavre/users_controller/contact_management.rb +166 -0
  30. data/app/controllers/concerns/collavre/users_controller/profile_and_settings.rb +102 -0
  31. data/app/controllers/concerns/collavre/users_controller/registration.rb +63 -0
  32. data/app/helpers/collavre/application_helper.rb +1 -0
  33. data/app/helpers/collavre/creatives_helper.rb +12 -9
  34. data/app/helpers/collavre/navigation_helper.rb +1 -1
  35. data/app/javascript/collavre.js +0 -1
  36. data/app/javascript/controllers/comment_controller.js +33 -70
  37. data/app/javascript/controllers/comment_version_controller.js +164 -0
  38. data/app/javascript/controllers/comments/__tests__/form_controller_review.test.js +305 -0
  39. data/app/javascript/controllers/comments/__tests__/list_controller_selection.test.js +103 -0
  40. data/app/javascript/controllers/comments/__tests__/review_quotes_store.test.js +113 -0
  41. data/app/javascript/controllers/comments/contexts_controller.js +363 -0
  42. data/app/javascript/controllers/comments/form_controller.js +304 -13
  43. data/app/javascript/controllers/comments/list_controller.js +151 -62
  44. data/app/javascript/controllers/comments/popup_controller.js +66 -38
  45. data/app/javascript/controllers/comments/presence_controller.js +2 -10
  46. data/app/javascript/controllers/comments/review_quotes_store.js +189 -0
  47. data/app/javascript/controllers/comments/topics_controller.js +34 -10
  48. data/app/javascript/controllers/index.js +15 -1
  49. data/app/javascript/controllers/org_chart_controller.js +46 -0
  50. data/app/javascript/controllers/share_modal_controller.js +369 -0
  51. data/app/javascript/controllers/topic_search_controller.js +103 -0
  52. data/app/javascript/creatives/drag_drop/event_handlers.js +42 -1
  53. data/app/javascript/lib/api/creatives.js +12 -0
  54. data/app/javascript/lib/api/csrf_fetch.js +35 -0
  55. data/app/javascript/lib/api/drag_drop.js +17 -0
  56. data/app/javascript/modules/command_menu.js +40 -0
  57. data/app/javascript/modules/creative_row_editor.js +88 -0
  58. data/app/javascript/modules/slide_view.js +2 -1
  59. data/app/jobs/collavre/ai_agent_job.rb +42 -30
  60. data/app/jobs/collavre/compress_job.rb +92 -0
  61. data/app/models/collavre/comment.rb +36 -1
  62. data/app/models/collavre/comment_version.rb +15 -0
  63. data/app/models/collavre/creative/describable.rb +1 -1
  64. data/app/models/collavre/creative.rb +51 -0
  65. data/app/models/collavre/task.rb +30 -2
  66. data/app/models/collavre/user.rb +20 -3
  67. data/app/services/collavre/ai_agent/a2a_dispatcher.rb +68 -0
  68. data/app/services/collavre/ai_agent/agent_lifecycle_manager.rb +89 -0
  69. data/app/services/collavre/ai_agent/message_builder.rb +85 -6
  70. data/app/services/collavre/ai_agent/response_finalizer.rb +97 -0
  71. data/app/services/collavre/ai_agent/response_streamer.rb +56 -0
  72. data/app/services/collavre/ai_agent/review_handler.rb +18 -1
  73. data/app/services/collavre/ai_agent_service.rb +130 -183
  74. data/app/services/collavre/ai_client.rb +6 -0
  75. data/app/services/collavre/auto_theme_generator.rb +1 -1
  76. data/app/services/collavre/command_menu_service.rb +19 -0
  77. data/app/services/collavre/comments/command_processor.rb +3 -1
  78. data/app/services/collavre/comments/compress_command.rb +75 -0
  79. data/app/services/collavre/comments/concerns/workflow_support.rb +115 -0
  80. data/app/services/collavre/comments/work_command.rb +161 -0
  81. data/app/services/collavre/comments/workflow_executor.rb +276 -0
  82. data/app/services/collavre/creatives/plan_tagger.rb +14 -3
  83. data/app/services/collavre/creatives/tree_formatter.rb +53 -13
  84. data/app/services/collavre/gemini_parent_recommender.rb +4 -4
  85. data/app/services/collavre/orchestration/agent_context_builder.rb +1 -3
  86. data/app/services/collavre/orchestration/agent_orchestrator.rb +15 -4
  87. data/app/services/collavre/orchestration/policy_resolver.rb +0 -19
  88. data/app/services/collavre/orchestration/scheduler.rb +3 -2
  89. data/app/services/collavre/orchestration/stuck_detector.rb +1 -1
  90. data/app/services/collavre/system_events/dispatcher.rb +9 -0
  91. data/app/services/collavre/tools/creative_create_service.rb +1 -8
  92. data/app/services/collavre/tools/creative_import_service.rb +46 -0
  93. data/app/services/collavre/tools/creative_retrieval_service.rb +157 -96
  94. data/app/services/collavre/tools/creative_update_service.rb +1 -8
  95. data/app/services/collavre/tools/cron_list_service.rb +1 -1
  96. data/app/services/collavre/tools/description_normalizable.rb +16 -0
  97. data/app/views/collavre/comments/_comment.html.erb +25 -8
  98. data/app/views/collavre/comments/_comments_popup.html.erb +32 -5
  99. data/app/views/collavre/creatives/_inline_edit_form.html.erb +13 -0
  100. data/app/views/collavre/creatives/_share_button.html.erb +4 -1
  101. data/app/views/collavre/creatives/_share_modal.html.erb +31 -1
  102. data/app/views/collavre/creatives/index.html.erb +5 -5
  103. data/app/views/collavre/creatives/slide_view.html.erb +1 -1
  104. data/app/views/collavre/users/{_contact_management.html.erb → _contact_list.html.erb} +4 -8
  105. data/app/views/collavre/users/_org_chart.html.erb +68 -0
  106. data/app/views/collavre/users/_org_chart_node.html.erb +169 -0
  107. data/app/views/collavre/users/new_ai.html.erb +9 -0
  108. data/app/views/collavre/users/show.html.erb +32 -8
  109. data/config/locales/comments.en.yml +57 -2
  110. data/config/locales/comments.ko.yml +57 -2
  111. data/config/locales/contacts.en.yml +31 -0
  112. data/config/locales/contacts.ko.yml +31 -0
  113. data/config/locales/contexts.en.yml +8 -0
  114. data/config/locales/contexts.ko.yml +8 -0
  115. data/config/locales/creatives.en.yml +6 -0
  116. data/config/locales/creatives.ko.yml +6 -0
  117. data/config/locales/users.en.yml +1 -0
  118. data/config/locales/users.ko.yml +1 -0
  119. data/config/routes.rb +14 -1
  120. data/db/migrate/20260220072200_add_workflow_fields_to_tasks.rb +12 -0
  121. data/db/migrate/20260223173533_add_review_type_to_comments.rb +5 -0
  122. data/db/migrate/20260225065200_create_comment_versions.rb +14 -0
  123. data/db/migrate/20260225074416_add_selected_version_id_to_comments.rb +7 -0
  124. data/lib/collavre/version.rb +1 -1
  125. metadata +47 -10
  126. data/app/javascript/lib/lexical/__tests__/action_text_attachment_node.test.jsx +0 -91
  127. data/app/javascript/lib/lexical/action_text_attachment_node.js +0 -459
  128. data/app/javascript/lib/lexical/dom_attachment_utils.js +0 -66
  129. data/app/javascript/modules/share_modal.js +0 -76
  130. data/app/javascript/modules/share_user_popup.js +0 -77
  131. data/app/services/collavre/orchestration/self_reflection_evaluator.rb +0 -231
  132. data/app/views/collavre/comments/_presence_avatars.html.erb +0 -8
  133. data/app/views/collavre/creatives/_delete_button.html.erb +0 -12
@@ -0,0 +1,319 @@
1
+ .org-chart-tree {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--space-3);
5
+ }
6
+
7
+ .org-chart-group {
8
+ border: 1px solid var(--border-color);
9
+ border-radius: var(--radius-2);
10
+ overflow: hidden;
11
+ }
12
+
13
+ .org-chart-group .org-chart-group {
14
+ border: none;
15
+ border-top: 1px solid var(--border-color);
16
+ border-radius: 0;
17
+ }
18
+
19
+ .org-chart-creative-row {
20
+ display: flex;
21
+ align-items: center;
22
+ gap: var(--space-2);
23
+ padding: var(--space-2) var(--space-3);
24
+ background-color: var(--surface-section);
25
+ cursor: pointer;
26
+ list-style: none;
27
+ user-select: none;
28
+ }
29
+
30
+ .org-chart-creative-row::-webkit-details-marker {
31
+ display: none;
32
+ }
33
+
34
+ .org-chart-creative-row::before {
35
+ content: '▶';
36
+ font-size: 0.7em;
37
+ color: var(--text-muted);
38
+ transition: transform 0.15s ease;
39
+ flex-shrink: 0;
40
+ }
41
+
42
+ details[open] > .org-chart-creative-row::before {
43
+ transform: rotate(90deg);
44
+ }
45
+
46
+ .org-chart-creative-row {
47
+ --depth: 0;
48
+ padding-left: calc(var(--space-5) * var(--depth));
49
+ }
50
+
51
+ .org-chart-depth-0 {
52
+ background-color: var(--surface-section);
53
+ font-weight: var(--weight-6);
54
+ }
55
+
56
+ .org-chart-creative-row:not(.org-chart-depth-0) {
57
+ background-color: transparent;
58
+ font-weight: var(--weight-4);
59
+ font-size: max(0.85em, 1em - var(--depth) * 0.05em);
60
+ }
61
+
62
+ .org-chart-creative-link {
63
+ color: var(--text-primary);
64
+ text-decoration: none;
65
+ flex: 1;
66
+ min-width: 0;
67
+ overflow: hidden;
68
+ text-overflow: ellipsis;
69
+ white-space: nowrap;
70
+ }
71
+
72
+ .org-chart-creative-link:hover {
73
+ color: var(--color-link);
74
+ }
75
+
76
+ .org-chart-owner-badge {
77
+ font-size: var(--text-0);
78
+ color: var(--text-muted);
79
+ padding: 2px 6px;
80
+ border-radius: var(--radius-1);
81
+ background-color: var(--surface-input);
82
+ white-space: nowrap;
83
+ }
84
+
85
+ /* Manage permissions button on creative row */
86
+ .org-chart-manage-btn {
87
+ margin-left: auto;
88
+ }
89
+
90
+ .org-chart-content {
91
+ border-top: 1px solid var(--border-color);
92
+ }
93
+
94
+ .org-chart-members {
95
+ padding: var(--space-1) 0;
96
+ }
97
+
98
+ .org-chart-members,
99
+ .org-chart-children,
100
+ .org-chart-no-members {
101
+ --indent: 0;
102
+ }
103
+
104
+ .org-chart-members .org-chart-member-row,
105
+ .org-chart-no-members {
106
+ padding-left: calc(var(--space-5) * (var(--indent) + 1) + var(--space-4));
107
+ }
108
+
109
+ .org-chart-toolbar {
110
+ display: flex;
111
+ justify-content: flex-end;
112
+ margin-bottom: var(--space-2);
113
+ }
114
+
115
+ .org-chart-children {
116
+ padding: 0;
117
+ }
118
+
119
+ /* Grid layout for column alignment */
120
+ .org-chart-member-row {
121
+ display: grid;
122
+ grid-template-columns: 1fr auto auto;
123
+ align-items: center;
124
+ padding: var(--space-1) var(--space-3);
125
+ gap: var(--space-3);
126
+ }
127
+
128
+ .org-chart-member-row:hover {
129
+ background-color: var(--surface-input);
130
+ }
131
+
132
+ /* Column 1: User info (avatar + name + email) */
133
+ .org-chart-member-info {
134
+ display: flex;
135
+ align-items: center;
136
+ gap: var(--space-2);
137
+ min-width: 0;
138
+ }
139
+
140
+ .org-chart-member-name-group {
141
+ display: flex;
142
+ align-items: baseline;
143
+ gap: var(--space-2);
144
+ min-width: 0;
145
+ overflow: hidden;
146
+ }
147
+
148
+ .org-chart-member-name {
149
+ overflow: hidden;
150
+ text-overflow: ellipsis;
151
+ white-space: nowrap;
152
+ color: var(--text-primary);
153
+ text-decoration: none;
154
+ font-weight: var(--weight-5);
155
+ }
156
+
157
+ .org-chart-member-name:hover {
158
+ color: var(--color-link);
159
+ }
160
+
161
+ .org-chart-member-email {
162
+ font-size: var(--text-0);
163
+ color: var(--text-muted);
164
+ white-space: nowrap;
165
+ overflow: hidden;
166
+ text-overflow: ellipsis;
167
+ }
168
+
169
+ /* Column 2: Permission badge/select */
170
+ .org-chart-member-permission {
171
+ display: flex;
172
+ align-items: center;
173
+ justify-content: center;
174
+ min-width: 80px;
175
+ }
176
+
177
+ /* Column 3: Actions */
178
+ .org-chart-member-actions {
179
+ display: flex;
180
+ gap: var(--space-1);
181
+ justify-content: flex-end;
182
+ min-width: 120px;
183
+ }
184
+
185
+ /* Permission badges */
186
+ .org-chart-permission-badge {
187
+ font-size: var(--text-0);
188
+ padding: 2px 8px;
189
+ border-radius: var(--radius-round);
190
+ white-space: nowrap;
191
+ }
192
+
193
+ .org-chart-permission-admin {
194
+ background-color: var(--color-danger);
195
+ color: white;
196
+ }
197
+
198
+ .org-chart-permission-write {
199
+ background-color: var(--color-success);
200
+ color: white;
201
+ }
202
+
203
+ .org-chart-permission-feedback {
204
+ background-color: var(--color-warning);
205
+ color: var(--text-primary);
206
+ }
207
+
208
+ .org-chart-permission-read {
209
+ background-color: var(--text-muted);
210
+ color: white;
211
+ }
212
+
213
+ .org-chart-permission-no_access {
214
+ background-color: var(--surface-input);
215
+ color: var(--text-muted);
216
+ }
217
+
218
+ /* Share direction labels */
219
+ .org-chart-direction {
220
+ font-size: var(--text-0);
221
+ white-space: nowrap;
222
+ }
223
+
224
+ .org-chart-direction-outgoing {
225
+ color: var(--color-brand);
226
+ }
227
+
228
+ .org-chart-direction-incoming {
229
+ color: var(--color-warning);
230
+ }
231
+
232
+ /* Permission select dropdown */
233
+ .org-chart-permission-select {
234
+ font-size: var(--text-0);
235
+ padding: 2px 6px;
236
+ border-radius: var(--radius-round);
237
+ border: 1px solid transparent;
238
+ cursor: pointer;
239
+ appearance: auto;
240
+ white-space: nowrap;
241
+ }
242
+
243
+ .org-chart-permission-select.org-chart-permission-admin {
244
+ background-color: var(--color-danger);
245
+ color: white;
246
+ }
247
+
248
+ .org-chart-permission-select.org-chart-permission-write {
249
+ background-color: var(--color-success);
250
+ color: white;
251
+ }
252
+
253
+ .org-chart-permission-select.org-chart-permission-feedback {
254
+ background-color: var(--color-warning);
255
+ color: var(--text-primary);
256
+ }
257
+
258
+ .org-chart-permission-select.org-chart-permission-read {
259
+ background-color: var(--text-muted);
260
+ color: white;
261
+ }
262
+
263
+ .org-chart-permission-select.org-chart-permission-no_access {
264
+ background-color: var(--surface-input);
265
+ color: var(--text-muted);
266
+ }
267
+
268
+ .org-chart-no-members {
269
+ color: var(--text-muted);
270
+ font-size: var(--text-0);
271
+ padding: var(--space-1) var(--space-5);
272
+ margin: 0;
273
+ }
274
+
275
+ /* Horizontal scroll instead of wrapping */
276
+ .org-chart-members {
277
+ overflow-x: auto;
278
+ -webkit-overflow-scrolling: touch;
279
+ }
280
+
281
+ .org-chart-member-row {
282
+ min-width: max-content;
283
+ }
284
+
285
+ /* Pending invitation styles */
286
+ .org-chart-pending-badge {
287
+ font-size: var(--text-00);
288
+ color: var(--color-warning);
289
+ font-weight: var(--weight-5);
290
+ }
291
+
292
+ .org-chart-invitation-row {
293
+ opacity: 0.85;
294
+ }
295
+
296
+ /* Unassigned users section */
297
+ .org-chart-unassigned {
298
+ margin-top: var(--space-4);
299
+ border-top: 1px solid var(--color-border);
300
+ padding-top: var(--space-3);
301
+ }
302
+
303
+ .org-chart-unassigned-title {
304
+ font-size: var(--text-1);
305
+ font-weight: var(--weight-5);
306
+ color: var(--text-muted);
307
+ margin-bottom: var(--space-2);
308
+ }
309
+
310
+ /* View switcher */
311
+ .contacts-view-switcher {
312
+ display: flex;
313
+ gap: var(--space-2);
314
+ margin-bottom: var(--space-3);
315
+ }
316
+
317
+ .contacts-description {
318
+ margin: 0 0 var(--space-2) 0;
319
+ }
@@ -20,6 +20,34 @@
20
20
  overflow-y: auto;
21
21
  }
22
22
 
23
+ /* Ensure autocomplete popup inside share modal works without mention_menu.css */
24
+ #share-creative-modal .common-popup {
25
+ position: absolute;
26
+ z-index: var(--layer-modal, 100);
27
+ background: var(--surface-section, #fff);
28
+ border: 1px solid var(--border-color, #ddd);
29
+ box-shadow: var(--shadow-2, 0 2px 8px rgba(0,0,0,.15));
30
+ border-radius: 6px;
31
+ padding: 0.35em;
32
+ max-width: min(420px, 90vw);
33
+ }
34
+ #share-creative-modal .common-popup ul,
35
+ #share-creative-modal .common-popup-list {
36
+ list-style: none;
37
+ margin: 0;
38
+ padding: 0;
39
+ max-height: 260px;
40
+ overflow-y: auto;
41
+ }
42
+ #share-creative-modal .common-popup-list li {
43
+ list-style: none;
44
+ padding: 0.35em 0.5em;
45
+ cursor: pointer;
46
+ }
47
+ #share-creative-modal .common-popup-list li:hover {
48
+ background: var(--border-drag-over, #f0f0f0);
49
+ }
50
+
23
51
  .share-grid {
24
52
  list-style: none;
25
53
  padding: 0;
@@ -37,8 +65,10 @@
37
65
  margin-right: 0.4em;
38
66
  }
39
67
 
40
- #link-creative-modal {
68
+ #link-creative-modal,
69
+ #topic-search-modal {
41
70
  min-width: 320px;
71
+ z-index: calc(var(--layer-modal) + 10);
42
72
  }
43
73
 
44
74
  .common-popup {
@@ -113,6 +143,43 @@
113
143
  color: var(--danger);
114
144
  }
115
145
 
146
+ .share-modal-message {
147
+ padding: var(--space-2) var(--space-3);
148
+ border-radius: var(--radius-2);
149
+ margin-bottom: var(--space-3);
150
+ font-size: var(--text-0);
151
+ animation: share-modal-message-fade 4s ease-in-out;
152
+ }
153
+ .share-modal-message-success {
154
+ background: var(--success-bg, #d4edda);
155
+ color: var(--success-text, #155724);
156
+ border: 1px solid var(--success-border, #c3e6cb);
157
+ }
158
+ .share-modal-message-error {
159
+ background: var(--danger-bg, #f8d7da);
160
+ color: var(--danger-text, #721c24);
161
+ border: 1px solid var(--danger-border, #f5c6cb);
162
+ }
163
+ @keyframes share-modal-message-fade {
164
+ 0%, 80% { opacity: 1; }
165
+ 100% { opacity: 0; }
166
+ }
167
+ .share-modal-pending {
168
+ opacity: 0.6;
169
+ }
170
+ .share-modal-spinner {
171
+ display: inline-block;
172
+ width: 12px;
173
+ height: 12px;
174
+ border: 2px solid var(--text-muted, #999);
175
+ border-top-color: transparent;
176
+ border-radius: 50%;
177
+ animation: share-spinner 0.6s linear infinite;
178
+ }
179
+ @keyframes share-spinner {
180
+ to { transform: rotate(360deg); }
181
+ }
182
+
116
183
  #github-integration-modal .github-modal-status {
117
184
  color: var(--text-muted);
118
185
  margin-bottom: 1em;
@@ -2,8 +2,21 @@ module Collavre
2
2
  class ApplicationController < ::ApplicationController
3
3
  protect_from_forgery with: :exception
4
4
 
5
+ # Include a fresh CSRF token in every response header so that
6
+ # JavaScript callers can update the stale <meta> tag after the
7
+ # browser has been in the background (OS window switch, tab
8
+ # freeze, etc.). Without this, the meta-tag token drifts out
9
+ # of sync with the session cookie and POSTs fail with 422.
10
+ after_action :set_csrf_token_header
11
+
5
12
  private
6
13
 
14
+ def set_csrf_token_header
15
+ return unless protect_against_forgery?
16
+
17
+ response.headers["X-CSRF-Token"] = form_authenticity_token
18
+ end
19
+
7
20
  # Helper to get the engine's routes
8
21
  def collavre_engine
9
22
  Collavre::Engine.routes.url_helpers
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Collavre
4
+ module Comments
5
+ class VersionsController < ApplicationController
6
+ before_action :set_creative
7
+ before_action :set_comment
8
+
9
+ def index
10
+ versions = @comment.comment_versions.order(:version_number).map do |v|
11
+ {
12
+ id: v.id,
13
+ version_number: v.version_number,
14
+ content: v.content,
15
+ created_at: v.created_at.iso8601
16
+ }
17
+ end
18
+
19
+ render json: {
20
+ versions: versions,
21
+ selected_version_id: @comment.selected_version_id,
22
+ total: versions.size
23
+ }
24
+ end
25
+
26
+ def select
27
+ unless @comment.user == Current.user || @creative.has_permission?(Current.user, :admin)
28
+ render json: { error: I18n.t("collavre.comments.not_owner") }, status: :forbidden and return
29
+ end
30
+
31
+ version = @comment.comment_versions.find(params[:id])
32
+ @comment.update!(selected_version_id: version.id, content: version.content)
33
+
34
+ render json: { selected_version_id: version.id, content: version.content }
35
+ end
36
+
37
+ def destroy
38
+ unless @comment.user == Current.user || @creative.has_permission?(Current.user, :admin)
39
+ render json: { error: I18n.t("collavre.comments.not_owner") }, status: :forbidden and return
40
+ end
41
+
42
+ version = @comment.comment_versions.find(params[:id])
43
+ was_selected = @comment.selected_version_id == version.id
44
+ version.destroy!
45
+
46
+ if was_selected
47
+ # Roll back to the latest remaining version
48
+ latest = @comment.comment_versions.order(:version_number).last
49
+ if latest
50
+ @comment.update!(selected_version_id: latest.id, content: latest.content)
51
+ else
52
+ @comment.update!(selected_version_id: nil)
53
+ end
54
+ end
55
+
56
+ remaining = @comment.comment_versions.count
57
+ render json: {
58
+ selected_version_id: @comment.selected_version_id,
59
+ content: @comment.content,
60
+ total: remaining
61
+ }
62
+ end
63
+
64
+ private
65
+
66
+ def set_creative
67
+ @creative = Creative.find(params[:creative_id]).effective_origin
68
+ end
69
+
70
+ def set_comment
71
+ @comment = @creative.comments
72
+ .where(
73
+ "comments.private = ? OR comments.user_id = ? OR comments.approver_id = ?",
74
+ false,
75
+ Current.user.id,
76
+ Current.user.id
77
+ )
78
+ .find(params[:comment_id])
79
+ end
80
+ end
81
+ end
82
+ end