collavre 0.5.0 → 0.6.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.
- checksums.yaml +4 -4
- data/app/assets/stylesheets/collavre/comment_versions.css +76 -0
- data/app/assets/stylesheets/collavre/comments_popup.css +190 -36
- data/app/assets/stylesheets/collavre/popup.css +3 -1
- data/app/controllers/collavre/comments/versions_controller.rb +82 -0
- data/app/controllers/collavre/comments_controller.rb +40 -6
- data/app/javascript/controllers/comment_controller.js +33 -70
- data/app/javascript/controllers/comment_version_controller.js +164 -0
- data/app/javascript/controllers/comments/__tests__/form_controller_review.test.js +305 -0
- data/app/javascript/controllers/comments/__tests__/list_controller_selection.test.js +103 -0
- data/app/javascript/controllers/comments/__tests__/review_quotes_store.test.js +113 -0
- data/app/javascript/controllers/comments/form_controller.js +276 -12
- data/app/javascript/controllers/comments/list_controller.js +146 -62
- data/app/javascript/controllers/comments/review_quotes_store.js +189 -0
- data/app/javascript/controllers/index.js +7 -1
- data/app/javascript/controllers/topic_search_controller.js +103 -0
- data/app/jobs/collavre/ai_agent_job.rb +46 -4
- data/app/jobs/collavre/compress_job.rb +92 -0
- data/app/models/collavre/comment.rb +35 -1
- data/app/models/collavre/comment_version.rb +15 -0
- data/app/models/collavre/task.rb +30 -2
- data/app/models/collavre/user.rb +8 -0
- data/app/services/collavre/ai_agent/review_handler.rb +18 -1
- data/app/services/collavre/ai_client.rb +6 -0
- data/app/services/collavre/command_menu_service.rb +12 -0
- data/app/services/collavre/comments/command_processor.rb +3 -1
- data/app/services/collavre/comments/compress_command.rb +75 -0
- data/app/services/collavre/comments/work_command.rb +175 -0
- data/app/services/collavre/comments/workflow_executor.rb +344 -0
- data/app/services/collavre/creatives/tree_formatter.rb +53 -13
- data/app/services/collavre/gemini_parent_recommender.rb +3 -3
- data/app/services/collavre/orchestration/agent_orchestrator.rb +15 -4
- data/app/services/collavre/orchestration/scheduler.rb +3 -2
- data/app/services/collavre/orchestration/stuck_detector.rb +1 -1
- data/app/services/collavre/system_events/dispatcher.rb +9 -0
- data/app/services/collavre/tools/creative_create_service.rb +1 -8
- data/app/services/collavre/tools/creative_import_service.rb +46 -0
- data/app/services/collavre/tools/creative_retrieval_service.rb +156 -96
- data/app/services/collavre/tools/creative_update_service.rb +1 -8
- data/app/services/collavre/tools/description_normalizable.rb +16 -0
- data/app/views/collavre/comments/_comment.html.erb +25 -8
- data/app/views/collavre/comments/_comments_popup.html.erb +20 -4
- data/config/locales/comments.en.yml +53 -2
- data/config/locales/comments.ko.yml +53 -2
- data/config/routes.rb +6 -0
- data/db/migrate/20260220072200_add_workflow_fields_to_tasks.rb +12 -0
- data/db/migrate/20260223173533_add_review_type_to_comments.rb +5 -0
- data/db/migrate/20260225065200_create_comment_versions.rb +14 -0
- data/db/migrate/20260225074416_add_selected_version_id_to_comments.rb +7 -0
- data/lib/collavre/version.rb +1 -1
- metadata +20 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 88ee0fe394fdf6b52cb501be92fbac6b05e549adf30aa46937d7e9a14a690e7c
|
|
4
|
+
data.tar.gz: e3573f4001310e3227e6084f5544af98412ba341d217cec06731974d57fc154d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a139147e03c807d07ab876d053172acfb736956fb5d6a8e1d519a070b83913caba150389153329461963129394b52af20cbd53b7e0506a68f5c4210dc6625cc2
|
|
7
|
+
data.tar.gz: f9f2ea3a2921c3baee34452e8908cd48e93b59692124b54b672312255eb8a25eb77f8b3dad61f3ce17e6533f9d79e7b11ce7bd928dfcc74ea336dc30329204b0
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
.comment-version-navigator {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
gap: var(--space-2, 0.25rem);
|
|
5
|
+
margin-top: var(--space-2, 0.25rem);
|
|
6
|
+
font-size: var(--text-xs, 0.75rem);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.comment-version-btn {
|
|
10
|
+
background: none;
|
|
11
|
+
border: 1px solid var(--border-color, #ddd);
|
|
12
|
+
border-radius: var(--radius-1, 4px);
|
|
13
|
+
cursor: pointer;
|
|
14
|
+
padding: 0 var(--space-2, 0.25rem);
|
|
15
|
+
font-size: var(--text-xs, 0.75rem);
|
|
16
|
+
line-height: 1.6;
|
|
17
|
+
color: var(--text-color, #333);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.comment-version-btn:hover:not(:disabled) {
|
|
21
|
+
background: var(--surface-hover, #f0f0f0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.comment-version-btn:disabled {
|
|
25
|
+
opacity: 0.3;
|
|
26
|
+
cursor: default;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.comment-version-indicator {
|
|
30
|
+
color: var(--text-muted, #888);
|
|
31
|
+
font-variant-numeric: tabular-nums;
|
|
32
|
+
min-width: 3em;
|
|
33
|
+
text-align: center;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.comment-version-delete-btn {
|
|
37
|
+
background: none;
|
|
38
|
+
border: none;
|
|
39
|
+
cursor: pointer;
|
|
40
|
+
color: var(--text-muted, #888);
|
|
41
|
+
font-size: var(--text-xs, 0.75rem);
|
|
42
|
+
padding: 0 var(--space-1, 0.125rem);
|
|
43
|
+
margin-left: var(--space-1, 0.125rem);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.comment-version-delete-btn:hover {
|
|
47
|
+
color: var(--danger-color, #e53e3e);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.comment-version-selected {
|
|
51
|
+
color: var(--primary-color, #4a90d9);
|
|
52
|
+
font-weight: bold;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.comment-version-select-btn {
|
|
56
|
+
background: none;
|
|
57
|
+
border: 1px solid var(--primary-color, #4a90d9);
|
|
58
|
+
border-radius: var(--radius-1, 4px);
|
|
59
|
+
cursor: pointer;
|
|
60
|
+
color: var(--primary-color, #4a90d9);
|
|
61
|
+
font-size: var(--text-xs, 0.75rem);
|
|
62
|
+
padding: 0 var(--space-2, 0.25rem);
|
|
63
|
+
margin-left: var(--space-1, 0.125rem);
|
|
64
|
+
line-height: 1.6;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.comment-version-select-btn:hover:not(:disabled) {
|
|
68
|
+
background: var(--primary-color, #4a90d9);
|
|
69
|
+
color: white;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.comment-version-select-btn:disabled,
|
|
73
|
+
.comment-version-delete-btn:disabled {
|
|
74
|
+
opacity: 0.3;
|
|
75
|
+
cursor: default;
|
|
76
|
+
}
|
|
@@ -138,8 +138,12 @@ body.chat-fullscreen {
|
|
|
138
138
|
|
|
139
139
|
#new-comment-form textarea {
|
|
140
140
|
width: 96%;
|
|
141
|
-
resize:
|
|
141
|
+
resize: none;
|
|
142
142
|
font-size: var(--text-1);
|
|
143
|
+
overflow-y: auto;
|
|
144
|
+
min-height: calc(var(--text-1) * 1.5 * 2); /* 2 lines minimum */
|
|
145
|
+
max-height: calc(var(--text-1) * 1.5 * 10); /* 10 lines maximum */
|
|
146
|
+
transition: height 0.1s ease-out;
|
|
143
147
|
/* Prevent iOS zoom on focus */
|
|
144
148
|
}
|
|
145
149
|
|
|
@@ -196,10 +200,11 @@ body.chat-fullscreen {
|
|
|
196
200
|
margin-bottom: 0.2em;
|
|
197
201
|
}
|
|
198
202
|
|
|
199
|
-
.comment-quoted-text
|
|
203
|
+
.comment-quoted-text,
|
|
204
|
+
.comment-content blockquote {
|
|
200
205
|
border-left: 3px solid var(--color-border);
|
|
201
|
-
padding: 0.
|
|
202
|
-
margin: 0;
|
|
206
|
+
padding: 0.2em 0.6em;
|
|
207
|
+
margin: 0 0 0.3em 0;
|
|
203
208
|
color: var(--color-muted);
|
|
204
209
|
font-size: 0.9em;
|
|
205
210
|
background: color-mix(in srgb, var(--color-section-bg) 50%, transparent);
|
|
@@ -208,6 +213,10 @@ body.chat-fullscreen {
|
|
|
208
213
|
word-break: break-word;
|
|
209
214
|
}
|
|
210
215
|
|
|
216
|
+
.comment-content blockquote p {
|
|
217
|
+
margin: 0;
|
|
218
|
+
}
|
|
219
|
+
|
|
211
220
|
/* Quote indicator in form */
|
|
212
221
|
.comment-quote-indicator {
|
|
213
222
|
display: flex;
|
|
@@ -245,6 +254,100 @@ body.chat-fullscreen {
|
|
|
245
254
|
color: var(--color-text);
|
|
246
255
|
}
|
|
247
256
|
|
|
257
|
+
/* Review quote chips */
|
|
258
|
+
.review-quotes-container {
|
|
259
|
+
display: flex;
|
|
260
|
+
flex-direction: column;
|
|
261
|
+
gap: 0.25em;
|
|
262
|
+
padding: 0.4em 0.5em;
|
|
263
|
+
max-height: 8em;
|
|
264
|
+
overflow-y: auto;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.review-quote-chip {
|
|
268
|
+
display: flex;
|
|
269
|
+
align-items: center;
|
|
270
|
+
gap: 0.3em;
|
|
271
|
+
padding: 0.25em 0.5em;
|
|
272
|
+
background: color-mix(in srgb, var(--color-section-bg) 50%, transparent);
|
|
273
|
+
border-left: 3px solid var(--color-muted);
|
|
274
|
+
border-radius: 0 var(--radius-2) var(--radius-2) 0;
|
|
275
|
+
font-size: var(--text-0);
|
|
276
|
+
color: var(--color-muted);
|
|
277
|
+
line-height: 1.3;
|
|
278
|
+
transition: border-color 0.15s ease, background 0.15s ease;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.review-quote-chip--active {
|
|
282
|
+
border-left-color: var(--color-active);
|
|
283
|
+
background: color-mix(in srgb, var(--color-active) 8%, transparent);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.review-quote-type-toggle {
|
|
287
|
+
border: none;
|
|
288
|
+
background: none;
|
|
289
|
+
cursor: pointer;
|
|
290
|
+
padding: 0 0.1em;
|
|
291
|
+
font-size: 0.85em;
|
|
292
|
+
line-height: 1;
|
|
293
|
+
flex-shrink: 0;
|
|
294
|
+
opacity: 0.8;
|
|
295
|
+
transition: opacity 0.15s ease;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.review-quote-type-toggle:hover {
|
|
299
|
+
opacity: 1;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.review-quote-chip-text {
|
|
303
|
+
flex: 1;
|
|
304
|
+
min-width: 0;
|
|
305
|
+
overflow: hidden;
|
|
306
|
+
text-overflow: ellipsis;
|
|
307
|
+
white-space: nowrap;
|
|
308
|
+
cursor: pointer;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.review-quote-chip-text:hover {
|
|
312
|
+
color: var(--color-text);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.review-quote-chip-feedback {
|
|
316
|
+
flex-shrink: 1;
|
|
317
|
+
min-width: 0;
|
|
318
|
+
overflow: hidden;
|
|
319
|
+
text-overflow: ellipsis;
|
|
320
|
+
white-space: nowrap;
|
|
321
|
+
font-size: 0.85em;
|
|
322
|
+
color: var(--color-text);
|
|
323
|
+
opacity: 0.7;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.review-quote-chip-remove {
|
|
327
|
+
border: none;
|
|
328
|
+
background: none;
|
|
329
|
+
cursor: pointer;
|
|
330
|
+
color: var(--color-muted);
|
|
331
|
+
font-size: 1em;
|
|
332
|
+
padding: 0 0.15em;
|
|
333
|
+
line-height: 1;
|
|
334
|
+
flex-shrink: 0;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.review-quote-chip-remove:hover {
|
|
338
|
+
color: var(--color-danger);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.review-submit-btn {
|
|
342
|
+
font-size: var(--text-0) !important;
|
|
343
|
+
white-space: nowrap;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.comment-highlight {
|
|
347
|
+
background: color-mix(in srgb, var(--color-active) 15%, transparent) !important;
|
|
348
|
+
transition: background 0.3s ease;
|
|
349
|
+
}
|
|
350
|
+
|
|
248
351
|
/* Review popup (uses common-popup) */
|
|
249
352
|
.comment-review-popup {
|
|
250
353
|
min-width: auto;
|
|
@@ -599,7 +702,6 @@ body.chat-fullscreen {
|
|
|
599
702
|
.delete-comment-btn,
|
|
600
703
|
.edit-comment-btn,
|
|
601
704
|
.review-comment-btn,
|
|
602
|
-
.replace-comment-btn,
|
|
603
705
|
.copy-comment-link-btn {
|
|
604
706
|
border: none;
|
|
605
707
|
background: none;
|
|
@@ -614,11 +716,6 @@ body.chat-fullscreen {
|
|
|
614
716
|
display: none;
|
|
615
717
|
}
|
|
616
718
|
|
|
617
|
-
.replace-comment-btn:disabled {
|
|
618
|
-
opacity: 0.35;
|
|
619
|
-
cursor: default;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
719
|
.comment-copy-notice {
|
|
623
720
|
position: absolute;
|
|
624
721
|
right: 0.2em;
|
|
@@ -1022,35 +1119,81 @@ body.chat-fullscreen {
|
|
|
1022
1119
|
outline: none;
|
|
1023
1120
|
}
|
|
1024
1121
|
|
|
1025
|
-
/* Selection
|
|
1026
|
-
.selection-
|
|
1027
|
-
position:
|
|
1122
|
+
/* Selection action bar */
|
|
1123
|
+
.selection-action-bar {
|
|
1124
|
+
position: sticky;
|
|
1125
|
+
bottom: 0;
|
|
1028
1126
|
background: var(--surface-section);
|
|
1029
|
-
border: 1px solid var(--border-color);
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
white-space: nowrap;
|
|
1034
|
-
z-index: 10000;
|
|
1035
|
-
animation: hint-fade-in 0.2s ease-out;
|
|
1127
|
+
border-top: 1px solid var(--border-color);
|
|
1128
|
+
padding: var(--space-2) var(--space-3);
|
|
1129
|
+
z-index: 100;
|
|
1130
|
+
animation: action-bar-slide-up 0.2s ease-out;
|
|
1036
1131
|
}
|
|
1037
1132
|
|
|
1038
|
-
.selection-
|
|
1133
|
+
.selection-action-bar-main {
|
|
1039
1134
|
display: flex;
|
|
1040
|
-
|
|
1041
|
-
gap: var(--space-
|
|
1042
|
-
|
|
1135
|
+
align-items: center;
|
|
1136
|
+
gap: var(--space-2);
|
|
1137
|
+
flex-wrap: wrap;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
.selection-action-bar-count {
|
|
1141
|
+
font-size: 0.85em;
|
|
1142
|
+
font-weight: bold;
|
|
1143
|
+
color: var(--color-text);
|
|
1144
|
+
margin-right: var(--space-1);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
.selection-action-bar-btn {
|
|
1148
|
+
border: none;
|
|
1149
|
+
background: none;
|
|
1150
|
+
cursor: pointer;
|
|
1151
|
+
font-size: 0.85em;
|
|
1152
|
+
color: var(--color-chat-btn-text);
|
|
1153
|
+
font-weight: bold;
|
|
1154
|
+
padding: var(--space-1) var(--space-2);
|
|
1155
|
+
border-radius: var(--radius-2);
|
|
1156
|
+
white-space: nowrap;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
.selection-action-bar-btn:hover {
|
|
1160
|
+
background: var(--surface-hover, rgba(0, 0, 0, 0.05));
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
.selection-action-delete:hover {
|
|
1164
|
+
color: var(--color-danger);
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
.selection-action-bar-close {
|
|
1168
|
+
border: none;
|
|
1169
|
+
background: none;
|
|
1170
|
+
cursor: pointer;
|
|
1171
|
+
font-size: 1em;
|
|
1043
1172
|
color: var(--text-muted);
|
|
1173
|
+
margin-left: auto;
|
|
1174
|
+
padding: var(--space-1);
|
|
1044
1175
|
}
|
|
1045
1176
|
|
|
1046
|
-
.selection-
|
|
1047
|
-
|
|
1177
|
+
.selection-action-bar-close:hover {
|
|
1178
|
+
color: var(--color-text);
|
|
1048
1179
|
}
|
|
1049
1180
|
|
|
1050
|
-
|
|
1181
|
+
.selection-action-bar-hint {
|
|
1182
|
+
font-size: 0.75em;
|
|
1183
|
+
color: var(--text-muted);
|
|
1184
|
+
margin-top: var(--space-1);
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
@media (pointer: coarse) {
|
|
1188
|
+
.selection-action-bar-hint.no-touch {
|
|
1189
|
+
display: none;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
@keyframes action-bar-slide-up {
|
|
1051
1194
|
from {
|
|
1052
1195
|
opacity: 0;
|
|
1053
|
-
transform: translateY(
|
|
1196
|
+
transform: translateY(8px);
|
|
1054
1197
|
}
|
|
1055
1198
|
to {
|
|
1056
1199
|
opacity: 1;
|
|
@@ -1058,14 +1201,7 @@ body.chat-fullscreen {
|
|
|
1058
1201
|
}
|
|
1059
1202
|
}
|
|
1060
1203
|
|
|
1061
|
-
/*
|
|
1062
|
-
@media (prefers-color-scheme: dark) {
|
|
1063
|
-
.selection-hint-popup {
|
|
1064
|
-
background: var(--surface-section);
|
|
1065
|
-
border-color: var(--border-color);
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1204
|
+
/* Floating search popups — positioned by CommonPopup inside #comments-popup */
|
|
1069
1205
|
/* Override popup-close-btn absolute positioning inside comments popup header */
|
|
1070
1206
|
.comments-popup-actions .popup-close-btn {
|
|
1071
1207
|
position: static;
|
|
@@ -1122,3 +1258,21 @@ body.chat-fullscreen {
|
|
|
1122
1258
|
0%, 100% { opacity: 1; }
|
|
1123
1259
|
50% { opacity: 0; }
|
|
1124
1260
|
}
|
|
1261
|
+
|
|
1262
|
+
.review-hint {
|
|
1263
|
+
background: var(--surface-3, #333);
|
|
1264
|
+
color: var(--text-1, #fff);
|
|
1265
|
+
padding: var(--space-2, 4px) var(--space-3, 8px);
|
|
1266
|
+
border-radius: var(--radius-2, 4px);
|
|
1267
|
+
font-size: var(--text-0, 0.75rem);
|
|
1268
|
+
white-space: nowrap;
|
|
1269
|
+
z-index: var(--layer-important, 999);
|
|
1270
|
+
animation: review-hint-fade 3s ease-out forwards;
|
|
1271
|
+
pointer-events: none;
|
|
1272
|
+
box-shadow: var(--shadow-2, 0 2px 8px rgba(0,0,0,0.15));
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
@keyframes review-hint-fade {
|
|
1276
|
+
0%, 70% { opacity: 1; }
|
|
1277
|
+
100% { opacity: 0; }
|
|
1278
|
+
}
|
|
@@ -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
|
|
@@ -25,7 +25,7 @@ module Collavre
|
|
|
25
25
|
Current.user.id,
|
|
26
26
|
Current.user.id
|
|
27
27
|
)
|
|
28
|
-
scope = visible_scope.with_attached_images.includes(:topic, :comment_reactions)
|
|
28
|
+
scope = visible_scope.with_attached_images.includes(:topic, :comment_reactions, :comment_versions)
|
|
29
29
|
|
|
30
30
|
if params[:search].present?
|
|
31
31
|
search_term = ActiveRecord::Base.sanitize_sql_like(params[:search].to_s.strip.downcase)
|
|
@@ -191,7 +191,7 @@ module Collavre
|
|
|
191
191
|
)
|
|
192
192
|
end
|
|
193
193
|
end
|
|
194
|
-
@comment = Comment.with_attached_images.includes(:comment_reactions).find(@comment.id)
|
|
194
|
+
@comment = Comment.with_attached_images.includes(:comment_reactions, :comment_versions, :selected_version).find(@comment.id)
|
|
195
195
|
render partial: "collavre/comments/comment", locals: { comment: @comment, current_topic_id: current_topic_context }, status: :created
|
|
196
196
|
else
|
|
197
197
|
render json: { errors: @comment.errors.full_messages }, status: :unprocessable_entity
|
|
@@ -206,7 +206,7 @@ module Collavre
|
|
|
206
206
|
end
|
|
207
207
|
|
|
208
208
|
if @comment.update(safe_params)
|
|
209
|
-
@comment = Comment.with_attached_images.includes(:comment_reactions).find(@comment.id)
|
|
209
|
+
@comment = Comment.with_attached_images.includes(:comment_reactions, :comment_versions, :selected_version).find(@comment.id)
|
|
210
210
|
render partial: "collavre/comments/comment", locals: { comment: @comment, current_topic_id: current_topic_context }
|
|
211
211
|
else
|
|
212
212
|
render json: { errors: @comment.errors.full_messages }, status: :unprocessable_entity
|
|
@@ -295,7 +295,7 @@ module Collavre
|
|
|
295
295
|
|
|
296
296
|
begin
|
|
297
297
|
::Comments::ActionExecutor.new(comment: @comment, executor: Current.user).call
|
|
298
|
-
@comment = Comment.with_attached_images.includes(:comment_reactions).find(@comment.id)
|
|
298
|
+
@comment = Comment.with_attached_images.includes(:comment_reactions, :comment_versions, :selected_version).find(@comment.id)
|
|
299
299
|
render partial: "collavre/comments/comment", locals: { comment: @comment, current_topic_id: current_topic_context }
|
|
300
300
|
rescue ::Comments::ActionExecutor::ExecutionError => e
|
|
301
301
|
render json: { error: e.message }, status: :unprocessable_entity
|
|
@@ -365,7 +365,7 @@ module Collavre
|
|
|
365
365
|
elsif executed_error
|
|
366
366
|
render json: { error: I18n.t("collavre.comments.approve_already_executed") }, status: :unprocessable_entity
|
|
367
367
|
elsif update_success
|
|
368
|
-
@comment = Comment.with_attached_images.includes(:comment_reactions).find(@comment.id)
|
|
368
|
+
@comment = Comment.with_attached_images.includes(:comment_reactions, :comment_versions, :selected_version).find(@comment.id)
|
|
369
369
|
render partial: "collavre/comments/comment", locals: { comment: @comment, current_topic_id: current_topic_context }
|
|
370
370
|
else
|
|
371
371
|
error_message = @comment.errors.full_messages.to_sentence.presence || I18n.t("collavre.comments.action_update_error")
|
|
@@ -406,6 +406,40 @@ module Collavre
|
|
|
406
406
|
render json: CommandMenuService.new(user: Current.user).items
|
|
407
407
|
end
|
|
408
408
|
|
|
409
|
+
MAX_BATCH_DELETE = 100
|
|
410
|
+
|
|
411
|
+
def batch_destroy
|
|
412
|
+
comment_ids = Array(params[:comment_ids]).map(&:to_i).uniq.first(MAX_BATCH_DELETE)
|
|
413
|
+
if comment_ids.empty?
|
|
414
|
+
render json: { error: I18n.t("collavre.comments.batch_delete_no_selection") }, status: :unprocessable_entity and return
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
is_admin = @creative.has_permission?(Current.user, :admin)
|
|
418
|
+
is_creative_owner = @creative.user == Current.user
|
|
419
|
+
|
|
420
|
+
visible_scope = @creative.comments.where(
|
|
421
|
+
"comments.private = ? OR comments.user_id = ? OR comments.approver_id = ?",
|
|
422
|
+
false, Current.user.id, Current.user.id
|
|
423
|
+
)
|
|
424
|
+
comments = visible_scope.where(id: comment_ids).to_a
|
|
425
|
+
|
|
426
|
+
if comments.length != comment_ids.length
|
|
427
|
+
render json: { error: I18n.t("collavre.comments.batch_delete_not_found") }, status: :not_found and return
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# Check permissions: user must own all comments, or be admin/creative owner
|
|
431
|
+
unless is_admin || is_creative_owner
|
|
432
|
+
unauthorized = comments.reject { |c| c.user == Current.user }
|
|
433
|
+
if unauthorized.any?
|
|
434
|
+
render json: { error: I18n.t("collavre.comments.not_owner") }, status: :forbidden and return
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
Comment.where(id: comments.map(&:id)).destroy_all
|
|
439
|
+
|
|
440
|
+
head :no_content
|
|
441
|
+
end
|
|
442
|
+
|
|
409
443
|
def move
|
|
410
444
|
result = CommentMoveService.new(creative: @creative, user: Current.user).call(
|
|
411
445
|
comment_ids: params[:comment_ids],
|
|
@@ -438,7 +472,7 @@ module Collavre
|
|
|
438
472
|
end
|
|
439
473
|
|
|
440
474
|
def comment_params
|
|
441
|
-
params.require(:comment).permit(:content, :private, :topic_id, :quoted_comment_id, :quoted_text, images: [])
|
|
475
|
+
params.require(:comment).permit(:content, :private, :topic_id, :quoted_comment_id, :quoted_text, :review_type, images: [])
|
|
442
476
|
end
|
|
443
477
|
|
|
444
478
|
def can_convert_comment?
|