OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)
This commit is contained in:
9
app/Domain/Comments/Templates/components/input.blade.php
Normal file
9
app/Domain/Comments/Templates/components/input.blade.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<div class="commentBox tw-hidden" id="comment{!! $commentId !!}">
|
||||
<div class="commentImage">
|
||||
<x-users::profile-image :user="$user" />
|
||||
</div>
|
||||
<div class="commentReply">
|
||||
<x-global::forms.button tag="input" inputType="submit" contentRole="default" :labelText="__('links.reply')" name="comment" />
|
||||
</div>
|
||||
<div class="clearall"></div>
|
||||
</div>
|
||||
41
app/Domain/Comments/Templates/components/reply.blade.php
Normal file
41
app/Domain/Comments/Templates/components/reply.blade.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<div id="#{{ $comment['id'] }}">
|
||||
<div class="commentImage">
|
||||
<x-users::profile-image :user="array('id'=> $comment['userId'], 'modified' => $comment['userModified'])" />
|
||||
</div>
|
||||
<div class="commentMain">
|
||||
<div class="commentContent">
|
||||
<div class="right commendDate">
|
||||
{!! sprintf(
|
||||
__('text.written_on'),
|
||||
format($comment['date'])->date(),
|
||||
format($comment['date'])->time()
|
||||
) !!}
|
||||
</div>
|
||||
<span class="name">{!! sprintf(
|
||||
__('text.full_name'),
|
||||
$tpl->escape($comment['firstname']),
|
||||
$tpl->escape($comment['lastname'])
|
||||
) !!}</span>
|
||||
|
||||
<div class="text">
|
||||
{!! $tpl->escapeMinimal($comment['text']) !!}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="commentLinks">
|
||||
@if ($login::userIsAtLeast(\Leantime\Domain\Auth\Models\Roles::$commenter))
|
||||
<a href="javascript:void(0);"
|
||||
onclick="leantime.commentsController.toggleCommentBoxes({{ $comment['commentParent'] }})">
|
||||
<span class="fa fa-reply"></span> {{ __('links.reply') }}
|
||||
</a>
|
||||
@if($comment['userId'] == session("userdata.id"))
|
||||
<a href="{{ CURRENT_URL }}?delComment={{ $comment['id'] }}"
|
||||
class="deleteComment">
|
||||
<span class="fa fa-trash"></span> {{ __('links.delete') }}
|
||||
</a>
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearall"></div>
|
||||
</div>
|
||||
56
app/Domain/Comments/Templates/partials/reactions.blade.php
Normal file
56
app/Domain/Comments/Templates/partials/reactions.blade.php
Normal file
@@ -0,0 +1,56 @@
|
||||
@props([
|
||||
'reactions' => [],
|
||||
'commentId' => 0,
|
||||
'userReactions' => []
|
||||
])
|
||||
|
||||
@php
|
||||
$emojiMap = [
|
||||
'like' => '👍',
|
||||
'love' => '❤️',
|
||||
'celebrate' => '🎉',
|
||||
'funny' => '😄',
|
||||
'interesting' => '🤔',
|
||||
'support' => '💯',
|
||||
'sad' => '😥',
|
||||
'anger' => '😡',
|
||||
];
|
||||
@endphp
|
||||
|
||||
<span class="comment-reactions" id="reactions-{{ $commentId }}">
|
||||
@if(count($reactions) > 0)
|
||||
<span class="reaction-list">
|
||||
@foreach($reactions as $reactionData)
|
||||
@php
|
||||
$reactionKey = $reactionData['reaction'];
|
||||
$emoji = $emojiMap[$reactionKey] ?? $reactionKey;
|
||||
$isActive = in_array($reactionKey, $userReactions);
|
||||
$userNames = [];
|
||||
if (!empty($reactionData['users'])) {
|
||||
foreach ($reactionData['users'] as $user) {
|
||||
$userNames[] = $user['name'];
|
||||
}
|
||||
}
|
||||
$tooltip = implode(', ', $userNames);
|
||||
@endphp
|
||||
<button type="button"
|
||||
class="reaction-btn {{ $isActive ? 'active' : '' }}"
|
||||
title="{{ $tooltip }}"
|
||||
hx-post="{{ BASE_URL }}/hx/comments/reactions/toggle?commentId={{ $commentId }}"
|
||||
hx-vals='{"reaction": "{{ $reactionKey }}"}'
|
||||
hx-target="#reactions-{{ $commentId }}"
|
||||
hx-swap="outerHTML">
|
||||
<span class="reaction-emoji">{{ $emoji }}</span>
|
||||
<span class="reaction-count">{{ $reactionData['reactionCount'] }}</span>
|
||||
</button>
|
||||
@endforeach
|
||||
</span>
|
||||
@endif
|
||||
|
||||
<span class="reaction-picker-toggle">
|
||||
<button type="button" class="add-reaction-btn" onclick="toggleReactionPicker(this, {{ $commentId }})">
|
||||
<i class="fa fa-smile-o"></i>
|
||||
<span class="sr-only">Add reaction</span>
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
241
app/Domain/Comments/Templates/showAll.blade.php
Normal file
241
app/Domain/Comments/Templates/showAll.blade.php
Normal file
@@ -0,0 +1,241 @@
|
||||
@extends($layout)
|
||||
|
||||
@section('content')
|
||||
|
||||
@php
|
||||
$comments = app()->make(Leantime\Domain\Comments\Repositories\Comments::class);
|
||||
$formUrl = CURRENT_URL;
|
||||
|
||||
// Controller may not redirect. Make sure delComment is only added once
|
||||
if (str_contains($formUrl, '?delComment=')) {
|
||||
$urlParts = explode('?delComment=', $formUrl);
|
||||
$deleteUrlBase = $urlParts[0] . '?delComment=';
|
||||
} else {
|
||||
$deleteUrlBase = $formUrl . '?delComment=';
|
||||
}
|
||||
@endphp
|
||||
|
||||
<h4 class="widgettitle title-light"><span
|
||||
class="fa fa-comments"></span>{!! __('subtitles.discussion') !!}
|
||||
</h4>
|
||||
|
||||
<form method="post" accept-charset="utf-8" action="{{ $formUrl }}"
|
||||
id="commentForm">
|
||||
<a href="javascript:void(0);" onclick="toggleCommentBoxes(0)"
|
||||
style="display:none;" id="mainToggler"><span
|
||||
class="fa fa-plus-square"></span> {!! __('links.add_new_comment') !!}
|
||||
</a>
|
||||
|
||||
<div id="comment0" class="commentBox">
|
||||
<textarea rows="5" cols="50" class="tiptapSimple"
|
||||
name="text"></textarea><br/>
|
||||
<input type="submit" value="{{ __('buttons.save') }}"
|
||||
name="comment" class="btn btn-default btn-success"
|
||||
style="margin-left: 0px;"/>
|
||||
<input type="hidden" name="comment" value="1"/>
|
||||
<input type="hidden" name="father" id="father" value="0"/>
|
||||
<br/>
|
||||
</div>
|
||||
<hr/>
|
||||
|
||||
<div id="comments">
|
||||
<div>
|
||||
@foreach ($__get_comments as $row)
|
||||
<div style="display:block; padding:10px; margin-top:10px; border-bottom:1px solid #f0f0f0;">
|
||||
<img src="{{ BASE_URL }}/api/users?profileImage={{ $row['userId'] }}&v={{ format($row['userModified'])->timestamp() }}"
|
||||
style="float:left; width:50px; margin-right:10px; padding:2px;"/>
|
||||
<div class="right">{!! sprintf(__('text.written_on'), format($row['date'])->date(), format($row['date'])->time()) !!}</div>
|
||||
<strong>
|
||||
{!! sprintf(__('text.full_name'), $tpl->escape($row['firstname']), $tpl->escape($row['lastname'])) !!}
|
||||
</strong><br/>
|
||||
<div style="margin-left:60px;">{!! $row['text'] !!}</div>
|
||||
<div class="clear"></div>
|
||||
<div style="padding-left:60px" class="commentLinks">
|
||||
<a href="javascript:void(0);" class="replyButton"
|
||||
onclick="toggleCommentBoxes({{ $row['id'] }})">
|
||||
<span class="fa fa-reply"></span> {!! __('links.reply') !!}
|
||||
</a>
|
||||
|
||||
@if ($row['userId'] == session('userdata.id'))
|
||||
<a href="{{ $deleteUrlBase . $row['id'] }}"
|
||||
class="deleteComment">
|
||||
<span class="fa fa-trash"></span> {!! __('links.delete') !!}
|
||||
</a>
|
||||
@endif
|
||||
<span class="comment-reactions" id="reactions-{{ $row['id'] }}"
|
||||
hx-get="{{ BASE_URL }}/hx/comments/reactions/get?commentId={{ $row['id'] }}"
|
||||
hx-trigger="load"
|
||||
hx-swap="outerHTML">
|
||||
</span>
|
||||
<div style="display:none;"
|
||||
id="comment{{ $row['id'] }}"
|
||||
class="commentBox">
|
||||
<br/><x-global::forms.button tag="input" inputType="submit"
|
||||
:labelText="__('links.reply')"
|
||||
name="comment" contentRole="primary" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
|
||||
@if ($comments->getReplies($row['id']))
|
||||
@foreach ($comments->getReplies($row['id']) as $comment)
|
||||
<div style="display:block; padding:10px; padding-left: 60px; border-bottom:1px solid #f0f0f0;">
|
||||
<img src="{{ BASE_URL }}/api/users?profileImage={{ $comment['userId'] }}&v={{ $comment['userModified'] }}"
|
||||
style="float:left; width:50px; margin-right:10px; padding:2px;"/>
|
||||
<div>
|
||||
<div class="right">
|
||||
{!! sprintf(__('text.written_on'), format($comment['date'])->date(), format($comment['date'])->time()) !!}
|
||||
</div>
|
||||
<strong>
|
||||
{!! sprintf(__('text.full_name'), $tpl->escape($comment['firstname']), $tpl->escape($comment['lastname'])) !!}
|
||||
</strong><br/>
|
||||
<p style="margin-left:60px;">{!! nl2br($comment['text']) !!}</p>
|
||||
<div class="clear"></div>
|
||||
|
||||
<div style="padding-left:60px" class="commentLinks">
|
||||
@if ($comment['userId'] == session('userdata.id'))
|
||||
<a href="{{ $deleteUrlBase . $comment['id'] }}"
|
||||
class="deleteComment">
|
||||
<span class="fa fa-trash"></span> {!! __('links.delete') !!}
|
||||
</a>
|
||||
@endif
|
||||
<span class="comment-reactions" id="reactions-{{ $comment['id'] }}"
|
||||
hx-get="{{ BASE_URL }}/hx/comments/reactions/get?commentId={{ $comment['id'] }}"
|
||||
hx-trigger="load"
|
||||
hx-swap="outerHTML">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
@if (count($__get_comments) == 0)
|
||||
<div class="text-center">
|
||||
<div style='width:33%' class='svgContainer'>
|
||||
{!! file_get_contents(ROOT . '/dist/images/svg/undraw_real_time_collaboration_c62i.svg') !!}
|
||||
{{ $tpl->escape($language->__('text.no_comments')) }}
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@once
|
||||
@push('scripts')
|
||||
<script type='text/javascript'>
|
||||
|
||||
// Initialize Tiptap simple editor
|
||||
if (window.leantime && window.leantime.tiptapController) {
|
||||
leantime.tiptapController.initSimpleEditor();
|
||||
}
|
||||
|
||||
function toggleCommentBoxes(id) {
|
||||
@if ($login::userIsAtLeast($roles::$commenter))
|
||||
if (id == 0) {
|
||||
jQuery('#mainToggler').hide();
|
||||
} else {
|
||||
jQuery('#mainToggler').show();
|
||||
}
|
||||
|
||||
// Destroy existing Tiptap editors before removing textareas
|
||||
jQuery('.commentBox').each(function() {
|
||||
var wrapper = jQuery(this).find('.tiptap-wrapper');
|
||||
if (wrapper.length && window.leantime && window.leantime.tiptapController) {
|
||||
leantime.tiptapController.registry.destroyWithin(wrapper[0]);
|
||||
}
|
||||
});
|
||||
|
||||
jQuery('.commentBox').hide('fast', function () {
|
||||
// Remove both textarea and any tiptap wrapper
|
||||
jQuery('.commentBox textarea').remove();
|
||||
jQuery('.commentBox .tiptap-wrapper').remove();
|
||||
|
||||
// Create new textarea with tiptapSimple class
|
||||
jQuery('#comment' + id + '').prepend('<textarea rows="5" cols="75" name="text" class="tiptapSimple"></textarea>');
|
||||
|
||||
// Initialize Tiptap editor on the new textarea
|
||||
if (window.leantime && window.leantime.tiptapController) {
|
||||
leantime.tiptapController.initSimpleEditor();
|
||||
}
|
||||
});
|
||||
|
||||
jQuery('#comment' + id + '').show('fast');
|
||||
jQuery('#father').val(id);
|
||||
@endif
|
||||
}
|
||||
|
||||
// Reaction emoji picker - uses keys that map to the Reactions model
|
||||
var reactionOptions = [
|
||||
{ key: 'like', emoji: '👍' },
|
||||
{ key: 'love', emoji: '❤️' },
|
||||
{ key: 'celebrate', emoji: '🎉' },
|
||||
{ key: 'funny', emoji: '😄' },
|
||||
{ key: 'interesting', emoji: '🤔' },
|
||||
{ key: 'support', emoji: '💯' }
|
||||
];
|
||||
var activeReactionPicker = null;
|
||||
|
||||
function toggleReactionPicker(btn, commentId) {
|
||||
// Close any existing picker
|
||||
if (activeReactionPicker) {
|
||||
activeReactionPicker.remove();
|
||||
activeReactionPicker = null;
|
||||
}
|
||||
|
||||
// Create picker element
|
||||
var picker = document.createElement('div');
|
||||
picker.className = 'reaction-emoji-picker show';
|
||||
picker.innerHTML = '<div class="reaction-emoji-picker__grid">' +
|
||||
reactionOptions.map(function(r) {
|
||||
return '<button type="button" class="reaction-emoji-picker__btn" ' +
|
||||
'onclick="addReaction(\'' + r.key + '\', ' + commentId + ')">' +
|
||||
r.emoji + '</button>';
|
||||
}).join('') +
|
||||
'</div>';
|
||||
|
||||
// Position the picker near the button
|
||||
var btnRect = btn.getBoundingClientRect();
|
||||
picker.style.position = 'fixed';
|
||||
picker.style.left = btnRect.left + 'px';
|
||||
picker.style.top = (btnRect.bottom + 5) + 'px';
|
||||
|
||||
document.body.appendChild(picker);
|
||||
activeReactionPicker = picker;
|
||||
|
||||
// Close on click outside
|
||||
setTimeout(function() {
|
||||
document.addEventListener('click', closeReactionPicker);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function closeReactionPicker(e) {
|
||||
if (activeReactionPicker && !activeReactionPicker.contains(e.target) && !e.target.classList.contains('add-reaction-btn')) {
|
||||
activeReactionPicker.remove();
|
||||
activeReactionPicker = null;
|
||||
document.removeEventListener('click', closeReactionPicker);
|
||||
}
|
||||
}
|
||||
|
||||
function addReaction(reactionKey, commentId) {
|
||||
if (activeReactionPicker) {
|
||||
activeReactionPicker.remove();
|
||||
activeReactionPicker = null;
|
||||
}
|
||||
|
||||
// Make HTMX request to toggle reaction
|
||||
htmx.ajax('POST', '{{ BASE_URL }}/hx/comments/reactions/toggle?commentId=' + commentId, {
|
||||
values: { reaction: reactionKey },
|
||||
target: '#reactions-' + commentId,
|
||||
swap: 'outerHTML'
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
@endpush
|
||||
@endonce
|
||||
|
||||
@endsection
|
||||
@@ -0,0 +1,330 @@
|
||||
@php
|
||||
// Repository is used downstream for getReplies(). Renamed from
|
||||
// $comments because the controller already assigns $comments to
|
||||
// the array of comments to render — overwriting it with the
|
||||
// repository object made the @foreach below iterate the object's
|
||||
// (empty) public properties instead of the array, so every
|
||||
// Discussion section came up empty regardless of how the comment
|
||||
// was added.
|
||||
$commentsRepo = app()->make(Leantime\Domain\Comments\Repositories\Comments::class);
|
||||
$formUrl = CURRENT_URL;
|
||||
$formHash = md5($formUrl);
|
||||
|
||||
// Controller may not redirect. Make sure delComment is only added once
|
||||
if (str_contains($formUrl, '?delComment=')) {
|
||||
$urlParts = explode('?delComment=', $formUrl);
|
||||
$deleteUrlBase = $urlParts[0] . '?delComment=';
|
||||
} else {
|
||||
$deleteUrlBase = $formUrl . '?delComment=';
|
||||
}
|
||||
@endphp
|
||||
|
||||
<form method="post" accept-charset="utf-8" action="{{ $formUrl }}" id="commentForm-{{ $formHash }}" class="formModal">
|
||||
|
||||
@if ($login::userIsAtLeast($roles::$commenter))
|
||||
<div class="mainToggler-{{ $formHash }}" id="">
|
||||
<div class="commentImage">
|
||||
<img src="{{ BASE_URL }}/api/users?profileImage={{ session('userdata.id') }}&v={{ format(session('userdata.modified'))->timestamp() }}" />
|
||||
</div>
|
||||
<div class="commentReply inactive">
|
||||
<a href="javascript:void(0);" onclick="toggleCommentBoxes(0, null, '{{ $formHash }}')">
|
||||
{!! __('links.add_new_comment') !!}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="comment-{{ $formHash }}-0" class="commentBox-{{ $formHash }} commenterFields" style="display:none;">
|
||||
<div class="commentImage">
|
||||
<img src="{{ BASE_URL }}/api/users?profileImage={{ session('userdata.id') }}&v={{ format(session('userdata.modified'))->timestamp() }}" />
|
||||
</div>
|
||||
<div class="commentReply">
|
||||
<textarea rows="5" cols="50" class="tiptapSimple" name="text"></textarea>
|
||||
<input type="submit" value="{{ __('buttons.save') }}" name="comment" class="btn btn-primary btn-success" style="margin-left: 0px;"/>
|
||||
</div>
|
||||
<input type="hidden" name="comment" class="commenterField" value="1"/>
|
||||
<input type="hidden" name="father" class="commenterField" id="father-{{ $formHash }}" value="0"/>
|
||||
<input type="hidden" name="edit-comment-helper" class="commenterField" id="edit-comment-helper-{{ $formHash }}" />
|
||||
<br/>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div id="comments-{{ $formHash }}">
|
||||
<div>
|
||||
@foreach ($comments as $row)
|
||||
<div class="clearall">
|
||||
<div class="commentImage" id="comment-image-to-hide-on-edit-{{ $formHash }}-{{ $row['id'] }}">
|
||||
<img src="{{ BASE_URL }}/api/users?profileImage={{ $row['userId'] }}&v={{ format($row['userModified'])->timestamp() }}"/>
|
||||
</div>
|
||||
<div class="commentMain">
|
||||
<div class="commentContent" id="comment-to-hide-on-edit-{{ $formHash }}-{{ $row['id'] }}">
|
||||
<div class="right commentDate">
|
||||
{!! sprintf(__('text.written_on'), format($row['date'])->date(), format($row['date'])->time()) !!}
|
||||
@if ($login::userIsAtLeast($roles::$editor))
|
||||
<div class="inlineDropDownContainer" style="float:right; margin-left:10px;">
|
||||
<a href="javascript:void(0);" class="dropdown-toggle ticketDropDown" data-toggle="dropdown">
|
||||
<i class="fa fa-ellipsis-v" aria-hidden="true"></i>
|
||||
</a>
|
||||
|
||||
<ul class="dropdown-menu">
|
||||
@if (($row['userId'] == session('userdata.id')) || can('comments.moderate'))
|
||||
<li><a href="{{ $deleteUrlBase . $row['id'] }}" class="deleteComment formModal">
|
||||
<span class="fa fa-trash"></span> {!! __('links.delete') !!}
|
||||
</a></li>
|
||||
@endif
|
||||
@if (($row['userId'] == session('userdata.id')) || can('comments.moderate'))
|
||||
<li>
|
||||
<a href="javascript:void(0);" onclick="toggleCommentBoxes({{ $row['id'] }}, null, '{{ $formHash }}', true)">
|
||||
<span class="fa fa-edit"></span> {!! __('label.edit') !!}
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
@if (isset($ticket->id))
|
||||
<li><a href="javascript:void(0);" onclick="leantime.ticketsController.addCommentTimesheetContent({{ $row['id'] }}, {{ $ticket->id }});">{!! __('links.add_to_timesheets') !!}</a></li>
|
||||
@endif
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<span class="name">{!! sprintf(__('text.full_name'), $tpl->escape($row['firstname']), $tpl->escape($row['lastname'])) !!}</span>
|
||||
<div class="text tiptap-content" id="commentText-{{ $formHash }}-{{ $row['id'] }}">
|
||||
<div id="comment-text-to-hide-{{ $formHash }}-{{ $row['id'] }}">{!! $tpl->escapeMinimal($row['text']) !!}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="commentLinks" id="comment-link-to-hide-on-edit-{{ $formHash }}-{{ $row['id'] }}">
|
||||
@if ($login::userIsAtLeast($roles::$commenter))
|
||||
<a href="javascript:void(0);"
|
||||
onclick="toggleCommentBoxes({{ $row['id'] }}, null, '{{ $formHash }}')">
|
||||
<span class="fa fa-reply"></span> {!! __('links.reply') !!}
|
||||
</a>
|
||||
@endif
|
||||
<span class="comment-reactions" id="reactions-{{ $row['id'] }}"
|
||||
hx-get="{{ BASE_URL }}/hx/comments/reactions/get?commentId={{ $row['id'] }}"
|
||||
hx-trigger="load"
|
||||
hx-swap="outerHTML">
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{{-- Reply/edit box for the parent comment. Kept ABOVE the replies thread so
|
||||
editing a comment that has replies opens the editor in place rather than
|
||||
jumping below its replies. (#3319) --}}
|
||||
<div style="display:none;" id="comment-{{ $formHash }}-{{ $row['id'] }}" class="commentBox">
|
||||
<div class="commentImage">
|
||||
<img src="{{ BASE_URL }}/api/users?profileImage={{ session('userdata.id') }}&v={{ format(session('userdata.modified'))->timestamp() }}"/>
|
||||
</div>
|
||||
<div class="commentReply">
|
||||
<x-global::forms.button tag="input" inputType="submit" :labelText="__('links.reply')" name="comment" id="submit-reply-button" contentRole="primary" />
|
||||
<x-global::forms.button tag="input" inputType="button" onclick="cancel({{ $row['id'] }}, '{{ $formHash }}')" :labelText="__('links.cancel')" contentRole="tertiary" />
|
||||
</div>
|
||||
<div class="clearall"></div>
|
||||
</div>
|
||||
|
||||
<div class="replies">
|
||||
@if ($commentsRepo->getReplies($row['id']))
|
||||
@foreach ($commentsRepo->getReplies($row['id']) as $comment)
|
||||
<div>
|
||||
<div class="commentImage">
|
||||
<img src="{{ BASE_URL }}/api/users?profileImage={{ $comment['userId'] }}&v={{ format($comment['userModified'])->timestamp() }}"/>
|
||||
</div>
|
||||
<div class="commentMain">
|
||||
<div class="commentContent">
|
||||
<div class="right commentDate">
|
||||
{!! sprintf(__('text.written_on'), format($comment['date'])->date(), format($comment['date'])->time()) !!}
|
||||
</div>
|
||||
<span class="name">{!! sprintf(__('text.full_name'), $tpl->escape($comment['firstname']), $tpl->escape($comment['lastname'])) !!}</span>
|
||||
<div class="text tiptap-content" id="comment-text-to-hide-reply-{{ $formHash }}-{{ $comment['id'] }}">{!! $tpl->escapeMinimal($comment['text']) !!}</div>
|
||||
</div>
|
||||
|
||||
<div class="commentLinks">
|
||||
@if ($login::userIsAtLeast($roles::$commenter))
|
||||
<a href="javascript:void(0);"
|
||||
onclick="toggleCommentBoxes({{ $row['id'] }}, null, '{{ $formHash }}')">
|
||||
<span class="fa fa-reply"></span> {!! __('links.reply') !!}
|
||||
</a>
|
||||
@if ($comment['userId'] == session('userdata.id'))
|
||||
<a href="{{ $deleteUrlBase . $comment['id'] }}"
|
||||
class="deleteComment formModal">
|
||||
<span class="fa fa-trash"></span> {!! __('links.delete') !!}
|
||||
</a>
|
||||
<a href="javascript:void(0);" onclick="toggleCommentBoxes({{ $row['id'] }}, {{ $comment['id'] }}, '{{ $formHash }}', true, true)">
|
||||
<span class="fa fa-edit"></span> {!! __('label.edit') !!}
|
||||
</a>
|
||||
@endif
|
||||
@endif
|
||||
<span class="comment-reactions" id="reactions-{{ $comment['id'] }}"
|
||||
hx-get="{{ BASE_URL }}/hx/comments/reactions/get?commentId={{ $comment['id'] }}"
|
||||
hx-trigger="load"
|
||||
hx-swap="outerHTML">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearall"></div>
|
||||
</div>
|
||||
@endforeach
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearall"></div>
|
||||
</form>
|
||||
|
||||
<script type='text/javascript'>
|
||||
|
||||
jQuery(document).ready(function() {
|
||||
if (window.leantime && window.leantime.tiptapController) {
|
||||
leantime.tiptapController.initSimpleEditor();
|
||||
}
|
||||
});
|
||||
|
||||
function toggleCommentBoxes(id, commentId, formHash, editComment = false, isReply = false) {
|
||||
@if ($login::userIsAtLeast($roles::$commenter))
|
||||
|
||||
if (parseInt(id, 10) === 0) {
|
||||
jQuery(`.mainToggler-${formHash}`).hide();
|
||||
} else {
|
||||
jQuery(`.mainToggler-${formHash}`).show();
|
||||
}
|
||||
if (editComment) {
|
||||
jQuery(`#comment-to-hide-on-edit-${formHash}-${id}`).hide();
|
||||
jQuery(`#comment-link-to-hide-on-edit-${formHash}-${id}`).hide();
|
||||
jQuery(`#comment-image-to-hide-on-edit-${formHash}-${id}`).hide();
|
||||
jQuery(`#edit-comment-helper-${formHash}`).val(commentId || id);
|
||||
jQuery('#submit-reply-button').val('{{ __('buttons.save') }}');
|
||||
}
|
||||
|
||||
// Destroy existing Tiptap editors before removing textareas
|
||||
jQuery(`.commentBox-${formHash}`).each(function() {
|
||||
var wrapper = jQuery(this).find('.tiptap-wrapper');
|
||||
if (wrapper.length && window.leantime && window.leantime.tiptapController) {
|
||||
leantime.tiptapController.registry.destroyWithin(wrapper[0]);
|
||||
}
|
||||
});
|
||||
|
||||
jQuery(`.commentBox-${formHash} textarea`).remove();
|
||||
jQuery(`.commentBox-${formHash} .tiptap-wrapper`).remove();
|
||||
jQuery(`.commentBox-${formHash}`).hide();
|
||||
|
||||
// Create textarea with tiptapSimple class
|
||||
var initialContent = editComment ? jQuery(`#comment-text-to-hide-${isReply ? 'reply-' : ''}${formHash}-${commentId || id}`).html() : '';
|
||||
jQuery(`#comment-${formHash}-${id} .commentReply`).prepend(`<textarea rows="5" cols="75" name="text" id="editor_${formHash}-${id}" class="tiptapSimple">${initialContent}</textarea>`);
|
||||
|
||||
// Initialize Tiptap editor
|
||||
if (window.leantime && window.leantime.tiptapController) {
|
||||
leantime.tiptapController.initSimpleEditor();
|
||||
// Focus the editor after a short delay to allow initialization
|
||||
setTimeout(function() {
|
||||
var editorEl = document.querySelector(`#comment-${formHash}-${id} .tiptap-editor`);
|
||||
if (editorEl) {
|
||||
var editor = leantime.tiptapController.registry.get(editorEl);
|
||||
if (editor) {
|
||||
editor.commands.focus('end');
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
jQuery(`#comment-${formHash}-${id}`).show();
|
||||
jQuery(`#father-${formHash}`).val(id);
|
||||
|
||||
@endif
|
||||
}
|
||||
function cancel(id, formHash) {
|
||||
@if ($login::userIsAtLeast($roles::$commenter))
|
||||
jQuery(`#comment-to-hide-on-edit-${formHash}-${id}`).show();
|
||||
jQuery(`.commentBox-${formHash} textarea`).remove();
|
||||
jQuery(`#comment-link-to-hide-on-edit-${formHash}-${id}`).show();
|
||||
jQuery(`#comment-image-to-hide-on-edit-${formHash}-${id}`).show();
|
||||
jQuery(`#comment-${formHash}-${id}`).hide();
|
||||
@endif
|
||||
}
|
||||
|
||||
jQuery(".confetti").click(function(){
|
||||
confetti({
|
||||
spread: 70,
|
||||
origin: { y: 1.2 },
|
||||
});
|
||||
});
|
||||
|
||||
function respondToVisibility(element, callback) {
|
||||
var options = {
|
||||
root: document.documentElement,
|
||||
};
|
||||
|
||||
var observer = new IntersectionObserver((entries, observer) => {
|
||||
entries.forEach(entry => {
|
||||
callback(entry.intersectionRatio > 0);
|
||||
});
|
||||
}, options);
|
||||
|
||||
observer.observe(element);
|
||||
}
|
||||
|
||||
// Reaction emoji picker - uses keys that map to the Reactions model
|
||||
var reactionOptions = [
|
||||
{ key: 'like', emoji: '👍' },
|
||||
{ key: 'love', emoji: '❤️' },
|
||||
{ key: 'celebrate', emoji: '🎉' },
|
||||
{ key: 'funny', emoji: '😄' },
|
||||
{ key: 'interesting', emoji: '🤔' },
|
||||
{ key: 'support', emoji: '💯' }
|
||||
];
|
||||
var activeReactionPicker = null;
|
||||
|
||||
function toggleReactionPicker(btn, commentId) {
|
||||
// Close any existing picker
|
||||
if (activeReactionPicker) {
|
||||
activeReactionPicker.remove();
|
||||
activeReactionPicker = null;
|
||||
}
|
||||
|
||||
// Create picker element
|
||||
var picker = document.createElement('div');
|
||||
picker.className = 'reaction-emoji-picker show';
|
||||
picker.innerHTML = '<div class="reaction-emoji-picker__grid">' +
|
||||
reactionOptions.map(function(r) {
|
||||
return '<button type="button" class="reaction-emoji-picker__btn" ' +
|
||||
'onclick="addReaction(\'' + r.key + '\', ' + commentId + ')">' +
|
||||
r.emoji + '</button>';
|
||||
}).join('') +
|
||||
'</div>';
|
||||
|
||||
// Position the picker near the button
|
||||
var btnRect = btn.getBoundingClientRect();
|
||||
picker.style.position = 'fixed';
|
||||
picker.style.left = btnRect.left + 'px';
|
||||
picker.style.top = (btnRect.bottom + 5) + 'px';
|
||||
|
||||
document.body.appendChild(picker);
|
||||
activeReactionPicker = picker;
|
||||
|
||||
// Close on click outside
|
||||
setTimeout(function() {
|
||||
document.addEventListener('click', closeReactionPicker);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function closeReactionPicker(e) {
|
||||
if (activeReactionPicker && !activeReactionPicker.contains(e.target) && !e.target.classList.contains('add-reaction-btn')) {
|
||||
activeReactionPicker.remove();
|
||||
activeReactionPicker = null;
|
||||
document.removeEventListener('click', closeReactionPicker);
|
||||
}
|
||||
}
|
||||
|
||||
function addReaction(reactionKey, commentId) {
|
||||
if (activeReactionPicker) {
|
||||
activeReactionPicker.remove();
|
||||
activeReactionPicker = null;
|
||||
}
|
||||
|
||||
// Make HTMX request to toggle reaction
|
||||
htmx.ajax('POST', '{{ BASE_URL }}/hx/comments/reactions/toggle?commentId=' + commentId, {
|
||||
values: { reaction: reactionKey },
|
||||
target: '#reactions-' + commentId,
|
||||
swap: 'outerHTML'
|
||||
});
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user