collavre 0.9.0 → 0.11.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/comments_popup.css +15 -1
- data/app/assets/stylesheets/collavre/creatives.css +241 -46
- data/app/channels/collavre/topics_channel.rb +2 -0
- data/app/controllers/collavre/comments_controller.rb +6 -2
- data/app/controllers/collavre/concerns/tree_manageable.rb +2 -2
- data/app/controllers/collavre/creatives_controller.rb +22 -2
- data/app/controllers/collavre/topics_controller.rb +58 -14
- data/app/controllers/collavre/user_creative_preferences_controller.rb +60 -0
- data/app/helpers/collavre/creatives_helper.rb +38 -3
- data/app/javascript/components/InlineLexicalEditor.jsx +31 -0
- data/app/javascript/components/creative_tree_row.js +73 -0
- data/app/javascript/controllers/comments/__tests__/popup_controller.test.js +20 -0
- data/app/javascript/controllers/comments/contexts_controller.js +7 -1
- data/app/javascript/controllers/comments/form_controller.js +78 -5
- data/app/javascript/controllers/comments/list_controller.js +12 -3
- data/app/javascript/controllers/comments/popup_controller.js +26 -0
- data/app/javascript/controllers/comments/presence_controller.js +4 -0
- data/app/javascript/controllers/comments/topics_controller.js +86 -7
- data/app/javascript/controllers/creatives/import_controller.js +9 -3
- data/app/javascript/controllers/creatives/tree_controller.js +8 -3
- data/app/javascript/controllers/popup_menu_controller.js +10 -0
- data/app/javascript/controllers/topic_search_controller.js +80 -10
- data/app/javascript/lib/api/topics.js +87 -0
- data/app/javascript/lib/gnb_popup_manager.js +30 -0
- data/app/javascript/modules/creative_guide.js +14 -0
- data/app/javascript/modules/creative_row_editor.js +16 -5
- data/app/javascript/modules/inbox_panel.js +6 -0
- data/app/javascript/modules/lexical_inline_editor.jsx +2 -1
- data/app/javascript/modules/plans_menu.js +10 -0
- data/app/models/collavre/comment/broadcastable.rb +7 -2
- data/app/models/collavre/creative.rb +1 -1
- data/app/models/collavre/topic.rb +2 -0
- data/app/models/collavre/user.rb +1 -1
- data/app/models/collavre/{creative_expanded_state.rb → user_creative_preference.rb} +4 -3
- data/app/services/collavre/creatives/filters/search_filter.rb +17 -4
- data/app/services/collavre/creatives/tree_builder.rb +3 -3
- data/app/views/collavre/comments/_comments_popup.html.erb +1 -0
- data/app/views/collavre/creatives/_add_button.html.erb +2 -2
- data/app/views/collavre/creatives/index.html.erb +79 -51
- data/config/locales/comments.en.yml +2 -0
- data/config/locales/comments.ko.yml +2 -0
- data/config/locales/creatives.en.yml +4 -0
- data/config/locales/creatives.ko.yml +4 -0
- data/config/locales/user_creative_preferences.en.yml +5 -0
- data/config/locales/user_creative_preferences.ko.yml +5 -0
- data/config/routes.rb +3 -1
- data/db/migrate/20260320061144_rename_creative_expanded_states_to_user_creative_preferences.rb +6 -0
- data/lib/collavre/user_extensions.rb +1 -1
- data/lib/collavre/version.rb +1 -1
- metadata +8 -3
- data/app/controllers/collavre/creative_expanded_states_controller.rb +0 -27
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f1357a437e24482ad596536b50cdae4f58dd247e6e32dbef0be05b99dfdf2103
|
|
4
|
+
data.tar.gz: cc8f618b3b06dcf43f484c87e05a8b92d03a59ff6d63d1356dbc13647316b8a5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 26ec481a50ca488ed2155f8979619e868825282d96057e5875b7de1d6f265f29cac8511197f7fc7797c4f087e27c36981135012feb2c0f0a752ae78a2fc0dbab
|
|
7
|
+
data.tar.gz: 8caef1fbc81332be2aaac3bdbefc10563851f20b929769338a9416d1f48a329f4c1b4be8c79ccef723298351ccc7fff32ca3de65d3e3fed2e71e217cb341a233
|
|
@@ -159,10 +159,15 @@ body.chat-fullscreen {
|
|
|
159
159
|
overflow-y: auto;
|
|
160
160
|
min-height: calc(var(--text-1) * 1.5 * 2); /* 2 lines minimum */
|
|
161
161
|
max-height: calc(var(--text-1) * 1.5 * 10); /* 10 lines maximum */
|
|
162
|
-
transition: height 0.1s ease-out;
|
|
162
|
+
transition: height 0.1s ease-out, border-color 0.15s ease-out, box-shadow 0.15s ease-out;
|
|
163
163
|
/* Prevent iOS zoom on focus */
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
#new-comment-form.creative-drop-hover textarea {
|
|
167
|
+
border-color: var(--link);
|
|
168
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--link) 25%, transparent);
|
|
169
|
+
}
|
|
170
|
+
|
|
166
171
|
#new-comment-form button[type="submit"] {
|
|
167
172
|
float: right;
|
|
168
173
|
}
|
|
@@ -1539,3 +1544,12 @@ body.chat-fullscreen {
|
|
|
1539
1544
|
0%, 70% { opacity: 1; }
|
|
1540
1545
|
100% { opacity: 0; }
|
|
1541
1546
|
}
|
|
1547
|
+
|
|
1548
|
+
.topic-create-option {
|
|
1549
|
+
color: var(--link);
|
|
1550
|
+
font-style: italic;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
li:hover .topic-create-option {
|
|
1554
|
+
text-decoration: underline;
|
|
1555
|
+
}
|
|
@@ -2,11 +2,19 @@
|
|
|
2
2
|
--creative-row-text-offset: 1.8em;
|
|
3
3
|
--creative-loading-emojis: "🚀,🧠,🧩,🤝,🗂️,⚡";
|
|
4
4
|
|
|
5
|
+
/* Icon size used for vertical centering calc */
|
|
6
|
+
--creative-icon-size: 16px;
|
|
7
|
+
|
|
5
8
|
/* Creative tree heading styles — defaults match pre-#799 behavior.
|
|
6
9
|
Themes that don't define these variables render identically to before. */
|
|
7
10
|
--creative-h1-size: 1.3em;
|
|
8
11
|
--creative-h2-size: 1.2em;
|
|
9
12
|
--creative-h3-size: 1.1em;
|
|
13
|
+
/* Line heights for headings (used by calc-based vertical centering) */
|
|
14
|
+
--creative-h1-lh: 1.3;
|
|
15
|
+
--creative-h2-lh: 1.3;
|
|
16
|
+
--creative-h3-lh: 1.3;
|
|
17
|
+
--creative-body-lh: 1.5;
|
|
10
18
|
--creative-h1-weight: bold;
|
|
11
19
|
--creative-h2-weight: bold;
|
|
12
20
|
--creative-h3-weight: bold;
|
|
@@ -44,17 +52,135 @@ html.creative-alignment-ready .page-title {
|
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
.creative-actions-row {
|
|
47
|
-
margin: 0
|
|
48
|
-
padding:
|
|
55
|
+
margin: 0 0 0 var(--creative-row-text-offset);
|
|
56
|
+
padding: var(--space-1) 0;
|
|
57
|
+
display: flex;
|
|
58
|
+
justify-content: space-between;
|
|
59
|
+
align-items: center;
|
|
60
|
+
min-height: 36px;
|
|
61
|
+
border: none;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* === Breadcrumb (left-aligned) === */
|
|
65
|
+
.creative-breadcrumb {
|
|
66
|
+
display: flex;
|
|
67
|
+
align-items: center;
|
|
68
|
+
font-size: var(--text-0);
|
|
69
|
+
color: var(--text-muted);
|
|
70
|
+
overflow-x: auto;
|
|
71
|
+
white-space: nowrap;
|
|
72
|
+
scrollbar-width: none;
|
|
73
|
+
min-width: 0;
|
|
74
|
+
margin-right: var(--space-3);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.creative-breadcrumb::-webkit-scrollbar {
|
|
78
|
+
display: none;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Override application.css .creative-actions-row a for breadcrumb links */
|
|
82
|
+
.creative-actions-row a.creative-breadcrumb-link {
|
|
83
|
+
color: var(--text-muted) !important;
|
|
84
|
+
text-decoration: underline !important;
|
|
85
|
+
text-underline-offset: 2px;
|
|
86
|
+
text-decoration-color: color-mix(in srgb, var(--text-muted) 40%, transparent);
|
|
87
|
+
background: none !important;
|
|
88
|
+
border: none !important;
|
|
89
|
+
padding: 0 !important;
|
|
90
|
+
font-size: var(--text-0) !important;
|
|
91
|
+
filter: none !important;
|
|
92
|
+
line-height: normal;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.creative-actions-row a.creative-breadcrumb-link:hover {
|
|
96
|
+
color: var(--text-primary) !important;
|
|
97
|
+
text-decoration-color: var(--text-primary) !important;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.creative-actions-row a.creative-breadcrumb-current {
|
|
101
|
+
color: var(--text-secondary) !important;
|
|
102
|
+
font-weight: var(--weight-5);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.creative-breadcrumb-sep {
|
|
106
|
+
color: var(--text-muted);
|
|
107
|
+
opacity: 0.4;
|
|
108
|
+
margin: 0 var(--space-1);
|
|
109
|
+
font-size: var(--text-00);
|
|
110
|
+
user-select: none;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* === Header actions (right-aligned) === */
|
|
114
|
+
.creative-header-actions {
|
|
115
|
+
display: flex;
|
|
116
|
+
align-items: center;
|
|
117
|
+
gap: var(--space-1);
|
|
118
|
+
flex-shrink: 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Outline button — GNB-like with border.
|
|
122
|
+
Higher specificity to override application.css .creative-actions-row button */
|
|
123
|
+
.creative-actions-row .creative-header-actions .creative-header-outline-btn {
|
|
124
|
+
background: transparent;
|
|
125
|
+
border: 1px solid var(--border-color);
|
|
126
|
+
color: var(--text-secondary);
|
|
127
|
+
cursor: pointer;
|
|
128
|
+
height: 30px;
|
|
129
|
+
padding: 0 var(--space-2);
|
|
130
|
+
border-radius: var(--radius-2);
|
|
131
|
+
display: inline-flex;
|
|
132
|
+
align-items: center;
|
|
133
|
+
justify-content: center;
|
|
134
|
+
font-size: var(--text-0);
|
|
135
|
+
transition: background 0.15s var(--ease-out-2), color 0.15s var(--ease-out-2), border-color 0.15s var(--ease-out-2);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.creative-actions-row .creative-header-actions .creative-header-outline-btn:hover {
|
|
139
|
+
background: var(--surface-hover);
|
|
140
|
+
color: var(--text-primary);
|
|
141
|
+
border-color: var(--text-muted);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* === Overflow popup menu — GNB style === */
|
|
145
|
+
#creative-overflow-menu {
|
|
146
|
+
min-width: 200px;
|
|
147
|
+
border: 1px solid var(--border-color);
|
|
148
|
+
border-radius: var(--radius-3);
|
|
149
|
+
box-shadow: var(--shadow-2);
|
|
150
|
+
padding: var(--space-1);
|
|
151
|
+
background: var(--surface-section);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
#creative-overflow-menu .popup-menu-item {
|
|
49
155
|
display: flex;
|
|
50
|
-
flex-wrap: wrap;
|
|
51
|
-
gap: 10px;
|
|
52
|
-
height: 48px;
|
|
53
156
|
align-items: center;
|
|
157
|
+
gap: var(--space-2);
|
|
158
|
+
width: 100%;
|
|
159
|
+
text-align: left;
|
|
160
|
+
padding: var(--space-1) var(--space-2);
|
|
161
|
+
border-radius: var(--radius-2);
|
|
162
|
+
border: none;
|
|
163
|
+
background: none;
|
|
164
|
+
color: var(--text-primary);
|
|
165
|
+
font-size: var(--text-1);
|
|
166
|
+
cursor: pointer;
|
|
167
|
+
transition: background 0.15s var(--ease-out-2);
|
|
168
|
+
white-space: nowrap;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
#creative-overflow-menu .popup-menu-item:hover {
|
|
172
|
+
background: var(--surface-hover);
|
|
54
173
|
}
|
|
55
174
|
|
|
56
|
-
|
|
57
|
-
|
|
175
|
+
#creative-overflow-menu .popup-menu-item.active {
|
|
176
|
+
background: color-mix(in srgb, var(--color-active) 15%, transparent);
|
|
177
|
+
color: var(--color-active);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.creative-overflow-divider {
|
|
181
|
+
height: 1px;
|
|
182
|
+
background: var(--border-color);
|
|
183
|
+
margin: var(--space-1) 0;
|
|
58
184
|
}
|
|
59
185
|
|
|
60
186
|
.delete-options {
|
|
@@ -119,7 +245,6 @@ creative-tree-row.show-edit .creative-row {
|
|
|
119
245
|
.creative-tree-li {
|
|
120
246
|
display: flex;
|
|
121
247
|
align-items: flex-start;
|
|
122
|
-
/* Changed from center to flex-start */
|
|
123
248
|
position: relative;
|
|
124
249
|
}
|
|
125
250
|
|
|
@@ -187,8 +312,7 @@ creative-tree-row.show-edit .creative-row {
|
|
|
187
312
|
margin-left: 4px;
|
|
188
313
|
margin-right: 8px;
|
|
189
314
|
flex: 0 0 var(--creative-bullet-size, 5px);
|
|
190
|
-
margin-top:
|
|
191
|
-
/* Align with first line of text (approx) */
|
|
315
|
+
margin-top: calc((1em * var(--creative-body-lh, 1.5) - var(--creative-bullet-size, 5px)) / 2);
|
|
192
316
|
}
|
|
193
317
|
|
|
194
318
|
.creative-row-end {
|
|
@@ -198,8 +322,13 @@ creative-tree-row.show-edit .creative-row {
|
|
|
198
322
|
white-space: nowrap;
|
|
199
323
|
display: flex;
|
|
200
324
|
align-items: flex-start;
|
|
201
|
-
|
|
202
|
-
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/* Reset row-start vertical centering for buttons inside row-end.
|
|
328
|
+
Uses .creative-row to boost specificity above .level-N rules. */
|
|
329
|
+
.creative-row .creative-row-end .creative-action-btn,
|
|
330
|
+
.creative-row .creative-row-end .comments-btn {
|
|
331
|
+
margin-top: 0;
|
|
203
332
|
}
|
|
204
333
|
|
|
205
334
|
.creative-tags {
|
|
@@ -208,27 +337,84 @@ creative-tree-row.show-edit .creative-row {
|
|
|
208
337
|
white-space: nowrap;
|
|
209
338
|
display: flex;
|
|
210
339
|
flex-wrap: wrap;
|
|
211
|
-
padding-top: 4px;
|
|
212
|
-
/* Restore baseline alignment */
|
|
213
340
|
}
|
|
214
341
|
|
|
215
342
|
.creative-progress-complete,
|
|
216
343
|
.creative-progress-incomplete {
|
|
217
344
|
color: var(--color-brand);
|
|
218
|
-
padding-top: 4px;
|
|
219
|
-
/* Restore baseline alignment */
|
|
220
345
|
}
|
|
221
346
|
|
|
222
347
|
.creative-progress-incomplete {
|
|
223
348
|
color: var(--text-muted);
|
|
224
349
|
}
|
|
225
350
|
|
|
226
|
-
/*
|
|
351
|
+
/* Progress toggle (hover-to-complete) */
|
|
352
|
+
.progress-toggle-wrap {
|
|
353
|
+
position: relative;
|
|
354
|
+
cursor: pointer;
|
|
355
|
+
display: inline-flex;
|
|
356
|
+
align-items: center;
|
|
357
|
+
padding-top: 4px;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.progress-toggle-wrap .creative-progress-complete,
|
|
361
|
+
.progress-toggle-wrap .creative-progress-incomplete {
|
|
362
|
+
padding-top: 0;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.progress-toggle-checkbox {
|
|
366
|
+
position: absolute;
|
|
367
|
+
inset: 0;
|
|
368
|
+
width: 100%;
|
|
369
|
+
height: 100%;
|
|
370
|
+
opacity: 0;
|
|
371
|
+
cursor: pointer;
|
|
372
|
+
margin: 0;
|
|
373
|
+
z-index: 1;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/* Desktop: show checkbox on row hover (same pattern as edit-btn, comments-btn) */
|
|
377
|
+
@media (hover: hover) {
|
|
378
|
+
.progress-toggle-checkbox {
|
|
379
|
+
width: 1em;
|
|
380
|
+
height: 1em;
|
|
381
|
+
visibility: hidden;
|
|
382
|
+
opacity: 0;
|
|
383
|
+
position: absolute;
|
|
384
|
+
margin: auto;
|
|
385
|
+
pointer-events: none;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.creative-row:hover .progress-toggle-wrap .creative-progress-complete,
|
|
389
|
+
.creative-row:hover .progress-toggle-wrap .creative-progress-incomplete {
|
|
390
|
+
display: none;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.creative-row:hover .progress-toggle-checkbox {
|
|
394
|
+
visibility: visible;
|
|
395
|
+
opacity: 1;
|
|
396
|
+
position: relative;
|
|
397
|
+
pointer-events: auto;
|
|
398
|
+
accent-color: var(--color-brand);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/* Touch devices: always show both text and clickable area */
|
|
403
|
+
@media (hover: none) {
|
|
404
|
+
.progress-toggle-checkbox {
|
|
405
|
+
opacity: 0;
|
|
406
|
+
pointer-events: auto;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.progress-toggle-saving {
|
|
411
|
+
opacity: 0.5;
|
|
412
|
+
pointer-events: none;
|
|
413
|
+
}
|
|
227
414
|
|
|
228
415
|
.creative-row-start {
|
|
229
416
|
display: flex;
|
|
230
417
|
align-items: flex-start;
|
|
231
|
-
/* Changed from center to flex-start */
|
|
232
418
|
flex-grow: 1;
|
|
233
419
|
}
|
|
234
420
|
|
|
@@ -274,8 +460,8 @@ creative-tree-row.show-edit .creative-row {
|
|
|
274
460
|
line-height: 1;
|
|
275
461
|
display: inline-flex;
|
|
276
462
|
flex-shrink: 0;
|
|
277
|
-
|
|
278
|
-
|
|
463
|
+
/* Default vertical centering for body-text rows (level 4+) */
|
|
464
|
+
margin-top: calc((1em * var(--creative-body-lh, 1.5) - var(--creative-icon-size, 16px)) / 2);
|
|
279
465
|
}
|
|
280
466
|
|
|
281
467
|
/* Ensure inline editor actions use the correct text color */
|
|
@@ -313,13 +499,12 @@ creative-tree-row.show-edit .creative-row {
|
|
|
313
499
|
width: 16px;
|
|
314
500
|
height: 16px;
|
|
315
501
|
margin-right: 4px;
|
|
316
|
-
margin-top:
|
|
317
|
-
/* Align with text */
|
|
502
|
+
margin-top: calc((1em * var(--creative-body-lh, 1.5) - var(--creative-icon-size, 16px)) / 2);
|
|
318
503
|
visibility: hidden;
|
|
319
504
|
/* Hidden by default on desktop */
|
|
320
505
|
/* Total width is 20px (16 + 4) */
|
|
321
506
|
color: var(--text-secondary);
|
|
322
|
-
display: flex;
|
|
507
|
+
display: inline-flex;
|
|
323
508
|
align-items: center;
|
|
324
509
|
justify-content: center;
|
|
325
510
|
}
|
|
@@ -419,17 +604,14 @@ creative-tree-row:not([expanded]) .creative-toggle-btn {
|
|
|
419
604
|
height: 16px;
|
|
420
605
|
cursor: pointer;
|
|
421
606
|
visibility: visible;
|
|
422
|
-
margin-top:
|
|
423
|
-
/* Align with text */
|
|
607
|
+
margin-top: calc((1em * var(--creative-body-lh, 1.5) - var(--creative-icon-size, 16px)) / 2);
|
|
424
608
|
}
|
|
425
609
|
|
|
426
610
|
.creative-row {
|
|
427
611
|
display: flex;
|
|
428
612
|
justify-content: space-between;
|
|
429
613
|
align-items: flex-start;
|
|
430
|
-
/* Changed from center to flex-start */
|
|
431
614
|
padding: 2px 0;
|
|
432
|
-
/* Add some vertical padding */
|
|
433
615
|
}
|
|
434
616
|
|
|
435
617
|
.creative-row.selected {
|
|
@@ -454,8 +636,7 @@ creative-tree-row.chat-active .creative-row {
|
|
|
454
636
|
color: var(--text-muted);
|
|
455
637
|
font-size: 0.9em;
|
|
456
638
|
margin-left: 10px;
|
|
457
|
-
|
|
458
|
-
/* Align with text */
|
|
639
|
+
margin-top: calc((1em * var(--creative-body-lh, 1.5) - 1em) / 2);
|
|
459
640
|
}
|
|
460
641
|
|
|
461
642
|
.page-title {
|
|
@@ -494,6 +675,7 @@ creative-tree-row.chat-active .creative-row {
|
|
|
494
675
|
font-size: var(--creative-h1-size, 1.3em);
|
|
495
676
|
font-weight: var(--creative-h1-weight, bold);
|
|
496
677
|
color: var(--creative-h1-color, inherit);
|
|
678
|
+
line-height: var(--creative-h1-lh, 1.3);
|
|
497
679
|
margin: 0;
|
|
498
680
|
}
|
|
499
681
|
|
|
@@ -501,6 +683,7 @@ creative-tree-row.chat-active .creative-row {
|
|
|
501
683
|
font-size: var(--creative-h2-size, 1.2em);
|
|
502
684
|
font-weight: var(--creative-h2-weight, bold);
|
|
503
685
|
color: var(--creative-h2-color, inherit);
|
|
686
|
+
line-height: var(--creative-h2-lh, 1.3);
|
|
504
687
|
margin: 0;
|
|
505
688
|
}
|
|
506
689
|
|
|
@@ -508,6 +691,7 @@ creative-tree-row.chat-active .creative-row {
|
|
|
508
691
|
font-size: var(--creative-h3-size, 1.1em);
|
|
509
692
|
font-weight: var(--creative-h3-weight, bold);
|
|
510
693
|
color: var(--creative-h3-color, inherit);
|
|
694
|
+
line-height: var(--creative-h3-lh, 1.3);
|
|
511
695
|
margin: 0;
|
|
512
696
|
}
|
|
513
697
|
|
|
@@ -523,23 +707,38 @@ creative-tree-row.chat-active .creative-row {
|
|
|
523
707
|
font-weight: var(--creative-childless-weight, 400);
|
|
524
708
|
}
|
|
525
709
|
|
|
526
|
-
/*
|
|
527
|
-
.
|
|
710
|
+
/* Dynamic vertical centering: calc((font-size × line-height − icon-size) / 2)
|
|
711
|
+
Scoped to .creative-row-start to avoid affecting .creative-row-end buttons. */
|
|
712
|
+
.level-1 .creative-row-start > .creative-action-btn,
|
|
528
713
|
.level-1 .select-creative-checkbox,
|
|
529
714
|
.level-1 .creative-toggle-btn {
|
|
530
|
-
margin-top:
|
|
715
|
+
margin-top: calc((var(--creative-h1-size, 1.3em) * var(--creative-h1-lh, 1.3) - var(--creative-icon-size, 16px)) / 2);
|
|
531
716
|
}
|
|
532
717
|
|
|
533
|
-
.level-2 .creative-action-btn,
|
|
718
|
+
.level-2 .creative-row-start > .creative-action-btn,
|
|
534
719
|
.level-2 .select-creative-checkbox,
|
|
535
720
|
.level-2 .creative-toggle-btn {
|
|
536
|
-
margin-top:
|
|
721
|
+
margin-top: calc((var(--creative-h2-size, 1.2em) * var(--creative-h2-lh, 1.3) - var(--creative-icon-size, 16px)) / 2);
|
|
537
722
|
}
|
|
538
723
|
|
|
539
|
-
.level-3 .creative-action-btn,
|
|
724
|
+
.level-3 .creative-row-start > .creative-action-btn,
|
|
540
725
|
.level-3 .select-creative-checkbox,
|
|
541
726
|
.level-3 .creative-toggle-btn {
|
|
542
|
-
margin-top:
|
|
727
|
+
margin-top: calc((var(--creative-h3-size, 1.1em) * var(--creative-h3-lh, 1.3) - var(--creative-icon-size, 16px)) / 2);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/* Childless items (level 1-3 without children) use body font size.
|
|
731
|
+
Scoped to .creative-row-start to avoid affecting .creative-row-end buttons. */
|
|
732
|
+
.level-1:has(.creative-childless) .creative-row-start > .creative-action-btn,
|
|
733
|
+
.level-2:has(.creative-childless) .creative-row-start > .creative-action-btn,
|
|
734
|
+
.level-3:has(.creative-childless) .creative-row-start > .creative-action-btn,
|
|
735
|
+
.level-1:has(.creative-childless) .creative-toggle-btn,
|
|
736
|
+
.level-2:has(.creative-childless) .creative-toggle-btn,
|
|
737
|
+
.level-3:has(.creative-childless) .creative-toggle-btn,
|
|
738
|
+
.level-1:has(.creative-childless) .select-creative-checkbox,
|
|
739
|
+
.level-2:has(.creative-childless) .select-creative-checkbox,
|
|
740
|
+
.level-3:has(.creative-childless) .select-creative-checkbox {
|
|
741
|
+
margin-top: calc((1em * var(--creative-body-lh, 1.5) - var(--creative-icon-size, 16px)) / 2);
|
|
543
742
|
}
|
|
544
743
|
|
|
545
744
|
|
|
@@ -549,7 +748,6 @@ creative-tree-row.chat-active .creative-row {
|
|
|
549
748
|
display: flex;
|
|
550
749
|
align-items: flex-start;
|
|
551
750
|
margin-left: 0;
|
|
552
|
-
/* Indentation now comes from tree lines; avoid extra left margin here */
|
|
553
751
|
}
|
|
554
752
|
|
|
555
753
|
.comments-btn {
|
|
@@ -557,15 +755,6 @@ creative-tree-row.chat-active .creative-row {
|
|
|
557
755
|
height: 28px;
|
|
558
756
|
position: relative;
|
|
559
757
|
color: var(--text-primary);
|
|
560
|
-
margin-top: -2px;
|
|
561
|
-
/* Move higher as requested */
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
/* Ensure comments button stays high for headings, overriding generic action button margin */
|
|
565
|
-
.level-1 .comments-btn,
|
|
566
|
-
.level-2 .comments-btn,
|
|
567
|
-
.level-3 .comments-btn {
|
|
568
|
-
margin-top: -2px;
|
|
569
758
|
}
|
|
570
759
|
|
|
571
760
|
/* Add spacing for headings via row padding to preserve alignment */
|
|
@@ -717,3 +906,9 @@ creative-tree-row[archived] .creative-tree {
|
|
|
717
906
|
creative-tree-row[archived] .creative-row {
|
|
718
907
|
background-color: var(--surface-input);
|
|
719
908
|
}
|
|
909
|
+
|
|
910
|
+
/* Import dropzone drag-over feedback */
|
|
911
|
+
#import-markdown-dropzone.dragover {
|
|
912
|
+
border-color: var(--color-accent-border);
|
|
913
|
+
background-color: var(--surface-input);
|
|
914
|
+
}
|
|
@@ -7,6 +7,8 @@ class TopicsChannel < ApplicationCable::Channel
|
|
|
7
7
|
return reject unless @creative.has_permission?(current_user, :read)
|
|
8
8
|
|
|
9
9
|
stream_for @creative
|
|
10
|
+
# User-specific stream for preference sync (e.g. last_topic_changed)
|
|
11
|
+
stream_for "user_#{current_user.id}_creative_#{@creative.id}"
|
|
10
12
|
end
|
|
11
13
|
end
|
|
12
14
|
end
|
|
@@ -35,8 +35,12 @@ module Collavre
|
|
|
35
35
|
scope = visible_scope.with_attached_images.includes(:topic, :comment_reactions, :comment_versions)
|
|
36
36
|
|
|
37
37
|
if params[:search].present?
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
words = params[:search].to_s.strip.downcase.split(/\s+/)
|
|
39
|
+
.first(Creatives::Filters::SearchFilter::MAX_SEARCH_WORDS)
|
|
40
|
+
words.each do |word|
|
|
41
|
+
sanitized = "%#{ActiveRecord::Base.sanitize_sql_like(word)}%"
|
|
42
|
+
scope = scope.where("LOWER(comments.content) LIKE ?", sanitized)
|
|
43
|
+
end
|
|
40
44
|
end
|
|
41
45
|
|
|
42
46
|
# Filter by topic
|
|
@@ -70,7 +70,7 @@ module Collavre
|
|
|
70
70
|
|
|
71
71
|
# HTTP caching disabled for children endpoint:
|
|
72
72
|
# Response depends on child updates, permission changes (CreativeSharesCache),
|
|
73
|
-
# and
|
|
73
|
+
# and UserCreativePreference. Tracking all dependencies reliably is expensive
|
|
74
74
|
# (requires descendant_ids query). Stale 304 responses could leak data after
|
|
75
75
|
# permission revocation. Re-enable when a cheap version key mechanism exists.
|
|
76
76
|
# Use private + no-store to prevent any caching (proxy or browser).
|
|
@@ -117,7 +117,7 @@ module Collavre
|
|
|
117
117
|
private
|
|
118
118
|
|
|
119
119
|
def render_children_json(parent, user_id, allowed_ids, progress_map)
|
|
120
|
-
expanded_state_map =
|
|
120
|
+
expanded_state_map = UserCreativePreference
|
|
121
121
|
.where(user_id: user_id, creative_id: parent.id)
|
|
122
122
|
.first&.expanded_status || {}
|
|
123
123
|
children = parent.children_with_permission(Current.user)
|
|
@@ -33,7 +33,7 @@ module Collavre
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
@expanded_state_map = if user_id_for_state
|
|
36
|
-
|
|
36
|
+
UserCreativePreference.where(user_id: user_id_for_state, creative_id: params[:id]).first&.expanded_status || {}
|
|
37
37
|
else
|
|
38
38
|
{}
|
|
39
39
|
end
|
|
@@ -226,7 +226,27 @@ module Collavre
|
|
|
226
226
|
|
|
227
227
|
if success
|
|
228
228
|
format.html { redirect_to @creative }
|
|
229
|
-
format.json
|
|
229
|
+
format.json do
|
|
230
|
+
base.reload
|
|
231
|
+
response_data = {
|
|
232
|
+
id: base.id,
|
|
233
|
+
progress: base.progress,
|
|
234
|
+
progress_html: view_context.render_creative_progress(base),
|
|
235
|
+
has_children: base.children.exists?
|
|
236
|
+
}
|
|
237
|
+
# Build ancestor chain for progress updates (closure_tree: 1 SELECT via hierarchy table)
|
|
238
|
+
ancestor_records = base.ancestors.order(:id)
|
|
239
|
+
if ancestor_records.any?
|
|
240
|
+
response_data[:ancestors] = ancestor_records.map do |anc|
|
|
241
|
+
{
|
|
242
|
+
id: anc.id,
|
|
243
|
+
progress: anc.progress,
|
|
244
|
+
progress_html: view_context.render_creative_progress(anc, has_children: true)
|
|
245
|
+
}
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
render json: response_data
|
|
249
|
+
end
|
|
230
250
|
else
|
|
231
251
|
format.html { render :edit, status: :unprocessable_entity }
|
|
232
252
|
format.json { render json: { errors: @creative.errors.full_messages }, status: :unprocessable_entity }
|
|
@@ -11,11 +11,18 @@ module Collavre
|
|
|
11
11
|
preload_primary_agents(active_topics)
|
|
12
12
|
archived_topics = @creative.topics.archived.order(:created_at)
|
|
13
13
|
|
|
14
|
+
last_topic_id = if Current.user
|
|
15
|
+
UserCreativePreference
|
|
16
|
+
.where(user_id: Current.user.id, creative_id: @creative.id)
|
|
17
|
+
.pick(:last_topic_id)
|
|
18
|
+
end
|
|
19
|
+
|
|
14
20
|
render json: {
|
|
15
21
|
topics: active_topics.map { |t| topic_json(t) },
|
|
16
22
|
archived_topics: archived_topics,
|
|
17
23
|
can_manage: can_manage,
|
|
18
|
-
can_create_topic: can_create_topic
|
|
24
|
+
can_create_topic: can_create_topic,
|
|
25
|
+
last_topic_id: last_topic_id
|
|
19
26
|
}
|
|
20
27
|
end
|
|
21
28
|
|
|
@@ -27,22 +34,43 @@ module Collavre
|
|
|
27
34
|
topic = @creative.topics.build(topic_params)
|
|
28
35
|
topic.user = Current.user
|
|
29
36
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
Topic.transaction do
|
|
38
|
+
if topic.save
|
|
39
|
+
agent = nil
|
|
40
|
+
if params[:agent_id].present?
|
|
41
|
+
agent = User.find_by(id: params[:agent_id])
|
|
42
|
+
topic.set_primary_agent!(agent) if agent&.ai_user?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Move comments to the new topic if comment_ids provided
|
|
46
|
+
comment_ids = Array(params[:comment_ids]).map(&:presence).compact
|
|
47
|
+
if comment_ids.any?
|
|
48
|
+
CommentMoveService.new(creative: @creative, user: Current.user).call(
|
|
49
|
+
comment_ids: comment_ids,
|
|
50
|
+
target_topic_id: topic.id
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
broadcast_data = agent ? topic_json_with_agent(topic, agent) : topic.slice(:id, :name)
|
|
55
|
+
TopicsChannel.broadcast_to(
|
|
56
|
+
@creative,
|
|
57
|
+
{ action: "created", topic: broadcast_data, user_id: Current.user.id }
|
|
58
|
+
)
|
|
59
|
+
render json: topic, status: :created
|
|
60
|
+
else
|
|
61
|
+
render json: { errors: topic.errors.full_messages }, status: :unprocessable_entity
|
|
35
62
|
end
|
|
63
|
+
end
|
|
64
|
+
rescue CommentMoveService::MoveError => e
|
|
65
|
+
render json: { errors: [ e.message ] }, status: :unprocessable_entity
|
|
66
|
+
end
|
|
36
67
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
{ action: "created", topic: broadcast_data, user_id: Current.user.id }
|
|
41
|
-
)
|
|
42
|
-
render json: topic, status: :created
|
|
43
|
-
else
|
|
44
|
-
render json: { errors: topic.errors.full_messages }, status: :unprocessable_entity
|
|
68
|
+
def next_name
|
|
69
|
+
unless @creative.has_permission?(Current.user, :read) || @creative.user == Current.user
|
|
70
|
+
render json: { error: I18n.t("collavre.topics.no_permission") }, status: :forbidden and return
|
|
45
71
|
end
|
|
72
|
+
|
|
73
|
+
render json: { name: generate_next_topic_name }
|
|
46
74
|
end
|
|
47
75
|
|
|
48
76
|
def update
|
|
@@ -70,6 +98,8 @@ module Collavre
|
|
|
70
98
|
|
|
71
99
|
topic = @creative.topics.find(params[:id])
|
|
72
100
|
topic_id = topic.id
|
|
101
|
+
|
|
102
|
+
# last_topic_id is nullified by DB FK (on_delete: :nullify) and model dependent: :nullify
|
|
73
103
|
topic.destroy
|
|
74
104
|
|
|
75
105
|
TopicsChannel.broadcast_to(
|
|
@@ -202,6 +232,20 @@ module Collavre
|
|
|
202
232
|
params.require(:topic).permit(:name)
|
|
203
233
|
end
|
|
204
234
|
|
|
235
|
+
def generate_next_topic_name
|
|
236
|
+
prefix = I18n.t("collavre.topics.default_name_prefix")
|
|
237
|
+
existing_numbers = @creative.topics.active
|
|
238
|
+
.where("name LIKE ?", "#{Topic.sanitize_sql_like(prefix)}%")
|
|
239
|
+
.pluck(:name)
|
|
240
|
+
.filter_map { |n|
|
|
241
|
+
suffix = n.delete_prefix(prefix)
|
|
242
|
+
suffix.match?(/\A\d+\z/) ? suffix.to_i : nil
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
next_number = (existing_numbers.max || 0) + 1
|
|
246
|
+
"#{prefix}#{next_number}"
|
|
247
|
+
end
|
|
248
|
+
|
|
205
249
|
# Batch-load primary agents for all topics to avoid N+1 queries
|
|
206
250
|
def preload_primary_agents(topics)
|
|
207
251
|
topic_ids = topics.map(&:id)
|