Plugin Directory

source: code-click-to-copy/trunk/wpj-code-click-to-copy.php

Last change on this file was 3341180, checked in by hassanhashmi9, 8 months ago

Fix plugin header: add license, shorten description, add required fields.

  • Property svn:executable set to *
File size: 18.0 KB
Line 
1<?php
2/**
3 * Plugin Name: Code Click-to-Copy by WPJohnny
4 * Plugin URI: https://wpjohnny.com/code-click-to-copy/
5 * Description: Click anywhere on pre and code tags to automatically copy to clipboard. No aiming required - just click anywhere on the code block.
6 * Version: 1.0.1
7 * Requires at least: 5.0
8 * Tested up to: 6.4
9 * Requires PHP: 7.0
10 * Author: <a href="https://wpjohnny.com">WPJohnny</a>
11 * Author URI: https://wpjohnny.com
12 * License: GPLv2 or later
13 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
14 * Text Domain: code-click-to-copy
15 * Donate link: https://www.paypal.me/wpjohnny
16 * Network: false
17 */
18
19/**
20 * Load plugin textdomain.
21 */
22
23
24
25add_action( 'init', 'codeClickToCopyLoadTextdomain' );
26function codeClickToCopyLoadTextdomain() {
27    load_plugin_textdomain( 'code-click-to-copy', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
28}
29
30// Add admin menu
31add_action('admin_menu', 'code_click_to_copy_admin_menu');
32function code_click_to_copy_admin_menu() {
33    add_submenu_page(
34        'options-general.php', // Parent slug (Settings)
35        __('Code Click to Copy Settings', 'code-click-to-copy'),
36        'Code Click to Copy',
37        'manage_options',
38        'code-click-to-copy',
39        'code_click_to_copy_settings_page'
40    );
41}
42
43// Register settings
44add_action('admin_init', 'code_click_to_copy_settings_init');
45add_action('admin_enqueue_scripts', 'enqueue_admin_scripts');
46
47function code_click_to_copy_settings_init() {
48    register_setting('code_click_to_copy', 'code_click_to_copy_settings');
49
50    add_settings_section(
51        'code_click_to_copy_section',
52        __('Tooltip Customizations', 'code-click-to-copy'),
53        'code_click_to_copy_section_callback',
54        'code-click-to-copy'
55    );
56
57    // 1. Tooltip Background Color
58    add_settings_field(
59        'tooltip_background',
60        __('Tooltip Background Color', 'code-click-to-copy'),
61        'tooltip_background_callback',
62        'code-click-to-copy',
63        'code_click_to_copy_section'
64    );
65
66    // 2. Tooltip Text Color
67    add_settings_field(
68        'tooltip_text_color',
69        __('Tooltip Text Color', 'code-click-to-copy'),
70        'tooltip_text_color_callback',
71        'code-click-to-copy',
72        'code_click_to_copy_section'
73    );
74
75    // 3. Tooltip Copy Text
76    add_settings_field(
77        'click_to_copy_text',
78        __('Tooltip Copy Text', 'code-click-to-copy'),
79        'click_to_copy_text_callback',
80        'code-click-to-copy',
81        'code_click_to_copy_section'
82    );
83
84    // 4. Tooltip Copied Text
85    add_settings_field(
86        'copied_text',
87        __('Tooltip Copied Text', 'code-click-to-copy'),
88        'copied_text_callback',
89        'code-click-to-copy',
90        'code_click_to_copy_section'
91    );
92
93    // 5. Tooltip Hover CSS Class
94    add_settings_field(
95        'tooltip_custom_class',
96        __('Tooltip Hover CSS Class', 'code-click-to-copy'),
97        'tooltip_custom_class_callback',
98        'code-click-to-copy',
99        'code_click_to_copy_section'
100    );
101
102    // 6. Tooltip Function CSS Class
103    add_settings_field(
104        'custom_css_class',
105        __('Tooltip Function CSS Class', 'code-click-to-copy'),
106        'custom_css_class_callback',
107        'code-click-to-copy',
108        'code_click_to_copy_section'
109    );
110}
111
112function code_click_to_copy_section_callback() {
113    // Removed extra explanation text
114}
115
116function tooltip_background_callback() {
117    $options = get_option('code_click_to_copy_settings');
118    $background = isset($options['tooltip_background']) ? $options['tooltip_background'] : '#333333';
119    // Convert 3-digit hex to 6-digit if needed
120    if (preg_match('/^#([0-9A-F])([0-9A-F])([0-9A-F])$/i', $background, $matches)) {
121        $background = '#' . $matches[1] . $matches[1] . $matches[2] . $matches[2] . $matches[3] . $matches[3];
122    }
123    echo '<div style="display:flex;align-items:center;gap:10px;">';
124    echo '<input type="color" name="code_click_to_copy_settings[tooltip_background]" value="' . esc_attr($background) . '" style="width:50px;height:30px;">';
125    echo '<input type="text" class="hex-input" value="' . esc_attr($background) . '" style="width:100px;" placeholder="#000000">';
126    echo ' <a href="#" class="reset-color" data-default="#333333">' . esc_html__('Reset', 'code-click-to-copy') . '</a>';
127    echo '</div>';
128}
129
130function tooltip_text_color_callback() {
131    $options = get_option('code_click_to_copy_settings');
132    $text_color = isset($options['tooltip_text_color']) ? $options['tooltip_text_color'] : '#ffffff';
133    // Convert 3-digit hex to 6-digit if needed
134    if (preg_match('/^#([0-9A-F])([0-9A-F])([0-9A-F])$/i', $text_color, $matches)) {
135        $text_color = '#' . $matches[1] . $matches[1] . $matches[2] . $matches[2] . $matches[3] . $matches[3];
136    }
137    echo '<div style="display:flex;align-items:center;gap:10px;">';
138    echo '<input type="color" name="code_click_to_copy_settings[tooltip_text_color]" value="' . esc_attr($text_color) . '" style="width:50px;height:30px;">';
139    echo '<input type="text" class="hex-input" value="' . esc_attr($text_color) . '" style="width:100px;" placeholder="#000000">';
140    echo ' <a href="#" class="reset-color" data-default="#ffffff">' . esc_html__('Reset', 'code-click-to-copy') . '</a>';
141    echo '</div>';
142}
143
144function click_to_copy_text_callback() {
145    $options = get_option('code_click_to_copy_settings');
146    $val = isset($options['click_to_copy_text']) ? $options['click_to_copy_text'] : '';
147    echo '<input type="text" name="code_click_to_copy_settings[click_to_copy_text]" value="' . esc_attr($val) . '" placeholder="' . esc_attr__('Click to Copy (default)', 'code-click-to-copy') . '" />';
148}
149
150function copied_text_callback() {
151    $options = get_option('code_click_to_copy_settings');
152    $val = isset($options['copied_text']) ? $options['copied_text'] : '';
153    echo '<input type="text" name="code_click_to_copy_settings[copied_text]" value="' . esc_attr($val) . '" placeholder="' . esc_attr__('Copied! (default)', 'code-click-to-copy') . '" />';
154}
155
156function tooltip_custom_class_callback() {
157    $options = get_option('code_click_to_copy_settings');
158    $val = isset($options['tooltip_custom_class']) ? $options['tooltip_custom_class'] : 'codeCopyTooltip';
159    echo '<input type="text" name="code_click_to_copy_settings[tooltip_custom_class]" value="' . esc_attr($val) . '" placeholder="' . esc_attr__('e.g. my-tooltip-class', 'code-click-to-copy') . '" />';
160    echo '<br><small style="color:#666;">' . sprintf(esc_html__('Use default %1$scodeCopyTooltip%2$s class, or restyle tooltip hover with your own.', 'code-click-to-copy'),'<code>','</code>') . '</small>';
161}
162
163function custom_css_class_callback() {
164    $options = get_option('code_click_to_copy_settings');
165    if (isset($options['custom_css_class']) && trim($options['custom_css_class']) !== '') {
166        $custom_class = $options['custom_css_class'];
167    } else {
168        $custom_class = 'code, pre';
169    }
170    echo '<input type="text" name="code_click_to_copy_settings[custom_css_class]" value="' . esc_attr($custom_class) . '" placeholder="' . esc_attr__('e.g. my-copy-class,another-class', 'code-click-to-copy') . '" />';
171    echo '<br><small style="color:#666;">' . sprintf(esc_html__('Enter class names (without dot or spaces) to apply tooltip function, separate by comma if multiple. Can add to, or replace existing default class %1$scode%2$s, %1$spre%2$s.', 'code-click-to-copy'),'<code>','</code>') . '</small>';
172}
173
174
175
176function code_click_to_copy_settings_page() {
177    if (!current_user_can('manage_options')) {
178        return;
179    }
180    ?>
181    <div class="wrap">
182        <h1><?php echo esc_html(get_admin_page_title()); ?></h1>
183        <form action="options.php" method="post">
184            <?php
185            settings_fields('code_click_to_copy');
186            do_settings_sections('code-click-to-copy');
187            submit_button(esc_html__('Save Settings', 'code-click-to-copy'));
188            ?>
189        </form>
190        <p style="margin-top:30px; color:#555;">
191            <?php printf(esc_html__( 'Like this plugin? %1$sBuy me a beer%2$s or %3$sleave a 5-star review%2$s.', 'code-click-to-copy' ),'<a href="https://www.paypal.me/wpjohnny" target="_blank">','</a>','<a href="https://wordpress.org/support/plugin/code-click-to-copy/reviews/#new-post" target="_blank">'); ?>
192        </p>
193    </div>
194    <?php
195}
196
197add_action('wp_footer', 'codeCopyActivate');
198function codeCopyActivate(){
199    // Only run on frontend, not in admin areas
200    if (is_admin()) {
201        return;
202    }
203   
204    $options = get_option('code_click_to_copy_settings');
205    $clickToCopyStr = isset($options['click_to_copy_text']) && $options['click_to_copy_text'] !== '' ? $options['click_to_copy_text'] : __('Click to Copy', 'code-click-to-copy');
206    $copiedMessage = isset($options['copied_text']) && $options['copied_text'] !== '' ? $options['copied_text'] : __('Copied!', 'code-click-to-copy');
207    $tooltip_bg = isset($options['tooltip_background']) ? $options['tooltip_background'] : '#333333';
208    $tooltip_text = isset($options['tooltip_text_color']) ? $options['tooltip_text_color'] : '#ffffff';
209    if (isset($options['custom_css_class'])) {
210        $custom_class = $options['custom_css_class'];
211    } else {
212        $custom_class = 'code';
213    }
214    $tooltip_custom_class = isset($options['tooltip_custom_class']) ? trim($options['tooltip_custom_class']) : 'codeCopyTooltip';
215?>
216
217
218
219
220<div class="codeCopyTooltip <?php echo esc_attr($tooltip_custom_class); ?>" style="display: inline-block; background: <?php echo esc_attr($tooltip_bg); ?>; color: <?php echo esc_attr($tooltip_text); ?>; padding: 0 8px; font-size: 14px; border-radius: 2px; border: 1px solid #111; position: absolute; display: none;">
221<?= $clickToCopyStr; ?></div>
222<script type="text/javascript">
223    function getElementPosition(el) {
224        var rect = el.getBoundingClientRect(),
225        scrollLeft = window.pageXOffset || document.documentElement.scrollLeft,
226        scrollTop = window.pageYOffset || document.documentElement.scrollTop;
227        return { top: rect.top + scrollTop - 30, left: rect.left + scrollLeft }
228    }
229
230    async function copyToClipboard(text) {
231        try {
232            console.log('Code Click to Copy: Attempting to copy text:', text.substring(0, 50) + '...');
233            // Try modern Clipboard API first
234            if (navigator.clipboard && window.isSecureContext) {
235                console.log('Code Click to Copy: Using modern Clipboard API');
236                await navigator.clipboard.writeText(text);
237                console.log('Code Click to Copy: Successfully copied with Clipboard API');
238                return true;
239            } else {
240                console.log('Code Click to Copy: Using fallback execCommand method');
241                // Fallback for older browsers or non-secure contexts
242                const textArea = document.createElement('textarea');
243                textArea.value = text;
244                textArea.style.position = 'fixed';
245                textArea.style.left = '-999999px';
246                textArea.style.top = '-999999px';
247                document.body.appendChild(textArea);
248                textArea.focus();
249                textArea.select();
250               
251                try {
252                    document.execCommand('copy');
253                    document.body.removeChild(textArea);
254                    console.log('Code Click to Copy: Successfully copied with execCommand');
255                    return true;
256                } catch (err) {
257                    document.body.removeChild(textArea);
258                    console.error('Code Click to Copy: execCommand failed:', err);
259                    return false;
260                }
261            }
262        } catch (err) {
263            console.error('Code Click to Copy: Failed to copy: ', err);
264            return false;
265        }
266    }
267
268    function applyCodeCopy(element) {
269        element.style.cursor = 'pointer';
270        element.style.position = 'relative';
271       
272        element.addEventListener("click", async function(event) {
273            event.stopPropagation();
274            const textToCopy = element.textContent || element.innerText;
275           
276            const success = await copyToClipboard(textToCopy);
277           
278            if (success) {
279                codeCopyTooltip.innerHTML = <?= json_encode($copiedMessage); ?>;
280                codeCopyTooltip.style.display = 'block';
281               
282                // Hide tooltip after 2 seconds
283                setTimeout(() => {
284                    codeCopyTooltip.style.display = 'none';
285                }, 2000);
286            } else {
287                codeCopyTooltip.innerHTML = 'Failed to copy';
288                codeCopyTooltip.style.display = 'block';
289               
290                setTimeout(() => {
291                    codeCopyTooltip.style.display = 'none';
292                }, 2000);
293            }
294        });
295       
296        element.addEventListener("mouseover", function(event) {
297            event.stopPropagation();
298            var position = getElementPosition(element);
299            codeCopyTooltip.innerHTML = <?= json_encode($clickToCopyStr); ?>;
300            codeCopyTooltip.style.display = 'inline-block';
301            codeCopyTooltip.style.top = position.top + 'px';
302            codeCopyTooltip.style.left = position.left + 'px';
303        });
304       
305        element.addEventListener("mouseout", function(event) {
306            event.stopPropagation();
307            codeCopyTooltip.style.display = 'none';
308        });
309    }
310
311    var codeCopyTooltip = document.querySelector('.<?php echo esc_js($tooltip_custom_class); ?>');
312
313    (function() {
314        var selector = '<?php echo esc_js($custom_class); ?>';
315        if(selector.indexOf(',') !== -1) {
316            selector = selector.split(',').map(function(sel) {
317                sel = sel.trim();
318                if (/^[a-zA-Z][a-zA-Z0-9-]*$/.test(sel)) {
319                    return sel;
320                } else if (sel.startsWith('.')) {
321                    return sel;
322                } else {
323                    return '.' + sel;
324                }
325            }).join(', ');
326        } else {
327            selector = selector.trim();
328            if (!selector.startsWith('.') && !/^[a-zA-Z][a-zA-Z0-9-]*$/.test(selector)) {
329                selector = '.' + selector;
330            }
331        }
332       
333        // console.log('Code Click to Copy: Looking for elements with selector:', selector);
334       
335        // Wait for DOM to be ready
336        if (document.readyState === 'loading') {
337            document.addEventListener('DOMContentLoaded', function() {
338                var elements = document.querySelectorAll(selector);
339                // console.log('Code Click to Copy: Found', elements.length, 'elements on DOMContentLoaded');
340                elements.forEach(function(element) {
341                    applyCodeCopy(element);
342                });
343            });
344        } else {
345            var elements = document.querySelectorAll(selector);
346            // console.log('Code Click to Copy: Found', elements.length, 'elements immediately');
347            elements.forEach(function(element) {
348                applyCodeCopy(element);
349            });
350        }
351       
352        // Also handle dynamically added content
353        var observer = new MutationObserver(function(mutations) {
354            mutations.forEach(function(mutation) {
355                mutation.addedNodes.forEach(function(node) {
356                    if (node.nodeType === 1) { // Element node
357                        if (node.matches && node.matches(selector)) {
358                            applyCodeCopy(node);
359                        }
360                        if (node.querySelectorAll) {
361                            node.querySelectorAll(selector).forEach(function(element) {
362                                applyCodeCopy(element);
363                            });
364                        }
365                    }
366                });
367            });
368        });
369       
370        observer.observe(document.body, {
371            childList: true,
372            subtree: true
373        });
374    })();
375</script>
376<?php
377};
378
379function enqueue_admin_scripts($hook) {
380    if ('settings_page_code-click-to-copy' !== $hook) {
381        return;
382    }
383    wp_add_inline_script('jquery', '
384        jQuery(document).ready(function($) {
385            // Function to convert 3-digit hex to 6-digit
386            function expandHex(hex) {
387                if (hex.match(/^#([0-9A-F])([0-9A-F])([0-9A-F])$/i)) {
388                    return hex.replace(/^#([0-9A-F])([0-9A-F])([0-9A-F])$/i, "#$1$1$2$2$3$3");
389                }
390                return hex;
391            }
392
393            // Function to validate hex color
394            function isValidHex(hex) {
395                return /^#[0-9A-F]{6}$/i.test(hex) || /^#[0-9A-F]{3}$/i.test(hex);
396            }
397
398            // Sync color picker with HEX input
399            $("input[type=color]").on("input", function() {
400                var color = expandHex($(this).val());
401                $(this).next(".hex-input").val(color);
402            });
403
404            // Sync HEX input with color picker
405            $(".hex-input").on("input", function() {
406                var color = $(this).val();
407                if (isValidHex(color)) {
408                    var expandedColor = expandHex(color);
409                    $(this).prev("input[type=color]").val(expandedColor);
410                    $(this).val(expandedColor);
411                }
412            });
413
414            // Reset functionality
415            $(".reset-color").on("click", function(e) {
416                e.preventDefault();
417                var defaultColor = $(this).data("default");
418                var colorInput = $(this).prevAll("input[type=color]");
419                var hexInput = $(this).prevAll("input.hex-input");
420                colorInput.val(defaultColor);
421                hexInput.val(defaultColor);
422            });
423        });
424    ');
425}
426
427
428
Note: See TracBrowser for help on using the repository browser.