Add click-to-copy for URL snippets in profile overview with i18n and keyboard accessibility#39
Add click-to-copy for URL snippets in profile overview with i18n and keyboard accessibility#39
Conversation
Co-authored-by: AWqxKAWERbXo <3855487+AWqxKAWERbXo@users.noreply.github.com>
There was a problem hiding this comment.
Pull Request Overview
This PR adds click-to-copy functionality for URL call snippets in profile overview cards, allowing developers to click code snippets to copy PHP function calls to their clipboard instead of manually selecting text.
- Added JavaScript-based clipboard functionality with modern Clipboard API and fallback support
- Enhanced code elements with visual feedback (icon swap and background color changes)
- Included CSS styling for hover states and success feedback
Reviewed Changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| assets/js/backend.js | New JavaScript file implementing click-to-copy with Clipboard API and fallback, plus visual feedback |
| fragments/url/profiles/card.php | Added url-code-copy class, data-copy attribute, copy icon, and tooltip to code snippets |
| assets/styles.css | Added hover and success state styling for copyable code elements |
| boot.php | Registered new JavaScript file for authenticated backend users |
Comments suppressed due to low confidence (1)
assets/js/backend.js:55
- Unused variable originalHtml.
var originalHtml = $element.html();
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Alex <3855487+AWqxKAWERbXo@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Alex <3855487+AWqxKAWERbXo@users.noreply.github.com>
Co-authored-by: AWqxKAWERbXo <3855487+AWqxKAWERbXo@users.noreply.github.com>
There was a problem hiding this comment.
Pull Request Overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@copilot instead of a temporary textarea, you can actually add a textarea element to the corresponding fragment prefilled with the necessary content. use |
Co-authored-by: AWqxKAWERbXo <3855487+AWqxKAWERbXo@users.noreply.github.com>
Applied in commit 18a1459. Changed implementation to:
|
There was a problem hiding this comment.
Pull Request Overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <code class="url-code-copy" data-copy-target="url-copy-<?= $profile['id'] ?>-1" title="<?= rex_i18n::msg('url_click_to_copy') ?>" tabindex="0"><i class="rex-icon fa-copy"></i> rex_getUrl('', '', ['<?= rex_escape($profile['namespace'] ?? '') ?>' => {id}])</code> | ||
| <textarea id="url-copy-<?= $profile['id'] ?>-1" style="position: absolute; left: -9999px;" aria-hidden="true" tabindex="-1">rex_getUrl('', '', ['<?= rex_escape($profile['namespace'] ?? '') ?>' => {id}])</textarea> | ||
| oder via Artikel | ||
| <code>rex_article::get(<?= $article->getId() ?>)->getUrl(['<?= htmlspecialchars($profile['namespace'] ?? '') ?>' => {id}])</code><br> | ||
| <code class="url-code-copy" data-copy-target="url-copy-<?= $profile['id'] ?>-2" title="<?= rex_i18n::msg('url_click_to_copy') ?>" tabindex="0"><i class="rex-icon fa-copy"></i> rex_article::get(<?= $article->getId() ?>)->getUrl(['<?= rex_escape($profile['namespace'] ?? '') ?>' => {id}])</code><br> |
There was a problem hiding this comment.
The <code> elements are being used as interactive buttons but lack the role="button" attribute. This can cause confusion for screen reader users who may not understand that these elements are clickable.
Add role="button" to the code elements to properly communicate their interactive nature to assistive technologies. Additionally, consider adding aria-label attributes with localized text to provide clear context about what will be copied.
| <code class="url-code-copy" data-copy-target="url-copy-<?= $profile['id'] ?>-1" title="<?= rex_i18n::msg('url_click_to_copy') ?>" tabindex="0"><i class="rex-icon fa-copy"></i> rex_getUrl('', '', ['<?= rex_escape($profile['namespace'] ?? '') ?>' => {id}])</code> | ||
| <textarea id="url-copy-<?= $profile['id'] ?>-1" style="position: absolute; left: -9999px;" aria-hidden="true" tabindex="-1">rex_getUrl('', '', ['<?= rex_escape($profile['namespace'] ?? '') ?>' => {id}])</textarea> | ||
| oder via Artikel | ||
| <code>rex_article::get(<?= $article->getId() ?>)->getUrl(['<?= htmlspecialchars($profile['namespace'] ?? '') ?>' => {id}])</code><br> | ||
| <code class="url-code-copy" data-copy-target="url-copy-<?= $profile['id'] ?>-2" title="<?= rex_i18n::msg('url_click_to_copy') ?>" tabindex="0"><i class="rex-icon fa-copy"></i> rex_article::get(<?= $article->getId() ?>)->getUrl(['<?= rex_escape($profile['namespace'] ?? '') ?>' => {id}])</code><br> | ||
| <textarea id="url-copy-<?= $profile['id'] ?>-2" style="position: absolute; left: -9999px;" aria-hidden="true" tabindex="-1">rex_article::get(<?= $article->getId() ?>)->getUrl(['<?= rex_escape($profile['namespace'] ?? '') ?>' => {id}])</textarea> |
There was a problem hiding this comment.
The $profile['id'] value is used directly in HTML attributes without escaping, which could lead to XSS if the ID contains malicious content. Additionally, using an unescaped ID in the jQuery selector on line 16 ($('#' + targetId)) could lead to selector injection vulnerabilities.
Both the data-copy-target attribute and the textarea id should escape the profile ID value. Consider using rex_escape() or htmlspecialchars() for the ID values.
| <code class="url-code-copy" data-copy-target="url-copy-<?= $profile['id'] ?>-1" title="<?= rex_i18n::msg('url_click_to_copy') ?>" tabindex="0"><i class="rex-icon fa-copy"></i> rex_getUrl('', '', ['<?= rex_escape($profile['namespace'] ?? '') ?>' => {id}])</code> | ||
| <textarea id="url-copy-<?= $profile['id'] ?>-1" style="position: absolute; left: -9999px;" aria-hidden="true" tabindex="-1">rex_getUrl('', '', ['<?= rex_escape($profile['namespace'] ?? '') ?>' => {id}])</textarea> | ||
| oder via Artikel | ||
| <code>rex_article::get(<?= $article->getId() ?>)->getUrl(['<?= htmlspecialchars($profile['namespace'] ?? '') ?>' => {id}])</code><br> | ||
| <code class="url-code-copy" data-copy-target="url-copy-<?= $profile['id'] ?>-2" title="<?= rex_i18n::msg('url_click_to_copy') ?>" tabindex="0"><i class="rex-icon fa-copy"></i> rex_article::get(<?= $article->getId() ?>)->getUrl(['<?= rex_escape($profile['namespace'] ?? '') ?>' => {id}])</code><br> | ||
| <textarea id="url-copy-<?= $profile['id'] ?>-2" style="position: absolute; left: -9999px;" aria-hidden="true" tabindex="-1">rex_article::get(<?= $article->getId() ?>)->getUrl(['<?= rex_escape($profile['namespace'] ?? '') ?>' => {id}])</textarea> |
There was a problem hiding this comment.
[nitpick] The code uses rex_escape() for escaping values in the new click-to-copy functionality, while the rest of the file consistently uses htmlspecialchars() for similar escaping purposes (see lines 66, 82, 85, 88, 91, etc.).
For consistency with the existing codebase and to avoid confusion, consider using htmlspecialchars() instead of rex_escape() unless there's a specific reason to use the REDAXO-specific function.
|
|
||
| var $this = $(this); | ||
| var targetId = $this.data('copy-target'); | ||
| var $textarea = $('#' + targetId); |
There was a problem hiding this comment.
The code constructs a jQuery selector by directly concatenating the targetId from the data attribute ($('#' + targetId)). If the ID contains special characters (like dots, colons, brackets), the selector could fail or behave unexpectedly.
Consider using document.getElementById(targetId) wrapped in jQuery ($(document.getElementById(targetId))) instead, which is safer and doesn't require escaping special characters in IDs.
| var $textarea = $('#' + targetId); | |
| var $textarea = $(document.getElementById(targetId)); |
| * Fallback copy method for older browsers or non-HTTPS contexts | ||
| */ | ||
| function fallbackCopyToClipboard($textarea, $element) { | ||
| $textarea[0].select(); |
There was a problem hiding this comment.
The select() method on a hidden textarea (positioned off-screen with left: -9999px) may not work reliably in all browsers, particularly on mobile devices or with certain browser security settings. The element needs to be temporarily visible or in the viewport for selection to work consistently.
Consider temporarily making the textarea visible (e.g., with position: fixed; top: 0; left: 0; opacity: 0;) before calling select(), then hiding it again after the copy operation.
| <textarea id="url-copy-<?= $profile['id'] ?>-1" style="position: absolute; left: -9999px;" aria-hidden="true" tabindex="-1">rex_getUrl('', '', ['<?= rex_escape($profile['namespace'] ?? '') ?>' => {id}])</textarea> | ||
| oder via Artikel | ||
| <code>rex_article::get(<?= $article->getId() ?>)->getUrl(['<?= htmlspecialchars($profile['namespace'] ?? '') ?>' => {id}])</code><br> | ||
| <code class="url-code-copy" data-copy-target="url-copy-<?= $profile['id'] ?>-2" title="<?= rex_i18n::msg('url_click_to_copy') ?>" tabindex="0"><i class="rex-icon fa-copy"></i> rex_article::get(<?= $article->getId() ?>)->getUrl(['<?= rex_escape($profile['namespace'] ?? '') ?>' => {id}])</code><br> | ||
| <textarea id="url-copy-<?= $profile['id'] ?>-2" style="position: absolute; left: -9999px;" aria-hidden="true" tabindex="-1">rex_article::get(<?= $article->getId() ?>)->getUrl(['<?= rex_escape($profile['namespace'] ?? '') ?>' => {id}])</textarea> |
There was a problem hiding this comment.
[nitpick] Inline styles are used here for positioning the hidden textarea (style="position: absolute; left: -9999px;"). While this works, it would be better to move this to a CSS class in styles.css for better maintainability and consistency.
Consider adding a class like .url-copy-textarea with these styles in the CSS file instead of using inline styles.
| <code class="url-code-copy" data-copy-target="url-copy-<?= $profile['id'] ?>-1" title="<?= rex_i18n::msg('url_click_to_copy') ?>" tabindex="0"><i class="rex-icon fa-copy"></i> rex_getUrl('', '', ['<?= rex_escape($profile['namespace'] ?? '') ?>' => {id}])</code> | ||
| <textarea id="url-copy-<?= $profile['id'] ?>-1" style="position: absolute; left: -9999px;" aria-hidden="true" tabindex="-1">rex_getUrl('', '', ['<?= rex_escape($profile['namespace'] ?? '') ?>' => {id}])</textarea> | ||
| oder via Artikel | ||
| <code>rex_article::get(<?= $article->getId() ?>)->getUrl(['<?= htmlspecialchars($profile['namespace'] ?? '') ?>' => {id}])</code><br> | ||
| <code class="url-code-copy" data-copy-target="url-copy-<?= $profile['id'] ?>-2" title="<?= rex_i18n::msg('url_click_to_copy') ?>" tabindex="0"><i class="rex-icon fa-copy"></i> rex_article::get(<?= $article->getId() ?>)->getUrl(['<?= rex_escape($profile['namespace'] ?? '') ?>' => {id}])</code><br> |
There was a problem hiding this comment.
[nitpick] The icon classes used here (fa-copy and fa-check) don't follow the REDAXO icon naming pattern used elsewhere in the file. Other icons in this file use the pattern rex-icon rex-icon-{name} (e.g., rex-icon-refresh, rex-icon-edit, rex-icon-delete on lines 72, 222, 228).
Consider using REDAXO's standard icon naming convention if icons like rex-icon-copy and rex-icon-check are available, or verify that the FontAwesome icons are intentionally being used here.
Add click-to-copy functionality for URL call snippets in profile overview cards
Completed:
rex_escape()instead ofhtmlspecialchars()data-copyattribute withdata-copy-targetto avoid XSSe.keyinstead of deprecatede.keyCodeImplementation Details:
Security Improvements:
htmlspecialchars()withrex_escape()for consistent escapingdata-copyattribute to using hidden textarea elementsJavaScript Features:
e.keyinstead of deprecatede.keyCodedata-copy-targetattributeAccessibility:
aria-hidden="true"andtabindex="-1"User Experience:
Original prompt
rex_getUrl('', '', ['warehouse-article-id' => {id}])) und Schlüssel #31💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.