Skip to content

Comments

Add click-to-copy for URL snippets in profile overview with i18n and keyboard accessibility#39

Open
Copilot wants to merge 6 commits intomainfrom
copilot/add-click-to-copy-url-snippets
Open

Add click-to-copy for URL snippets in profile overview with i18n and keyboard accessibility#39
Copilot wants to merge 6 commits intomainfrom
copilot/add-click-to-copy-url-snippets

Conversation

Copy link
Contributor

Copilot AI commented Nov 16, 2025

Add click-to-copy functionality for URL call snippets in profile overview cards

Completed:

  • Create JavaScript file with click-to-copy functionality
  • Update boot.php to include JavaScript
  • Update fragments to add click-to-copy functionality
  • Add CSS styling
  • Add i18n support for all languages
  • Add keyboard accessibility
  • Address security feedback:
    • Use rex_escape() instead of htmlspecialchars()
    • Use persistent textarea elements instead of dynamic creation
    • Replace data-copy attribute with data-copy-target to avoid XSS
  • Address accessibility feedback:
    • Use modern e.key instead of deprecated e.keyCode
    • Add visible focus indicator styling
  • Run CodeQL security analysis

Implementation Details:

Security Improvements:

  • Replaced htmlspecialchars() with rex_escape() for consistent escaping
  • Changed from storing code in data-copy attribute to using hidden textarea elements
  • Eliminates potential XSS vulnerability from unescaped attribute values

JavaScript Features:

  • Modern Clipboard API with fallback
  • Uses e.key instead of deprecated e.keyCode
  • References hidden textarea elements via data-copy-target attribute
  • Visual feedback with icon change and background flash

Accessibility:

  • Keyboard accessible with Tab navigation and Enter/Space keys
  • Visible focus indicator with outline
  • Hidden textarea elements have aria-hidden="true" and tabindex="-1"

User Experience:

  • Click or press Enter/Space to copy
  • Visual confirmation with checkmark icon
  • Localized tooltip messages
  • Auto-reset after 2 seconds
Original prompt

This section details on the original issue you should resolve

<issue_title>Kopierfähige URL-Aufrufe (rex_getUrl('', '', ['warehouse-article-id' => {id}])) und Schlüssel</issue_title>
<issue_description></issue_description>

<agent_instructions>Adds click-to-copy functionality for URL call snippets in profile overview cards, making it easier for developers to copy the exact PHP code needed to generate URLs for their profiles.

If Javascript is needed, create a seperate file as /assets/js/backend.js and use REDAXO methods to include backend js.</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@AWqxKAWERbXo Man soll auf den Text, der in der Übersicht der URL-Profile in der Backend-Page, generiert im Fragment, klicken können und damit direkt den Aufruf kopieren können.

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Co-authored-by: AWqxKAWERbXo <3855487+AWqxKAWERbXo@users.noreply.github.com>
Copilot AI changed the title [WIP] Add click-to-copy functionality for URL call snippets Add click-to-copy for URL snippets in profile overview Nov 16, 2025
Copilot AI requested a review from AWqxKAWERbXo November 16, 2025 16:40
@AWqxKAWERbXo AWqxKAWERbXo marked this pull request as ready for review November 16, 2025 16:53
Copilot AI review requested due to automatic review settings November 16, 2025 16:53
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
AWqxKAWERbXo and others added 2 commits November 16, 2025 17:59
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>
Copilot AI changed the title Add click-to-copy for URL snippets in profile overview Add click-to-copy for URL snippets in profile overview with i18n and keyboard accessibility Nov 16, 2025
@AWqxKAWERbXo AWqxKAWERbXo requested a review from Copilot November 16, 2025 22:16
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@AWqxKAWERbXo
Copy link
Member

@copilot instead of a temporary textarea, you can actually add a textarea element to the corresponding fragment prefilled with the necessary content.

use rex_escape() instead of htmlspecialchars()

Co-authored-by: AWqxKAWERbXo <3855487+AWqxKAWERbXo@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 16, 2025

@copilot instead of a temporary textarea, you can actually add a textarea element to the corresponding fragment prefilled with the necessary content.

use rex_escape() instead of `htmlspecialchars...

Applied in commit 18a1459. Changed implementation to:

  • Use persistent hidden textarea elements in the fragment (one per code snippet)
  • Replace htmlspecialchars() with rex_escape()
  • Changed data-copy attribute to data-copy-target (references textarea ID)
  • JavaScript now selects from textarea instead of creating temporary elements
  • Also addressed other feedback: added focus indicator styling and replaced deprecated keyCode with modern key property

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +99 to +102
<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>
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +99 to +103
<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>
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +99 to +103
<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>
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copilot uses AI. Check for mistakes.

var $this = $(this);
var targetId = $this.data('copy-target');
var $textarea = $('#' + targetId);
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
var $textarea = $('#' + targetId);
var $textarea = $(document.getElementById(targetId));

Copilot uses AI. Check for mistakes.
* Fallback copy method for older browsers or non-HTTPS contexts
*/
function fallbackCopyToClipboard($textarea, $element) {
$textarea[0].select();
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +100 to +103
<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>
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copilot uses AI. Check for mistakes.
Comment on lines +99 to +102
<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>
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Kopierfähige URL-Aufrufe (rex_getUrl('', '', ['warehouse-article-id' => {id}])) und Schlüssel

2 participants