collavre 0.12.1 → 0.12.3
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/actiontext.css +40 -5
- data/app/assets/stylesheets/collavre/popup.css +19 -4
- data/app/javascript/components/InlineLexicalEditor.jsx +66 -15
- data/app/javascript/lib/turbo_stream_actions.js +36 -4
- data/app/views/collavre/creatives/index.html.erb +3 -1
- data/app/views/collavre/shared/navigation/_panels.html.erb +5 -0
- data/lib/collavre/engine.rb +0 -1
- data/lib/collavre/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 61eb7c1c09136cc7e5e15121a76f89f27da2232fe9202a3d909488fe0df5045e
|
|
4
|
+
data.tar.gz: b2fa13c57e9394a2d9736361c2bb13f62dd5122fde785a8c885ab7ae88e84dd2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3a0a984f9b5074aefd2f95819c28df6421445bb26a39ae0cf69a395df1b72334037a0917cc3790b164beb29d95998dcf51fb3958f8cd8b3b5832a646a3ba9dc6
|
|
7
|
+
data.tar.gz: 4a1552462be73e3aeb0deba68260da717a101368130bc984a82ebff2bf78e6ec46dc363332632f976cb82535d083d279bcd33a5b208116e90c0ecb0358ae1362
|
|
@@ -56,14 +56,48 @@
|
|
|
56
56
|
top: calc(100% + 0.35rem);
|
|
57
57
|
left: 0;
|
|
58
58
|
z-index: 10;
|
|
59
|
-
display:
|
|
60
|
-
|
|
61
|
-
gap:
|
|
62
|
-
padding:
|
|
63
|
-
background: var(--surface-
|
|
59
|
+
display: flex;
|
|
60
|
+
flex-direction: column;
|
|
61
|
+
gap: var(--space-2);
|
|
62
|
+
padding: var(--space-2);
|
|
63
|
+
background: var(--surface-section);
|
|
64
64
|
border: 1px solid var(--border-color);
|
|
65
65
|
border-radius: var(--radius-2);
|
|
66
66
|
box-shadow: var(--shadow-3);
|
|
67
|
+
min-width: 180px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.lexical-toolbar-color__tokens {
|
|
71
|
+
display: grid;
|
|
72
|
+
grid-template-columns: repeat(4, 1fr);
|
|
73
|
+
gap: var(--space-1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.lexical-toolbar-color__token-btn {
|
|
77
|
+
width: 28px;
|
|
78
|
+
height: 28px;
|
|
79
|
+
border: 1px solid var(--border-color);
|
|
80
|
+
border-radius: var(--radius-2);
|
|
81
|
+
cursor: pointer;
|
|
82
|
+
padding: 0;
|
|
83
|
+
transition: transform 0.1s var(--ease-out-2);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.lexical-toolbar-color__token-btn:hover {
|
|
87
|
+
transform: scale(1.15);
|
|
88
|
+
border-color: var(--color-active);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.lexical-toolbar-color__token-btn.active {
|
|
92
|
+
box-shadow: 0 0 0 2px var(--color-active);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.lexical-toolbar-color__custom-row {
|
|
96
|
+
display: flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
gap: var(--space-1);
|
|
99
|
+
border-top: 1px solid var(--border-color);
|
|
100
|
+
padding-top: var(--space-2);
|
|
67
101
|
}
|
|
68
102
|
|
|
69
103
|
.lexical-toolbar-color__popover input[type="color"] {
|
|
@@ -74,6 +108,7 @@
|
|
|
74
108
|
height: var(--space-7);
|
|
75
109
|
padding: 0;
|
|
76
110
|
background: transparent;
|
|
111
|
+
flex-shrink: 0;
|
|
77
112
|
}
|
|
78
113
|
|
|
79
114
|
.lexical-toolbar-btn--small {
|
|
@@ -298,6 +298,7 @@
|
|
|
298
298
|
left: 0;
|
|
299
299
|
transform: translateX(0);
|
|
300
300
|
box-sizing: border-box;
|
|
301
|
+
overflow: hidden;
|
|
301
302
|
}
|
|
302
303
|
|
|
303
304
|
.popup-menu-right {
|
|
@@ -305,9 +306,22 @@
|
|
|
305
306
|
right: 0;
|
|
306
307
|
}
|
|
307
308
|
|
|
308
|
-
|
|
309
|
+
/* Wrapper div: layout only, no interaction styles */
|
|
310
|
+
div.popup-menu-item {
|
|
311
|
+
display: flex;
|
|
312
|
+
width: 100%;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/* Ensure forms from button_to fill the full width */
|
|
316
|
+
.popup-menu .popup-menu-item form {
|
|
317
|
+
width: 100%;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/* Interactive elements: button/a inside wrapper OR directly classed as popup-menu-item */
|
|
309
321
|
.popup-menu-item button,
|
|
310
|
-
.popup-menu-item a
|
|
322
|
+
.popup-menu-item a,
|
|
323
|
+
button.popup-menu-item,
|
|
324
|
+
a.popup-menu-item {
|
|
311
325
|
display: flex;
|
|
312
326
|
align-items: center;
|
|
313
327
|
gap: var(--space-2);
|
|
@@ -324,9 +338,10 @@
|
|
|
324
338
|
white-space: nowrap;
|
|
325
339
|
}
|
|
326
340
|
|
|
327
|
-
.popup-menu .popup-menu-item:hover,
|
|
328
341
|
.popup-menu-item button:hover,
|
|
329
|
-
.popup-menu-item a:hover
|
|
342
|
+
.popup-menu-item a:hover,
|
|
343
|
+
button.popup-menu-item:hover,
|
|
344
|
+
a.popup-menu-item:hover {
|
|
330
345
|
background: var(--surface-hover);
|
|
331
346
|
}
|
|
332
347
|
|
|
@@ -273,10 +273,40 @@ function CodeHighlightingPlugin() {
|
|
|
273
273
|
return null
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
-
|
|
276
|
+
const EDITOR_TEXT_TOKENS = [
|
|
277
|
+
{ token: "var(--text-primary)", label: "Primary" },
|
|
278
|
+
{ token: "var(--text-muted)", label: "Muted" },
|
|
279
|
+
{ token: "var(--color-danger)", label: "Danger" },
|
|
280
|
+
{ token: "var(--color-warning)", label: "Warning" },
|
|
281
|
+
{ token: "var(--color-brand)", label: "Brand" },
|
|
282
|
+
{ token: "var(--color-link)", label: "Link" },
|
|
283
|
+
{ token: "var(--color-accent-text)", label: "Accent" },
|
|
284
|
+
{ token: "var(--color-code-text)", label: "Code" }
|
|
285
|
+
]
|
|
286
|
+
|
|
287
|
+
const EDITOR_BG_TOKENS = [
|
|
288
|
+
{ token: "var(--surface-bg)", label: "Background" },
|
|
289
|
+
{ token: "var(--color-highlight)", label: "Highlight" },
|
|
290
|
+
{ token: "var(--color-brand)", label: "Brand" },
|
|
291
|
+
{ token: "var(--color-accent-border)", label: "Accent" },
|
|
292
|
+
{ token: "var(--color-danger)", label: "Danger" },
|
|
293
|
+
{ token: "var(--color-warning)", label: "Warning" },
|
|
294
|
+
{ token: "var(--color-code-bg)", label: "Code" },
|
|
295
|
+
{ token: "var(--surface-hover)", label: "Hover" }
|
|
296
|
+
]
|
|
297
|
+
|
|
298
|
+
function resolveColorForInput(color) {
|
|
299
|
+
if (!color || !color.startsWith("var(")) return color
|
|
300
|
+
const match = color.match(/^var\(([^)]+)\)$/)
|
|
301
|
+
if (!match) return color
|
|
302
|
+
return getComputedStyle(document.documentElement).getPropertyValue(match[1]).trim() || color
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function ToolbarColorPicker({ icon, title, color, onChange, onClear, colorType }) {
|
|
277
306
|
const [open, setOpen] = useState(false)
|
|
278
307
|
const triggerRef = useRef(null)
|
|
279
308
|
const popoverRef = useRef(null)
|
|
309
|
+
const tokens = colorType === "background" ? EDITOR_BG_TOKENS : EDITOR_TEXT_TOKENS
|
|
280
310
|
|
|
281
311
|
useEffect(() => {
|
|
282
312
|
if (!open) return
|
|
@@ -294,6 +324,8 @@ function ToolbarColorPicker({ icon, title, color, onChange, onClear }) {
|
|
|
294
324
|
return () => document.removeEventListener("mousedown", handleClick)
|
|
295
325
|
}, [open])
|
|
296
326
|
|
|
327
|
+
const resolvedColor = resolveColorForInput(color)
|
|
328
|
+
|
|
297
329
|
return (
|
|
298
330
|
<div className="lexical-toolbar-color" title={title}>
|
|
299
331
|
<button
|
|
@@ -306,20 +338,37 @@ function ToolbarColorPicker({ icon, title, color, onChange, onClear }) {
|
|
|
306
338
|
</button>
|
|
307
339
|
{open ? (
|
|
308
340
|
<div className="lexical-toolbar-color__popover" ref={popoverRef}>
|
|
309
|
-
<
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
341
|
+
<div className="lexical-toolbar-color__tokens">
|
|
342
|
+
{tokens.map(({ token, label }) => (
|
|
343
|
+
<button
|
|
344
|
+
key={token}
|
|
345
|
+
type="button"
|
|
346
|
+
className={`lexical-toolbar-color__token-btn${color === token ? " active" : ""}`}
|
|
347
|
+
style={{ backgroundColor: token }}
|
|
348
|
+
title={label}
|
|
349
|
+
onClick={() => {
|
|
350
|
+
onChange(token)
|
|
351
|
+
setOpen(false)
|
|
352
|
+
}}
|
|
353
|
+
/>
|
|
354
|
+
))}
|
|
355
|
+
</div>
|
|
356
|
+
<div className="lexical-toolbar-color__custom-row">
|
|
357
|
+
<input
|
|
358
|
+
type="color"
|
|
359
|
+
value={resolvedColor.startsWith("#") ? resolvedColor : "#000000"}
|
|
360
|
+
onChange={(event) => onChange(event.target.value)}
|
|
361
|
+
/>
|
|
362
|
+
<button
|
|
363
|
+
type="button"
|
|
364
|
+
className="lexical-toolbar-btn lexical-toolbar-btn--small"
|
|
365
|
+
onClick={() => {
|
|
366
|
+
onClear()
|
|
367
|
+
setOpen(false)
|
|
368
|
+
}}>
|
|
369
|
+
✕
|
|
370
|
+
</button>
|
|
371
|
+
</div>
|
|
323
372
|
</div>
|
|
324
373
|
) : null}
|
|
325
374
|
</div>
|
|
@@ -696,6 +745,7 @@ function Toolbar() {
|
|
|
696
745
|
icon="🎨"
|
|
697
746
|
title="Text color"
|
|
698
747
|
color={fontColor}
|
|
748
|
+
colorType="text"
|
|
699
749
|
onChange={(value) => {
|
|
700
750
|
setFontColor(value)
|
|
701
751
|
applyTextStyle({ color: value })
|
|
@@ -709,6 +759,7 @@ function Toolbar() {
|
|
|
709
759
|
icon="🖌️"
|
|
710
760
|
title="Background color"
|
|
711
761
|
color={bgColor}
|
|
762
|
+
colorType="background"
|
|
712
763
|
onChange={(value) => {
|
|
713
764
|
setBgColor(value)
|
|
714
765
|
applyTextStyle({ "background-color": value })
|
|
@@ -75,8 +75,14 @@ function handleCreated(creative) {
|
|
|
75
75
|
targetContainer.style.display = ''
|
|
76
76
|
if (targetContainer.dataset) targetContainer.dataset.expanded = 'true'
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
// Adjust broadcast level (absolute) to relative level for the current view
|
|
79
|
+
const relativeLevel = computeRelativeLevel(creative)
|
|
80
|
+
const adjustedCreative = relativeLevel != null
|
|
81
|
+
? { ...creative, level: relativeLevel }
|
|
82
|
+
: creative
|
|
83
|
+
|
|
84
|
+
const newRow = createRow(adjustedCreative)
|
|
85
|
+
insertAtCorrectPosition(newRow, adjustedCreative, targetContainer)
|
|
80
86
|
}
|
|
81
87
|
// If no targetContainer found, the creative is relevant but we can't determine
|
|
82
88
|
// the exact insertion point — it will appear on next page load.
|
|
@@ -230,6 +236,25 @@ function findRowsForCreative(creativeId, originId) {
|
|
|
230
236
|
return rows
|
|
231
237
|
}
|
|
232
238
|
|
|
239
|
+
// Compute the correct relative level for a creative based on its parent's DOM level.
|
|
240
|
+
// The broadcast sends absolute level (ancestors.size + 1), but the tree view uses
|
|
241
|
+
// relative levels starting at 1. Without this adjustment, broadcasts after drag & drop
|
|
242
|
+
// on sub-pages would overwrite the relative level with the absolute level, causing
|
|
243
|
+
// creatives to appear 1-3 levels deeper than expected.
|
|
244
|
+
function computeRelativeLevel(creative) {
|
|
245
|
+
const parentId = creative.parent_id
|
|
246
|
+
if (!parentId) return 1 // root-level creative
|
|
247
|
+
|
|
248
|
+
const parentRow = document.querySelector(`creative-tree-row[creative-id="${parentId}"]`)
|
|
249
|
+
if (!parentRow) return null // parent not in DOM — cannot compute
|
|
250
|
+
|
|
251
|
+
// Title row's children start at level 1 (same as title), not level + 1
|
|
252
|
+
if (parentRow.hasAttribute('is-title')) return 1
|
|
253
|
+
|
|
254
|
+
const parentLevel = Number(parentRow.getAttribute('level') || parentRow.level || 1)
|
|
255
|
+
return parentLevel + 1
|
|
256
|
+
}
|
|
257
|
+
|
|
233
258
|
function handleUpdated(creative) {
|
|
234
259
|
// Notify popup controllers about creative data changes (e.g. trigger state)
|
|
235
260
|
// Must fire before the early return — popup may be open for a creative
|
|
@@ -241,6 +266,13 @@ function handleUpdated(creative) {
|
|
|
241
266
|
const rows = findRowsForCreative(creative.id, creative.origin_id)
|
|
242
267
|
if (rows.length === 0) return
|
|
243
268
|
|
|
269
|
+
// Adjust broadcast level (absolute) to relative level (based on parent's DOM level).
|
|
270
|
+
// This prevents the level from jumping when viewing sub-pages.
|
|
271
|
+
const relativeLevel = computeRelativeLevel(creative)
|
|
272
|
+
const adjustedCreative = relativeLevel != null
|
|
273
|
+
? { ...creative, level: relativeLevel }
|
|
274
|
+
: creative
|
|
275
|
+
|
|
244
276
|
// Find currently editing creative ID to skip it
|
|
245
277
|
const editForm = document.querySelector('#inline-edit-form-element')
|
|
246
278
|
const editingId = editForm?.dataset?.creativeId
|
|
@@ -248,11 +280,11 @@ function handleUpdated(creative) {
|
|
|
248
280
|
rows.forEach(row => {
|
|
249
281
|
if (String(creative.id) === String(editingId)) {
|
|
250
282
|
if (creative.inline_editor_payload) {
|
|
251
|
-
row.dataset.pendingSyncData = JSON.stringify(
|
|
283
|
+
row.dataset.pendingSyncData = JSON.stringify(adjustedCreative)
|
|
252
284
|
}
|
|
253
285
|
return
|
|
254
286
|
}
|
|
255
|
-
applyRowProperties(row,
|
|
287
|
+
applyRowProperties(row, adjustedCreative)
|
|
256
288
|
})
|
|
257
289
|
}
|
|
258
290
|
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
<% if authenticated? %>
|
|
3
3
|
<%= turbo_stream_from Current.user, :creative_tree %>
|
|
4
4
|
<% end %>
|
|
5
|
+
<% if @auto_fullscreen %>
|
|
6
|
+
<% content_for :comments_popup_auto_fullscreen, "true" %>
|
|
7
|
+
<% end %>
|
|
5
8
|
|
|
6
9
|
<div data-controller="creatives--import creatives--select-mode creatives--drag-drop creatives--expansion creatives--row-editor share-modal"
|
|
7
10
|
data-creatives--import-parent-id-value="<%= @parent_creative&.id %>"
|
|
@@ -222,7 +225,6 @@
|
|
|
222
225
|
}
|
|
223
226
|
) %>
|
|
224
227
|
|
|
225
|
-
<%= render 'collavre/comments/comments_popup', auto_fullscreen: @auto_fullscreen %>
|
|
226
228
|
<%= render 'inline_edit_form' %>
|
|
227
229
|
|
|
228
230
|
<%= render_extension_slot(:creative_modals) %>
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
<%= render_extension_slot(:navigation_panels) %>
|
|
2
2
|
|
|
3
|
+
<% if Current.user %>
|
|
4
|
+
<%= render 'collavre/comments/comments_popup',
|
|
5
|
+
auto_fullscreen: content_for?(:comments_popup_auto_fullscreen) %>
|
|
6
|
+
<% end %>
|
|
7
|
+
|
|
3
8
|
<div id="creative-guide-popover" style="display:none; position:fixed; top:60px; left:50%; transform:translateX(-50%); background:white; border:1px solid var(--color-border); box-shadow:0 2px 8px rgba(0,0,0,0.12); padding:1.2em; max-width:400px; z-index:1000; border-radius:8px;">
|
|
4
9
|
<button type="button" id="close-creative-guide" class="popup-close-btn">×</button>
|
|
5
10
|
<div style="margin-top:0.5em; color:#444; font-size:1em; line-height:1.5;">
|
data/lib/collavre/engine.rb
CHANGED
data/lib/collavre/version.rb
CHANGED