collavre 0.12.1 → 0.12.2
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/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/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: e5cc8288417aa35fba130bae92aeb47383ba3f53dd10fb3e9d1d46d65605fbf2
|
|
4
|
+
data.tar.gz: 7862466adcb4e962f1a0fb5aa62f3958cafe98469df3da543d3df9d12b2c6bc7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fd4d1f51f6dd4044b1d5d7c09f06437dc4dd59962c2632486794a4dcfc2eb8baa01dd8ecd6ece69b54d9296bf8a1dc3a237f190ccd02b75c54a5074fe89b09d7
|
|
7
|
+
data.tar.gz: 5ec2172df49895ba3de6063f18373c52cefb8e475f829590e46a086b6b86a298782862936da30925bcd574fff8642fc5ba7ebba28ea7f68f2147fed27a0aa800
|
|
@@ -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 {
|
|
@@ -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/version.rb
CHANGED