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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: db1e7fbfb6264f74d110ff1e1655db8fef80c6d7aa096272174a262a60324bf6
4
- data.tar.gz: 5ae55bd529c1530c0fbb79b0428e736f59883e1c7c2a912135f635224f31f554
3
+ metadata.gz: e5cc8288417aa35fba130bae92aeb47383ba3f53dd10fb3e9d1d46d65605fbf2
4
+ data.tar.gz: 7862466adcb4e962f1a0fb5aa62f3958cafe98469df3da543d3df9d12b2c6bc7
5
5
  SHA512:
6
- metadata.gz: da01c7daa5fa38b5bff3bdfbe3212065f2e5ea1ad480cdf7b79c8c5888909350f92f4c053e98de97d7852293719620ed21c25430b1283ac4d4146c90ba9f3d30
7
- data.tar.gz: 7f1de4112b1ac732a17dc908cbcd9ef9b1c88d83565f49c1243779b536d1080358405e5c4aa6281e38ea1668253ac6fdb5d99abb96cff40658f5f00b4252784e
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: inline-flex;
60
- align-items: center;
61
- gap: 0.35rem;
62
- padding: 0.4rem 0.5rem;
63
- background: var(--surface-bg);
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
- function ToolbarColorPicker({ icon, title, color, onChange, onClear }) {
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
- <input
310
- type="color"
311
- value={color}
312
- onChange={(event) => onChange(event.target.value)}
313
- />
314
- <button
315
- type="button"
316
- className="lexical-toolbar-btn lexical-toolbar-btn--small"
317
- onClick={() => {
318
- onClear()
319
- setOpen(false)
320
- }}>
321
-
322
- </button>
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
- const newRow = createRow(creative)
79
- insertAtCorrectPosition(newRow, creative, targetContainer)
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(creative)
283
+ row.dataset.pendingSyncData = JSON.stringify(adjustedCreative)
252
284
  }
253
285
  return
254
286
  }
255
- applyRowProperties(row, creative)
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">&times;</button>
5
10
  <div style="margin-top:0.5em; color:#444; font-size:1em; line-height:1.5;">
@@ -1,3 +1,3 @@
1
1
  module Collavre
2
- VERSION = "0.12.1"
2
+ VERSION = "0.12.2"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: collavre
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.1
4
+ version: 0.12.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Collavre