Skip to content

Commit 71638eb

Browse files
authored
Merge pull request #73 from Laravel-Backpack/add-anchor-and-copy-functionality-to-log-manager
add anchor and copy functionality to log manager
2 parents 6acbc85 + 9b36cdd commit 71638eb

2 files changed

Lines changed: 98 additions & 7 deletions

File tree

src/resources/lang/en/logmanager.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,7 @@
3030
'delete_error_message' => 'The log file has NOT been deleted.',
3131
'delete_confirmation_message' => 'The log file was deleted.',
3232
'log_file_doesnt_exist' => "The log file doesn't exist.",
33+
'link_copied_to_clipboard' => 'Link copied to clipboard',
34+
'link_copy_failed' => 'Failed to copy link',
3335

3436
];

src/resources/views/log_item.blade.php

Lines changed: 96 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,20 @@
2727
<div id="accordion" role="tablist" aria-multiselectable="true">
2828
@forelse($logs as $key => $log)
2929
<div class="card mb-0 pb-0">
30-
<div class="card-header bg-{{ $log['level_class'] }}" role="tab" id="heading{{ $key }}">
31-
<a role="button" data-toggle="collapse" data-bs-toggle="collapse" data-parent="#accordion" href="#collapse{{ $key }}" aria-expanded="true" aria-controls="collapse{{ $key }}" class="text-white">
30+
<div class="card-header bg-{{ $log['level_class'] }} d-flex align-items-center" role="tab" id="heading{{ $key }}">
31+
<a href="#heading{{ $key }}" class="log-anchor text-white me-2 mr-2 border border-white rounded-circle text-center text-decoration-none flex-shrink-0" style="width: 30px; height: 30px; line-height: 28px;">
32+
<i class="la la-link"></i>
33+
</a>
34+
<a role="button" data-toggle="collapse" data-bs-toggle="collapse" data-parent="#accordion" href="#collapse{{ $key }}" aria-expanded="false" aria-controls="collapse{{ $key }}" class="text-white flex-grow-1 collapsed">
3235
<i class="la la-{{ $log['level_img'] }}"></i>
3336
<span>[{{ $log['date'] }}]</span>
3437
{{ Str::limit($log['text'], 150) }}
3538
</a>
3639
</div>
37-
<div id="collapse{{ $key }}" class="panel-collapse collapse p-3" role="tabpanel" aria-labelledby="heading{{ $key }}">
40+
<div id="collapse{{ $key }}" class="panel-collapse collapse p-3" role="tabpanel" aria-labelledby="heading{{ $key }}" data-parent="#accordion" data-bs-parent="#accordion">
3841
<div class="panel-body">
3942
<p>{{$log['text']}}</p>
40-
<pre class="p-0" ><code class="php">{{ trim($log['stack']) }}</code></pre>
43+
<pre class="p-0" ><code>{{ trim($log['stack']) }}</code></pre>
4144
</div>
4245
</div>
4346
</div>
@@ -49,7 +52,93 @@
4952
@endsection
5053

5154
@section('after_scripts')
52-
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.6/styles/default.min.css">
53-
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.6/highlight.min.js"></script>
54-
<script>hljs.initHighlightingOnLoad();</script>
55+
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css">
56+
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
57+
<script>hljs.highlightAll();</script>
58+
<script>
59+
function openAccordionFromHash() {
60+
if (window.location.hash && window.location.hash.startsWith('#heading')) {
61+
const heading = document.querySelector(window.location.hash);
62+
if (heading) {
63+
const trigger = heading.querySelector('a[data-toggle="collapse"], a[data-bs-toggle="collapse"]');
64+
65+
if (trigger) {
66+
const targetId = trigger.getAttribute('href') || trigger.getAttribute('data-bs-target');
67+
const collapseEl = document.querySelector(targetId);
68+
const isExpanded = collapseEl && (collapseEl.classList.contains('show') || collapseEl.classList.contains('in'));
69+
70+
if (!isExpanded) {
71+
trigger.click();
72+
73+
try {
74+
if (collapseEl && !collapseEl.classList.contains('show')) {
75+
const bsCollapse = bootstrap.Collapse.getInstance(collapseEl) || new bootstrap.Collapse(collapseEl, { toggle: false });
76+
bsCollapse.show();
77+
}
78+
} catch (e) {
79+
//
80+
}
81+
}
82+
}
83+
84+
setTimeout(() => {
85+
const elementPosition = heading.getBoundingClientRect().top + window.scrollY;
86+
const offsetPosition = elementPosition - 80;
87+
88+
window.scrollTo({
89+
top: offsetPosition,
90+
behavior: "smooth"
91+
});
92+
}, 250);
93+
}
94+
}
95+
}
96+
97+
document.addEventListener('DOMContentLoaded', () => {
98+
openAccordionFromHash();
99+
100+
window.addEventListener('hashchange', () => {
101+
openAccordionFromHash();
102+
});
103+
104+
105+
function showNotification(type, text) {
106+
new Noty({
107+
text: text,
108+
type: type
109+
}).show();
110+
}
111+
112+
async function copyToClipboard(text) {
113+
try {
114+
if (navigator.clipboard) {
115+
await navigator.clipboard.writeText(text);
116+
showNotification('success', "{{ trans('backpack::logmanager.link_copied_to_clipboard') }}");
117+
} else {
118+
throw new Error('Clipboard API unavailable');
119+
}
120+
} catch (err) {
121+
// Fallback
122+
const textArea = document.createElement("textarea");
123+
textArea.value = text;
124+
document.body.appendChild(textArea);
125+
textArea.select();
126+
try {
127+
document.execCommand('copy');
128+
showNotification('success', "{{ trans('backpack::logmanager.link_copied_to_clipboard') }}");
129+
} catch (fallbackErr) {
130+
showNotification('error', "{{ trans('backpack::logmanager.link_copy_failed') }}");
131+
}
132+
document.body.removeChild(textArea);
133+
}
134+
}
135+
136+
document.querySelectorAll('.log-anchor').forEach(anchor => {
137+
anchor.addEventListener('click', function(e) {
138+
const url = window.location.origin + window.location.pathname + this.getAttribute('href');
139+
copyToClipboard(url);
140+
});
141+
});
142+
});
143+
</script>
55144
@endsection

0 commit comments

Comments
 (0)