collavre 0.2.3 → 0.2.4
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 +12 -27
- data/app/controllers/collavre/comments_controller.rb +10 -2
- data/app/javascript/controllers/comment_controller.js +29 -2
- data/app/javascript/controllers/comments/popup_controller.js +319 -11
- data/app/javascript/controllers/comments/topics_controller.js +20 -13
- data/app/javascript/lib/utils/markdown.js +4 -3
- data/app/views/collavre/comments/_comment.html.erb +19 -19
- data/app/views/collavre/comments/_comments_popup.html.erb +23 -23
- data/app/views/collavre/creatives/_github_integration_modal.html.erb +1 -1
- data/app/views/collavre/creatives/_set_plan_modal.html.erb +1 -1
- data/app/views/collavre/creatives/_share_button.html.erb +1 -1
- data/app/views/collavre/creatives/index.html.erb +1 -1
- data/lib/collavre/version.rb +1 -1
- metadata +1 -3
- data/app/views/collavre/comments/fullscreen.html.erb +0 -5
- data/app/views/layouts/collavre/chat.html.erb +0 -46
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 35c8808cc85f9a6a43fc6800c660850f2725137c805765436f7439dd6b75e07c
|
|
4
|
+
data.tar.gz: f6783666bfa9dc57128f4e5497d1e5267dfbf0de47cc9f0a8d95b021f1e35843
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ed9266ed122d64cedc2fbda308d3400d837776bb60a71b39a21c3a717bf5dad6ecfb2fda336928fa1ac112bb59c5fda5171f627583648a353d53fe5dbc2508c4
|
|
7
|
+
data.tar.gz: f46c41dde92548ff415f9eaa5b233f8d35af31f69ad7859c122a4e7cca52d992b6459f147af270652193e5db815fcd7669a632eee5928480d3a1babd9960e028
|
|
@@ -9,38 +9,23 @@
|
|
|
9
9
|
max-height: 80vh;
|
|
10
10
|
min-width: 200px;
|
|
11
11
|
min-height: 200px;
|
|
12
|
-
transition:
|
|
12
|
+
transition: top 0.25s ease, left 0.25s ease, width 0.25s ease, height 0.25s ease,
|
|
13
|
+
right 0.25s ease, bottom 0.25s ease, border-radius 0.25s ease,
|
|
14
|
+
box-shadow 0.25s ease, padding 0.25s ease;
|
|
13
15
|
max-width: calc(100vw - 0.5em) !important;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
body.chat-fullscreen {
|
|
17
|
-
margin: 0;
|
|
18
|
-
padding: 0;
|
|
19
|
-
height: 100vh;
|
|
20
19
|
overflow: hidden;
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
body.chat-fullscreen main {
|
|
24
|
-
height: 100vh;
|
|
25
|
-
width: 100vw;
|
|
26
|
-
max-width: none;
|
|
27
|
-
padding: 0;
|
|
28
|
-
margin: 0;
|
|
29
|
-
box-sizing: border-box;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
.comments-fullscreen-page {
|
|
33
|
-
height: 100vh;
|
|
34
|
-
width: 100vw;
|
|
35
|
-
padding: 0;
|
|
36
|
-
margin: 0;
|
|
37
|
-
box-sizing: border-box;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
.comments-fullscreen-page #comments-popup,
|
|
41
22
|
#comments-popup[data-fullscreen="true"] {
|
|
42
23
|
display: flex !important;
|
|
43
|
-
position:
|
|
24
|
+
position: fixed;
|
|
25
|
+
top: 0;
|
|
26
|
+
left: 0;
|
|
27
|
+
right: 0;
|
|
28
|
+
bottom: 0;
|
|
44
29
|
width: 100%;
|
|
45
30
|
height: 100%;
|
|
46
31
|
max-width: none !important;
|
|
@@ -48,7 +33,7 @@ body.chat-fullscreen main {
|
|
|
48
33
|
border-radius: 0;
|
|
49
34
|
box-shadow: none;
|
|
50
35
|
border: none;
|
|
51
|
-
z-index:
|
|
36
|
+
z-index: 9999;
|
|
52
37
|
box-sizing: border-box;
|
|
53
38
|
padding: 1em;
|
|
54
39
|
}
|
|
@@ -517,7 +502,9 @@ body.chat-fullscreen main {
|
|
|
517
502
|
}
|
|
518
503
|
}
|
|
519
504
|
|
|
520
|
-
.comment-owner-only
|
|
505
|
+
.comment-owner-only,
|
|
506
|
+
.comment-delete-hidden,
|
|
507
|
+
.comment-approve-hidden {
|
|
521
508
|
display: none;
|
|
522
509
|
}
|
|
523
510
|
|
|
@@ -616,13 +603,11 @@ body.chat-fullscreen main {
|
|
|
616
603
|
transform: translateY(0);
|
|
617
604
|
}
|
|
618
605
|
|
|
619
|
-
.comments-fullscreen-page #comments-popup,
|
|
620
606
|
#comments-popup[data-fullscreen="true"] {
|
|
621
607
|
display: flex !important;
|
|
622
608
|
transform: none;
|
|
623
609
|
border-radius: 0;
|
|
624
610
|
padding: 1em;
|
|
625
|
-
position: static;
|
|
626
611
|
width: 100%;
|
|
627
612
|
height: 100%;
|
|
628
613
|
box-sizing: border-box;
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
module Collavre
|
|
2
2
|
class CommentsController < ApplicationController
|
|
3
|
-
layout "collavre/chat", only: [ :fullscreen ]
|
|
4
3
|
before_action :set_creative
|
|
5
4
|
before_action :set_comment, only: [ :destroy, :show, :update, :convert, :approve, :update_action ]
|
|
6
5
|
|
|
7
6
|
def fullscreen
|
|
8
|
-
|
|
7
|
+
# Render the creative index page with comments popup auto-opened in fullscreen.
|
|
8
|
+
# This way the creative list loads behind the popup, so exiting fullscreen
|
|
9
|
+
# doesn't require a page reload.
|
|
10
|
+
@parent_creative = @creative
|
|
11
|
+
@creatives = []
|
|
12
|
+
@shared_list = @creative.all_shared_users
|
|
13
|
+
@auto_fullscreen = true
|
|
14
|
+
# Prepend creatives prefix so partials like 'add_button' resolve to collavre/creatives/_add_button
|
|
15
|
+
lookup_context.prefixes.prepend "collavre/creatives"
|
|
16
|
+
render "collavre/creatives/index"
|
|
9
17
|
end
|
|
10
18
|
|
|
11
19
|
def index
|
|
@@ -3,7 +3,7 @@ import { renderCommentMarkdown } from '../lib/utils/markdown'
|
|
|
3
3
|
|
|
4
4
|
// Connects to data-controller="comment"
|
|
5
5
|
export default class extends Controller {
|
|
6
|
-
static targets = ["ownerButton"]
|
|
6
|
+
static targets = ["ownerButton", "deleteButton", "approveButton", "actionApproveControls"]
|
|
7
7
|
|
|
8
8
|
connect() {
|
|
9
9
|
const contentElement = this.element.querySelector('.comment-content')
|
|
@@ -15,12 +15,39 @@ export default class extends Controller {
|
|
|
15
15
|
|
|
16
16
|
this.currentUserId = document.body.dataset.currentUserId
|
|
17
17
|
const commentAuthorId = this.element.dataset.userId
|
|
18
|
+
const creativeOwnerId = this.element.dataset.creativeOwnerId
|
|
19
|
+
const isAdmin = document.body.dataset.systemAdmin === 'true'
|
|
20
|
+
const isOwner = this.currentUserId && commentAuthorId && this.currentUserId === commentAuthorId
|
|
21
|
+
const isCreativeOwner = this.currentUserId && creativeOwnerId && this.currentUserId === creativeOwnerId
|
|
18
22
|
|
|
19
|
-
if (
|
|
23
|
+
if (isOwner) {
|
|
20
24
|
this.ownerButtonTargets.forEach((button) => {
|
|
21
25
|
button.classList.remove('comment-owner-only')
|
|
22
26
|
})
|
|
23
27
|
}
|
|
28
|
+
|
|
29
|
+
// Show delete button if user is comment author, creative owner, or admin
|
|
30
|
+
if (isOwner || isCreativeOwner || isAdmin) {
|
|
31
|
+
this.deleteButtonTargets.forEach((button) => {
|
|
32
|
+
button.classList.remove('comment-delete-hidden')
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Show approve button: user must be the designated approver or a system admin
|
|
37
|
+
const hasPendingAction = this.element.dataset.hasPendingAction === 'true'
|
|
38
|
+
const approverId = this.element.dataset.approverId
|
|
39
|
+
const isApprover = this.currentUserId && approverId && this.currentUserId === approverId
|
|
40
|
+
const canApprove = hasPendingAction && (isApprover || isAdmin)
|
|
41
|
+
|
|
42
|
+
if (canApprove) {
|
|
43
|
+
this.approveButtonTargets.forEach((button) => {
|
|
44
|
+
button.classList.remove('comment-approve-hidden')
|
|
45
|
+
})
|
|
46
|
+
// Also show action block approve controls (edit action button, form)
|
|
47
|
+
this.actionApproveControlsTargets.forEach((el) => {
|
|
48
|
+
el.classList.remove('comment-approve-hidden')
|
|
49
|
+
})
|
|
50
|
+
}
|
|
24
51
|
}
|
|
25
52
|
|
|
26
53
|
triggerReactionPicker(event) {
|
|
@@ -11,7 +11,9 @@ export default class extends Controller {
|
|
|
11
11
|
'closeButton',
|
|
12
12
|
'leftHandle',
|
|
13
13
|
'rightHandle',
|
|
14
|
-
'
|
|
14
|
+
'fullscreenButton',
|
|
15
|
+
'fullscreenIcon',
|
|
16
|
+
'exitFullscreenIcon',
|
|
15
17
|
]
|
|
16
18
|
|
|
17
19
|
connect() {
|
|
@@ -31,11 +33,13 @@ export default class extends Controller {
|
|
|
31
33
|
this.handleOnline = this.handleOnline.bind(this)
|
|
32
34
|
this.handleWindowFocus = this.handleWindowFocus.bind(this)
|
|
33
35
|
this.handleVisibilityChange = this.handleVisibilityChange.bind(this)
|
|
36
|
+
this.handlePopState = this.handlePopState.bind(this)
|
|
34
37
|
|
|
35
38
|
document.addEventListener(CREATIVE_CLICK_EVENT, this.handleCreativeClick)
|
|
36
39
|
window.addEventListener('online', this.handleOnline)
|
|
37
40
|
window.addEventListener('focus', this.handleWindowFocus)
|
|
38
41
|
document.addEventListener('visibilitychange', this.handleVisibilityChange)
|
|
42
|
+
window.addEventListener('popstate', this.handlePopState)
|
|
39
43
|
|
|
40
44
|
if (this.hasCloseButtonTarget) {
|
|
41
45
|
this.closeButtonTarget.addEventListener('click', () => this.close())
|
|
@@ -61,7 +65,21 @@ export default class extends Controller {
|
|
|
61
65
|
form.addEventListener('submit', () => window.localStorage.removeItem(SIZE_STORAGE_KEY))
|
|
62
66
|
})
|
|
63
67
|
|
|
64
|
-
if (this.
|
|
68
|
+
if (this.element.dataset.autoFullscreen === 'true') {
|
|
69
|
+
// Auto-fullscreen: open popup for creative then enter fullscreen
|
|
70
|
+
delete this.element.dataset.autoFullscreen
|
|
71
|
+
// Set previous URL to creative page (not the fullscreen URL)
|
|
72
|
+
const creativeId = this.element.dataset.creativeId
|
|
73
|
+
if (creativeId) {
|
|
74
|
+
this._previousUrl = `/creatives/${creativeId}`
|
|
75
|
+
}
|
|
76
|
+
requestAnimationFrame(() => {
|
|
77
|
+
this.openForCreative()
|
|
78
|
+
this._enterFullscreenImmediate()
|
|
79
|
+
})
|
|
80
|
+
} else if (this.isFullscreen()) {
|
|
81
|
+
// Sync UI for initial fullscreen state (legacy fullscreen page)
|
|
82
|
+
this._syncFullscreenUI(true)
|
|
65
83
|
// Defer to ensure all sibling controllers are connected
|
|
66
84
|
requestAnimationFrame(() => this.openForCreative())
|
|
67
85
|
} else {
|
|
@@ -75,6 +93,7 @@ export default class extends Controller {
|
|
|
75
93
|
window.removeEventListener('online', this.handleOnline)
|
|
76
94
|
window.removeEventListener('focus', this.handleWindowFocus)
|
|
77
95
|
document.removeEventListener('visibilitychange', this.handleVisibilityChange)
|
|
96
|
+
window.removeEventListener('popstate', this.handlePopState)
|
|
78
97
|
window.removeEventListener('mousemove', this.handleResizeMove)
|
|
79
98
|
window.removeEventListener('mouseup', this.handleResizeStop)
|
|
80
99
|
if (this.isMobile()) {
|
|
@@ -131,8 +150,6 @@ export default class extends Controller {
|
|
|
131
150
|
this.element.dataset.canComment = canComment ? 'true' : 'false'
|
|
132
151
|
this.titleTarget.textContent = snippet
|
|
133
152
|
|
|
134
|
-
this.updateFullscreenLink(resolvedCreativeId)
|
|
135
|
-
|
|
136
153
|
this.prepareSize()
|
|
137
154
|
|
|
138
155
|
this.showPopup()
|
|
@@ -161,8 +178,6 @@ export default class extends Controller {
|
|
|
161
178
|
this.element.dataset.canComment = canComment ? 'true' : 'false'
|
|
162
179
|
this.titleTarget.textContent = snippet
|
|
163
180
|
|
|
164
|
-
this.updateFullscreenLink(resolvedCreativeId)
|
|
165
|
-
|
|
166
181
|
this.showPopup()
|
|
167
182
|
|
|
168
183
|
await this.notifyChildControllers({ creativeId: resolvedCreativeId, canComment })
|
|
@@ -388,11 +403,304 @@ export default class extends Controller {
|
|
|
388
403
|
}
|
|
389
404
|
}
|
|
390
405
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
const
|
|
394
|
-
|
|
395
|
-
|
|
406
|
+
// Enter fullscreen immediately without animation (for auto-fullscreen on page load)
|
|
407
|
+
_enterFullscreenImmediate() {
|
|
408
|
+
const el = this.element
|
|
409
|
+
el.style.transition = 'none'
|
|
410
|
+
el.dataset.fullscreen = 'true'
|
|
411
|
+
document.body.classList.add('chat-fullscreen')
|
|
412
|
+
this._syncFullscreenUI(true)
|
|
413
|
+
// Clear any inline position styles so CSS fullscreen rules apply
|
|
414
|
+
el.style.top = ''
|
|
415
|
+
el.style.left = ''
|
|
416
|
+
el.style.right = ''
|
|
417
|
+
el.style.bottom = ''
|
|
418
|
+
el.style.width = ''
|
|
419
|
+
el.style.height = ''
|
|
420
|
+
el.style.position = ''
|
|
421
|
+
// Force layout then restore transition
|
|
422
|
+
el.offsetHeight // eslint-disable-line no-unused-expressions
|
|
423
|
+
el.style.transition = ''
|
|
424
|
+
|
|
425
|
+
// URL is already /comments/fullscreen, no pushState needed
|
|
426
|
+
requestAnimationFrame(() => this.listController?.scrollToBottom())
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
toggleFullscreen() {
|
|
430
|
+
const entering = !this.isFullscreen()
|
|
431
|
+
const el = this.element
|
|
432
|
+
|
|
433
|
+
if (entering) {
|
|
434
|
+
// Save current inline styles for later restore
|
|
435
|
+
this._savedStyles = {
|
|
436
|
+
top: el.style.top,
|
|
437
|
+
right: el.style.right,
|
|
438
|
+
left: el.style.left,
|
|
439
|
+
width: el.style.width,
|
|
440
|
+
height: el.style.height,
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Capture current visual position
|
|
444
|
+
const rect = el.getBoundingClientRect()
|
|
445
|
+
|
|
446
|
+
// Disable transition, pin to current position as fixed
|
|
447
|
+
el.style.transition = 'none'
|
|
448
|
+
el.style.position = 'fixed'
|
|
449
|
+
el.style.top = `${rect.top}px`
|
|
450
|
+
el.style.left = `${rect.left}px`
|
|
451
|
+
el.style.right = 'auto'
|
|
452
|
+
el.style.width = `${rect.width}px`
|
|
453
|
+
el.style.height = `${rect.height}px`
|
|
454
|
+
|
|
455
|
+
// Force layout so the pinned position is applied
|
|
456
|
+
el.offsetHeight // eslint-disable-line no-unused-expressions
|
|
457
|
+
|
|
458
|
+
// Now enable transition and expand to fullscreen
|
|
459
|
+
el.style.transition = ''
|
|
460
|
+
el.dataset.fullscreen = 'true'
|
|
461
|
+
document.body.classList.add('chat-fullscreen')
|
|
462
|
+
this._syncFullscreenUI(true)
|
|
463
|
+
|
|
464
|
+
// Clear inline position so CSS fullscreen rules take over
|
|
465
|
+
el.style.top = '0'
|
|
466
|
+
el.style.left = '0'
|
|
467
|
+
el.style.right = '0'
|
|
468
|
+
el.style.bottom = '0'
|
|
469
|
+
el.style.width = '100%'
|
|
470
|
+
el.style.height = '100%'
|
|
471
|
+
|
|
472
|
+
// Update URL
|
|
473
|
+
const creativeId = el.dataset.creativeId
|
|
474
|
+
if (creativeId) {
|
|
475
|
+
this._previousUrl = window.location.href
|
|
476
|
+
const fullscreenPath = `/creatives/${creativeId}/comments/fullscreen`
|
|
477
|
+
window.history.pushState({ fullscreen: true }, '', fullscreenPath)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Clean up inline styles after transition ends
|
|
481
|
+
const cleanup = () => {
|
|
482
|
+
el.removeEventListener('transitionend', cleanup)
|
|
483
|
+
el.style.top = ''
|
|
484
|
+
el.style.left = ''
|
|
485
|
+
el.style.right = ''
|
|
486
|
+
el.style.bottom = ''
|
|
487
|
+
el.style.width = ''
|
|
488
|
+
el.style.height = ''
|
|
489
|
+
el.style.position = ''
|
|
490
|
+
}
|
|
491
|
+
el.addEventListener('transitionend', cleanup, { once: true })
|
|
492
|
+
// Fallback if transitionend doesn't fire
|
|
493
|
+
setTimeout(cleanup, 300)
|
|
494
|
+
|
|
495
|
+
} else {
|
|
496
|
+
const savedStyles = this._savedStyles
|
|
497
|
+
this._savedStyles = null
|
|
498
|
+
|
|
499
|
+
// Calculate target position using the same logic as updatePosition()
|
|
500
|
+
// so cleanup can apply it directly without calling updatePosition() (which would cause a snap)
|
|
501
|
+
const creativeId = el.dataset.creativeId
|
|
502
|
+
const scrollY = window.scrollY || window.pageYOffset
|
|
503
|
+
|
|
504
|
+
// Final absolute-position values (what updatePosition would set)
|
|
505
|
+
let finalTop = '' // px string with scrollY included
|
|
506
|
+
let finalRight = '' // px string
|
|
507
|
+
let finalWidth = savedStyles?.width || ''
|
|
508
|
+
let finalHeight = savedStyles?.height || ''
|
|
509
|
+
|
|
510
|
+
// Fixed-position animation targets (viewport-relative)
|
|
511
|
+
let animTop, animLeft, animWidth, animHeight
|
|
512
|
+
|
|
513
|
+
// Try to find the comment button for precise positioning
|
|
514
|
+
let targetButton = this.currentButton
|
|
515
|
+
if (!targetButton && creativeId) {
|
|
516
|
+
const row = document.querySelector(`creative-tree-row[creative-id="${creativeId}"]`)
|
|
517
|
+
targetButton = row?.querySelector('.comments-btn')
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (targetButton) {
|
|
521
|
+
this.currentButton = targetButton
|
|
522
|
+
const btnRect = targetButton.getBoundingClientRect()
|
|
523
|
+
const rightPx = window.innerWidth - btnRect.right + 24
|
|
524
|
+
|
|
525
|
+
animWidth = parseFloat(finalWidth) || 420
|
|
526
|
+
animHeight = parseFloat(finalHeight) || 640
|
|
527
|
+
|
|
528
|
+
// Calculate top in absolute coords (with scrollY) — same as updatePosition
|
|
529
|
+
let absTop = btnRect.bottom + scrollY + 4
|
|
530
|
+
const absBottom = absTop + animHeight
|
|
531
|
+
const viewportBottom = scrollY + window.innerHeight
|
|
532
|
+
if (absBottom > viewportBottom) {
|
|
533
|
+
absTop = Math.max(scrollY + 4, viewportBottom - animHeight - 4)
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Store final absolute-position values for cleanup
|
|
537
|
+
finalTop = `${absTop}px`
|
|
538
|
+
finalRight = `${rightPx}px`
|
|
539
|
+
|
|
540
|
+
// Convert to fixed coordinates for animation
|
|
541
|
+
animTop = absTop - scrollY
|
|
542
|
+
animLeft = window.innerWidth - rightPx - animWidth
|
|
543
|
+
} else if (savedStyles && Object.values(savedStyles).some(v => v)) {
|
|
544
|
+
// Fallback to saved styles
|
|
545
|
+
const rightVal = parseFloat(savedStyles.right) || 32
|
|
546
|
+
animWidth = parseFloat(savedStyles.width) || 420
|
|
547
|
+
animHeight = parseFloat(savedStyles.height) || 640
|
|
548
|
+
animLeft = savedStyles.left ? parseFloat(savedStyles.left) : (window.innerWidth - rightVal - animWidth)
|
|
549
|
+
animTop = parseFloat(savedStyles.top) ? (parseFloat(savedStyles.top) - scrollY) : 100
|
|
550
|
+
|
|
551
|
+
finalTop = savedStyles.top || ''
|
|
552
|
+
finalRight = savedStyles.right || ''
|
|
553
|
+
} else {
|
|
554
|
+
// No reference at all: use CSS defaults
|
|
555
|
+
animWidth = 420
|
|
556
|
+
animHeight = 640
|
|
557
|
+
animLeft = window.innerWidth - 32 - animWidth // right: 2em
|
|
558
|
+
animTop = 100
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Animated exit: pin at fullscreen position, then shrink to target
|
|
562
|
+
const fsRect = el.getBoundingClientRect()
|
|
563
|
+
|
|
564
|
+
el.style.transition = 'none'
|
|
565
|
+
el.style.position = 'fixed'
|
|
566
|
+
el.style.top = `${fsRect.top}px`
|
|
567
|
+
el.style.left = `${fsRect.left}px`
|
|
568
|
+
el.style.right = 'auto'
|
|
569
|
+
el.style.bottom = 'auto'
|
|
570
|
+
el.style.width = `${fsRect.width}px`
|
|
571
|
+
el.style.height = `${fsRect.height}px`
|
|
572
|
+
|
|
573
|
+
el.dataset.fullscreen = 'false'
|
|
574
|
+
document.body.classList.remove('chat-fullscreen')
|
|
575
|
+
this._syncFullscreenUI(false)
|
|
576
|
+
|
|
577
|
+
// Force layout so the pinned position is applied
|
|
578
|
+
el.offsetHeight // eslint-disable-line no-unused-expressions
|
|
579
|
+
|
|
580
|
+
// Animate to target position (fixed coordinates)
|
|
581
|
+
el.style.transition = ''
|
|
582
|
+
el.style.top = `${animTop}px`
|
|
583
|
+
el.style.left = `${animLeft}px`
|
|
584
|
+
el.style.width = `${animWidth}px`
|
|
585
|
+
el.style.height = `${animHeight}px`
|
|
586
|
+
|
|
587
|
+
const cleanup = () => {
|
|
588
|
+
el.removeEventListener('transitionend', cleanup)
|
|
589
|
+
// Switch from fixed back to default (absolute) positioning
|
|
590
|
+
// Apply the pre-calculated absolute coords directly — no updatePosition() needed
|
|
591
|
+
el.style.transition = 'none'
|
|
592
|
+
el.style.position = ''
|
|
593
|
+
el.style.bottom = ''
|
|
594
|
+
|
|
595
|
+
if (targetButton) {
|
|
596
|
+
// Set absolute coords matching updatePosition output
|
|
597
|
+
el.style.top = finalTop
|
|
598
|
+
el.style.right = finalRight
|
|
599
|
+
el.style.left = ''
|
|
600
|
+
el.style.width = finalWidth
|
|
601
|
+
el.style.height = finalHeight
|
|
602
|
+
} else if (savedStyles) {
|
|
603
|
+
el.style.top = ''
|
|
604
|
+
el.style.left = ''
|
|
605
|
+
el.style.right = ''
|
|
606
|
+
el.style.width = ''
|
|
607
|
+
el.style.height = ''
|
|
608
|
+
Object.assign(el.style, savedStyles)
|
|
609
|
+
} else {
|
|
610
|
+
el.style.top = ''
|
|
611
|
+
el.style.left = ''
|
|
612
|
+
el.style.right = ''
|
|
613
|
+
el.style.width = ''
|
|
614
|
+
el.style.height = ''
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Force layout then restore transitions
|
|
618
|
+
el.offsetHeight // eslint-disable-line no-unused-expressions
|
|
619
|
+
el.style.transition = ''
|
|
620
|
+
|
|
621
|
+
// Scroll active topic into view after popup has settled at final size
|
|
622
|
+
this.topicsController?.scrollToActiveTopic()
|
|
623
|
+
}
|
|
624
|
+
el.addEventListener('transitionend', cleanup, { once: true })
|
|
625
|
+
setTimeout(cleanup, 300)
|
|
626
|
+
|
|
627
|
+
// Update URL — append open_comments=true so the popup stays open on refresh
|
|
628
|
+
let backUrl = this._previousUrl || (creativeId ? `/creatives/${creativeId}` : null)
|
|
629
|
+
if (backUrl) {
|
|
630
|
+
const url = new URL(backUrl, window.location.origin)
|
|
631
|
+
url.searchParams.set('open_comments', 'true')
|
|
632
|
+
window.history.pushState({ fullscreen: false }, '', url.pathname + url.search)
|
|
633
|
+
}
|
|
634
|
+
this._previousUrl = null
|
|
635
|
+
|
|
636
|
+
// Scroll the selected creative row into view after exiting fullscreen
|
|
637
|
+
if (creativeId) {
|
|
638
|
+
requestAnimationFrame(() => {
|
|
639
|
+
const row = document.querySelector(`creative-tree-row[creative-id="${creativeId}"]`)
|
|
640
|
+
row?.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
|
641
|
+
})
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Scroll to bottom after layout change
|
|
646
|
+
requestAnimationFrame(() => {
|
|
647
|
+
this.listController?.scrollToBottom()
|
|
648
|
+
})
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
handlePopState(event) {
|
|
652
|
+
const isFs = event.state?.fullscreen === true
|
|
653
|
+
if (isFs !== this.isFullscreen()) {
|
|
654
|
+
const el = this.element
|
|
655
|
+
// Clear any animation inline styles to avoid stale positions
|
|
656
|
+
el.style.transition = 'none'
|
|
657
|
+
el.style.position = ''
|
|
658
|
+
el.style.top = ''
|
|
659
|
+
el.style.left = ''
|
|
660
|
+
el.style.right = ''
|
|
661
|
+
el.style.bottom = ''
|
|
662
|
+
el.style.width = ''
|
|
663
|
+
el.style.height = ''
|
|
664
|
+
|
|
665
|
+
el.dataset.fullscreen = isFs ? 'true' : 'false'
|
|
666
|
+
document.body.classList.toggle('chat-fullscreen', isFs)
|
|
667
|
+
this._syncFullscreenUI(isFs)
|
|
668
|
+
|
|
669
|
+
if (!isFs && this._savedStyles) {
|
|
670
|
+
Object.assign(el.style, this._savedStyles)
|
|
671
|
+
this._savedStyles = null
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Restore transition
|
|
675
|
+
el.offsetHeight // eslint-disable-line no-unused-expressions
|
|
676
|
+
el.style.transition = ''
|
|
677
|
+
|
|
678
|
+
requestAnimationFrame(() => this.listController?.scrollToBottom())
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
_syncFullscreenUI(entering) {
|
|
683
|
+
if (this.hasFullscreenIconTarget) {
|
|
684
|
+
this.fullscreenIconTarget.style.display = entering ? 'none' : ''
|
|
685
|
+
}
|
|
686
|
+
if (this.hasExitFullscreenIconTarget) {
|
|
687
|
+
this.exitFullscreenIconTarget.style.display = entering ? '' : 'none'
|
|
688
|
+
}
|
|
689
|
+
if (this.hasLeftHandleTarget) {
|
|
690
|
+
this.leftHandleTarget.style.display = entering ? 'none' : ''
|
|
691
|
+
}
|
|
692
|
+
if (this.hasRightHandleTarget) {
|
|
693
|
+
this.rightHandleTarget.style.display = entering ? 'none' : ''
|
|
694
|
+
}
|
|
695
|
+
if (this.hasCloseButtonTarget) {
|
|
696
|
+
this.closeButtonTarget.style.display = entering ? 'none' : ''
|
|
697
|
+
}
|
|
698
|
+
if (this.hasFullscreenButtonTarget) {
|
|
699
|
+
const label = entering
|
|
700
|
+
? (this.element.dataset.exitFullscreenLabel || 'Exit full screen')
|
|
701
|
+
: (this.element.dataset.fullscreenLabel || 'Full screen')
|
|
702
|
+
this.fullscreenButtonTarget.setAttribute('aria-label', label)
|
|
703
|
+
}
|
|
396
704
|
}
|
|
397
705
|
|
|
398
706
|
openFromUrl() {
|
|
@@ -86,7 +86,7 @@ export default class extends Controller {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
renderTopics(topics, canManage = false) {
|
|
89
|
-
const dragActions = canManage
|
|
89
|
+
const dragActions = canManage
|
|
90
90
|
? 'dragstart->comments--topics#handleTopicDragStart dragend->comments--topics#handleTopicDragEnd'
|
|
91
91
|
: ''
|
|
92
92
|
const dropActions = 'dragover->comments--topics#handleDragOver dragleave->comments--topics#handleDragLeave drop->comments--topics#handleDrop'
|
|
@@ -150,11 +150,11 @@ export default class extends Controller {
|
|
|
150
150
|
const targetTopicId = event.currentTarget.dataset.id // Empty string for Main
|
|
151
151
|
|
|
152
152
|
// Dispatch event for list_controller to handle the move
|
|
153
|
-
this.dispatch('move-to-topic', {
|
|
154
|
-
detail: {
|
|
155
|
-
commentIds,
|
|
156
|
-
targetTopicId
|
|
157
|
-
}
|
|
153
|
+
this.dispatch('move-to-topic', {
|
|
154
|
+
detail: {
|
|
155
|
+
commentIds,
|
|
156
|
+
targetTopicId
|
|
157
|
+
}
|
|
158
158
|
})
|
|
159
159
|
}
|
|
160
160
|
|
|
@@ -170,7 +170,7 @@ export default class extends Controller {
|
|
|
170
170
|
this.draggingTopicId = topicId
|
|
171
171
|
event.dataTransfer.setData('application/x-topic-id', topicId)
|
|
172
172
|
event.dataTransfer.effectAllowed = 'move'
|
|
173
|
-
|
|
173
|
+
|
|
174
174
|
requestAnimationFrame(() => {
|
|
175
175
|
topicEl.classList.add('topic-dragging')
|
|
176
176
|
})
|
|
@@ -213,7 +213,7 @@ export default class extends Controller {
|
|
|
213
213
|
|
|
214
214
|
async handleTopicReorderDrop(event) {
|
|
215
215
|
event.preventDefault()
|
|
216
|
-
|
|
216
|
+
|
|
217
217
|
const targetEl = event.currentTarget
|
|
218
218
|
targetEl.classList.remove('topic-drag-over-left', 'topic-drag-over-right')
|
|
219
219
|
|
|
@@ -356,13 +356,13 @@ export default class extends Controller {
|
|
|
356
356
|
if (event.target.closest('.topic-edit-input')) return
|
|
357
357
|
|
|
358
358
|
const id = event.currentTarget.dataset.id
|
|
359
|
-
|
|
359
|
+
|
|
360
360
|
// If clicking on already active topic (not Main), show edit mode
|
|
361
361
|
if (id && String(this.currentTopicId) === String(id) && this.canManageTopics) {
|
|
362
362
|
this.showEditInput(event.currentTarget, id)
|
|
363
363
|
return
|
|
364
364
|
}
|
|
365
|
-
|
|
365
|
+
|
|
366
366
|
this.selectTopic(id)
|
|
367
367
|
}
|
|
368
368
|
|
|
@@ -380,15 +380,15 @@ export default class extends Controller {
|
|
|
380
380
|
if (!topic) return
|
|
381
381
|
|
|
382
382
|
const currentName = topic.name
|
|
383
|
-
|
|
383
|
+
|
|
384
384
|
// Store original HTML for restore
|
|
385
385
|
topicEl.dataset.originalHtml = topicEl.innerHTML
|
|
386
|
-
|
|
386
|
+
|
|
387
387
|
// Replace content with input
|
|
388
388
|
topicEl.innerHTML = `<input type="text" class="topic-edit-input" value="${currentName}"
|
|
389
389
|
data-action="keydown->comments--topics#handleEditKey blur->comments--topics#cancelEdit"
|
|
390
390
|
data-topic-id="${topicId}">`
|
|
391
|
-
|
|
391
|
+
|
|
392
392
|
const input = topicEl.querySelector('input')
|
|
393
393
|
requestAnimationFrame(() => {
|
|
394
394
|
input.focus()
|
|
@@ -476,6 +476,13 @@ export default class extends Controller {
|
|
|
476
476
|
}
|
|
477
477
|
}
|
|
478
478
|
|
|
479
|
+
scrollToActiveTopic() {
|
|
480
|
+
const activeEl = this.listTarget.querySelector('.topic-tag.active')
|
|
481
|
+
if (activeEl) {
|
|
482
|
+
activeEl.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' })
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
479
486
|
handleNewMessage(event) {
|
|
480
487
|
const topicId = event.detail.topicId
|
|
481
488
|
if (!topicId) return
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { marked } from 'marked'
|
|
2
|
+
import DOMPurify from 'dompurify'
|
|
2
3
|
|
|
3
4
|
export function renderMarkdown(html) {
|
|
4
|
-
return marked.parse(html)
|
|
5
|
+
return DOMPurify.sanitize(marked.parse(html))
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
export function renderMarkdownInline(html) {
|
|
8
|
-
return marked.parseInline(html)
|
|
9
|
+
return DOMPurify.sanitize(marked.parseInline(html))
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export function renderCommentMarkdown(text) {
|
|
12
13
|
const content = text || ''
|
|
13
14
|
const html = content.includes('\n') ? marked.parse(content) : marked.parseInline(content)
|
|
14
|
-
return html.trim()
|
|
15
|
+
return DOMPurify.sanitize(html.trim())
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export function renderMarkdownInContainer(container) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<% comment_topic = comment.topic %>
|
|
2
2
|
<% current_topic_id = local_assigns[:current_topic_id] %>
|
|
3
|
-
|
|
3
|
+
<% has_pending_action = comment.action.present? && comment.action_executed_at.blank? %>
|
|
4
|
+
<% approver_id = comment.approver_id %>
|
|
5
|
+
<div class="comment-item" id="<%= dom_id(comment) %>" data-controller="comment" data-user-id="<%= comment.user&.id %>" data-comment-id="<%= comment.id %>" data-topic-id="<%= comment_topic&.id %>" data-creative-id="<%= comment.creative_id %>" data-creative-owner-id="<%= comment.creative&.user_id %>" data-has-pending-action="<%= has_pending_action %>" data-approver-id="<%= approver_id %>">
|
|
4
6
|
<div class="comment-select">
|
|
5
7
|
<input type="checkbox"
|
|
6
8
|
class="comment-select-checkbox"
|
|
@@ -49,9 +51,8 @@
|
|
|
49
51
|
<button class="<%= ['convert-comment-btn', ('comment-owner-only' unless can_convert_comment)].compact.join(' ') %>" data-comment-target="ownerButton" data-comment-id="<%= comment.id %>" title="<%= t('collavre.comments.convert_to_creative') %>">
|
|
50
52
|
<%= t('collavre.comments.convert_button') %>
|
|
51
53
|
</button>
|
|
52
|
-
<%
|
|
53
|
-
|
|
54
|
-
<button class="approve-comment-btn" data-comment-id="<%= comment.id %>" title="<%= t('collavre.comments.approve_button') %>">
|
|
54
|
+
<% if has_pending_action %>
|
|
55
|
+
<button class="approve-comment-btn comment-approve-hidden" data-comment-target="approveButton" data-comment-id="<%= comment.id %>" title="<%= t('collavre.comments.approve_button') %>">
|
|
55
56
|
<%= t('collavre.comments.approve_button') %>
|
|
56
57
|
</button>
|
|
57
58
|
<% end %>
|
|
@@ -61,12 +62,9 @@
|
|
|
61
62
|
<button class="copy-comment-link-btn" data-comment-id="<%= comment.id %>" data-comment-url="<%= collavre.creative_comment_url(comment.creative, comment, Rails.application.config.action_mailer.default_url_options) %>" title="<%= t('collavre.comments.copy_link_button') %>">
|
|
62
63
|
<%= t('collavre.comments.copy_link_button') %>
|
|
63
64
|
</button>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
<%= t('collavre.comments.delete_button') %>
|
|
68
|
-
</button>
|
|
69
|
-
<% end %>
|
|
65
|
+
<button class="delete-comment-btn comment-delete-hidden" data-comment-target="deleteButton" data-comment-id="<%= comment.id %>" title="<%= t('collavre.comments.delete') %>">
|
|
66
|
+
<%= t('collavre.comments.delete_button') %>
|
|
67
|
+
</button>
|
|
70
68
|
</div>
|
|
71
69
|
<div class="comment-content"><%= comment.content %></div>
|
|
72
70
|
|
|
@@ -118,15 +116,17 @@
|
|
|
118
116
|
<summary class="comment-action-summary"><%= t("collavre.comments.action_summary") %></summary>
|
|
119
117
|
<div class="comment-action-body">
|
|
120
118
|
<pre class="comment-action-json" data-comment-action-json><%= formatted_comment_action(comment) %></pre>
|
|
121
|
-
<% if
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
<
|
|
125
|
-
|
|
126
|
-
<
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
119
|
+
<% if has_pending_action %>
|
|
120
|
+
<div class="comment-action-approve-controls comment-approve-hidden" data-comment-target="actionApproveControls">
|
|
121
|
+
<button class="edit-comment-action-btn" type="button" data-comment-id="<%= comment.id %>"><%= t("collavre.comments.edit_action_button") %></button>
|
|
122
|
+
<form class="comment-action-edit-form" data-comment-id="<%= comment.id %>" style="display:none;">
|
|
123
|
+
<textarea class="comment-action-edit-textarea" name="comment[action]" rows="8"><%= formatted_comment_action(comment) %></textarea>
|
|
124
|
+
<div class="comment-action-edit-buttons">
|
|
125
|
+
<button class="cancel-comment-action-edit-btn" type="button"><%= t("app.cancel") %></button>
|
|
126
|
+
<button class="save-comment-action-btn" type="submit"><%= t("app.save") %></button>
|
|
127
|
+
</div>
|
|
128
|
+
</form>
|
|
129
|
+
</div>
|
|
130
130
|
<% end %>
|
|
131
131
|
</div>
|
|
132
132
|
</details>
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
|
|
2
2
|
<% fullscreen = local_assigns.fetch(:fullscreen, false) %>
|
|
3
|
+
<% auto_fullscreen = local_assigns.fetch(:auto_fullscreen, false) %>
|
|
3
4
|
<% creative = local_assigns[:creative] %>
|
|
4
5
|
<div id="comments-popup" data-controller="comments--popup comments--list comments--form comments--presence comments--mention-menu comments--topics" class="popup-box"
|
|
5
6
|
data-fullscreen="<%= fullscreen %>"
|
|
6
|
-
data-fullscreen
|
|
7
|
-
|
|
8
|
-
data-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
<% if auto_fullscreen %>data-auto-fullscreen="true"<% end %>
|
|
8
|
+
data-fullscreen-label="<%= t('collavre.comments.fullscreen', default: 'Full screen') %>"
|
|
9
|
+
data-exit-fullscreen-label="<%= t('collavre.comments.exit_fullscreen', default: 'Exit full screen') %>"
|
|
10
|
+
<% popup_creative = creative || (@parent_creative if auto_fullscreen) %>
|
|
11
|
+
<% if (fullscreen || auto_fullscreen) && popup_creative.present? %>
|
|
12
|
+
data-creative-id="<%= popup_creative.id %>"
|
|
13
|
+
data-creative-snippet="<%= popup_creative.creative_snippet %>"
|
|
14
|
+
data-can-comment="<%= popup_creative.has_permission?(Current.user, :feedback) %>"
|
|
11
15
|
<% end %>
|
|
12
16
|
data-loading-text="<%= t('app.loading') %>"
|
|
13
17
|
data-delete-confirm-text="<%= t("collavre.comments.delete_confirm") %>"
|
|
@@ -28,29 +32,25 @@
|
|
|
28
32
|
data-hint-drag-topic-text="<%= t('collavre.comments.hint_drag_topic') %>"
|
|
29
33
|
data-hint-move-button-text="<%= t('collavre.comments.hint_move_button') %>"
|
|
30
34
|
data-add-participant-text="<%= t('collavre.comments.add_participant') %>">
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
<div class="resize-handle resize-handle-right" data-comments--popup-target="rightHandle"></div>
|
|
34
|
-
<% end %>
|
|
35
|
+
<div class="resize-handle resize-handle-left" data-comments--popup-target="leftHandle"></div>
|
|
36
|
+
<div class="resize-handle resize-handle-right" data-comments--popup-target="rightHandle"></div>
|
|
35
37
|
<div class="comments-popup-header">
|
|
36
38
|
<h3 id="comments-popup-title" data-comments--popup-target="title"><%= fullscreen && creative.present? ? creative.creative_snippet : t('collavre.comments.comments') %></h3>
|
|
37
39
|
<span data-integration-badges class="integration-badges"></span>
|
|
38
40
|
<div class="comments-popup-actions">
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
<% else %>
|
|
46
|
-
<%= link_to "#",
|
|
47
|
-
class: "comments-popup-action comments-popup-fullscreen",
|
|
48
|
-
data: { "comments--popup-target": "fullscreenLink" },
|
|
49
|
-
aria: { label: t("collavre.comments.fullscreen", default: "Full screen") } do %>
|
|
41
|
+
<button class="comments-popup-action comments-popup-fullscreen"
|
|
42
|
+
data-comments--popup-target="fullscreenButton"
|
|
43
|
+
data-action="click->comments--popup#toggleFullscreen"
|
|
44
|
+
type="button"
|
|
45
|
+
aria-label="<%= t('collavre.comments.fullscreen', default: 'Full screen') %>">
|
|
46
|
+
<span data-comments--popup-target="fullscreenIcon">
|
|
50
47
|
<%= svg_tag "fullscreen.svg", class: "comments-popup-action-icon" %>
|
|
51
|
-
|
|
52
|
-
<
|
|
53
|
-
|
|
48
|
+
</span>
|
|
49
|
+
<span data-comments--popup-target="exitFullscreenIcon" style="display:none;">
|
|
50
|
+
<%= svg_tag "exit-fullscreen.svg", class: "comments-popup-action-icon" %>
|
|
51
|
+
</span>
|
|
52
|
+
</button>
|
|
53
|
+
<button id="close-comments-btn" data-comments--popup-target="closeButton" class="comments-popup-action comments-popup-close" type="button">×</button>
|
|
54
54
|
</div>
|
|
55
55
|
</div>
|
|
56
56
|
<div id="comment-participants" data-comments--presence-target="participants" data-comments--mention-menu-target="participants"></div>
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
data-delete-error="<%= t('collavre.creatives.index.github_integration_delete_error', default: 'Failed to remove the Github integration.') %>"
|
|
11
11
|
data-delete-button-label="<%= t('collavre.creatives.index.github_integration_delete_button', default: 'Remove integration') %>"
|
|
12
12
|
data-delete-select-warning="<%= t('collavre.creatives.index.github_integration_delete_select_warning', default: '삭제할 Repository를 선택하세요.') %>"
|
|
13
|
-
style="display:none;position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:
|
|
13
|
+
style="display:none;position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:10000;align-items:center;justify-content:center;">
|
|
14
14
|
<div class="popup-box" style="min-width:360px;max-width:90vw;">
|
|
15
15
|
<button type="button" id="close-github-modal" class="popup-close-btn">×</button>
|
|
16
16
|
<h2><%= t('collavre.creatives.index.github_integration_title', default: 'Configure Github integration') %></h2>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<div id="set-plan-modal" data-creatives--set-plan-modal-target="modal" data-action="click->creatives--set-plan-modal#backdrop" style="display:none;position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:
|
|
1
|
+
<div id="set-plan-modal" data-creatives--set-plan-modal-target="modal" data-action="click->creatives--set-plan-modal#backdrop" style="display:none;position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:10000;align-items:center;justify-content:center;">
|
|
2
2
|
<div class="popup-box" style="min-width:320px;max-width:90vw;">
|
|
3
3
|
<button id="close-set-plan-modal" class="popup-close-btn" data-action="creatives--set-plan-modal#close">×</button>
|
|
4
4
|
<h2><%= t('collavre.creatives.index.set_plan_title', default: 'Set Plan for Selected Creatives') %></h2>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<span aria-hidden="true"><%= svg_tag 'share.svg', class: 'icon-up', width: 22, height: 20 %></span>
|
|
3
3
|
</button>
|
|
4
4
|
<!-- Share Creative Modal -->
|
|
5
|
-
<div id="share-creative-modal" style="display:none;position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:
|
|
5
|
+
<div id="share-creative-modal" style="display:none;position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:10000;align-items:center;justify-content:center;" data-creative-id="<%= (@parent_creative || @creative).id %>">
|
|
6
6
|
<div class="popup-box" style="min-width:320px;max-width:90vw;">
|
|
7
7
|
<button id="close-share-modal" class="popup-close-btn">×</button>
|
|
8
8
|
<h2><%= t('collavre.creatives.index.share_creative') %></h2>
|
data/lib/collavre/version.rb
CHANGED
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.2.
|
|
4
|
+
version: 0.2.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Collavre
|
|
@@ -391,7 +391,6 @@ files:
|
|
|
391
391
|
- app/views/collavre/comments/_presence_avatars.html.erb
|
|
392
392
|
- app/views/collavre/comments/_reaction_picker.html.erb
|
|
393
393
|
- app/views/collavre/comments/_read_receipts.html.erb
|
|
394
|
-
- app/views/collavre/comments/fullscreen.html.erb
|
|
395
394
|
- app/views/collavre/creatives/_add_button.html.erb
|
|
396
395
|
- app/views/collavre/creatives/_delete_button.html.erb
|
|
397
396
|
- app/views/collavre/creatives/_github_integration_modal.html.erb
|
|
@@ -449,7 +448,6 @@ files:
|
|
|
449
448
|
- app/views/collavre/users/passkeys.html.erb
|
|
450
449
|
- app/views/collavre/users/show.html.erb
|
|
451
450
|
- app/views/inbox/badge_component/_count.html.erb
|
|
452
|
-
- app/views/layouts/collavre/chat.html.erb
|
|
453
451
|
- app/views/layouts/collavre/slide.html.erb
|
|
454
452
|
- config/locales/ai_agent.en.yml
|
|
455
453
|
- config/locales/ai_agent.ko.yml
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<title><%= content_for(:title) || t('app.name') %></title>
|
|
5
|
-
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
-
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
7
|
-
<meta name="mobile-web-app-capable" content="yes">
|
|
8
|
-
<meta name="app-version" content="<%= Rails.application.config.app_version %>">
|
|
9
|
-
<%= csrf_meta_tags %>
|
|
10
|
-
<%= csp_meta_tag %>
|
|
11
|
-
|
|
12
|
-
<%= yield :head %>
|
|
13
|
-
|
|
14
|
-
<link rel="icon" href="/icon-1e3cf549d2.png" type="image/png">
|
|
15
|
-
<link rel="icon" href="/icon-1e3cf549d2.svg" type="image/svg+xml">
|
|
16
|
-
<link rel="apple-touch-icon" href="/icon-1e3cf549d2.png">
|
|
17
|
-
|
|
18
|
-
<%= stylesheet_link_tag :app, "data-turbo-track": "reload" %>
|
|
19
|
-
<%= stylesheet_link_tag "collavre/creatives" %>
|
|
20
|
-
<%= stylesheet_link_tag "collavre/actiontext" %>
|
|
21
|
-
<%= stylesheet_link_tag "collavre/dark_mode" %>
|
|
22
|
-
<%= stylesheet_link_tag "collavre/popup" %>
|
|
23
|
-
<%= stylesheet_link_tag "collavre/comments_popup" %>
|
|
24
|
-
|
|
25
|
-
<%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true, type: "module" %>
|
|
26
|
-
|
|
27
|
-
<% if Current.user&.theme.present? && !%w[light dark].include?(Current.user.theme) %>
|
|
28
|
-
<% if (custom_theme = UserTheme.find_by(id: Current.user.theme)) %>
|
|
29
|
-
<style id="user-theme-styles" data-turbo-track="reload">
|
|
30
|
-
body {
|
|
31
|
-
<% custom_theme.variables.each do |key, value| %>
|
|
32
|
-
<%= key %>: <%= value %> !important;
|
|
33
|
-
<% end %>
|
|
34
|
-
}
|
|
35
|
-
</style>
|
|
36
|
-
<% end %>
|
|
37
|
-
<% end %>
|
|
38
|
-
</head>
|
|
39
|
-
<body class="chat-fullscreen<%= ' dark-mode' if Current.user&.theme == 'dark' %><%= ' light-mode' if Current.user&.theme == 'light' %>" data-current-user-id="<%= Current.user&.id %>" data-controller="action-text-attachment-link" data-action="click->action-text-attachment-link#download">
|
|
40
|
-
<main>
|
|
41
|
-
<%= yield %>
|
|
42
|
-
</main>
|
|
43
|
-
|
|
44
|
-
<%= render "collavre/comments/reaction_picker" %>
|
|
45
|
-
</body>
|
|
46
|
-
</html>
|