You've already forked wc-licensed-product
Add inline editing for licenses and copy license key button
- Add inline editing for status, expiry date, and domain fields - Add copy-to-clipboard button for license keys - Add AJAX handlers for inline editing with nonce verification - Update LicenseManager with updateLicenseExpiry method - Add new translations for inline editing strings (de_CH) - Compile updated German translations to .mo file Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -579,3 +579,144 @@
|
||||
#license-search-input {
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
/* Inline Editing Styles */
|
||||
.wclp-editable-cell {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.wclp-editable-cell .wclp-display-value {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.wclp-editable-cell .wclp-edit-btn {
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s ease;
|
||||
color: #2271b1;
|
||||
padding: 0;
|
||||
margin-left: 5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.wclp-editable-cell:hover .wclp-edit-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.wclp-editable-cell .wclp-edit-btn .dashicons {
|
||||
font-size: 14px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.wclp-editable-cell .wclp-edit-btn:hover {
|
||||
color: #135e96;
|
||||
}
|
||||
|
||||
.wclp-edit-form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.wclp-edit-form .wclp-edit-input {
|
||||
max-width: 150px;
|
||||
height: 28px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.wclp-edit-form .wclp-edit-input[type="date"] {
|
||||
max-width: 130px;
|
||||
}
|
||||
|
||||
.wclp-edit-form select.wclp-edit-input {
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
.wclp-edit-form .button-small {
|
||||
height: 26px;
|
||||
line-height: 24px;
|
||||
padding: 0 8px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.wclp-edit-form .wclp-lifetime-btn {
|
||||
font-size: 16px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
/* Inline notice animation */
|
||||
.wclp-inline-notice {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* Make editable cells have a minimum height for consistency */
|
||||
.licenses-table .wclp-editable-cell {
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
/* Domain column wider for edit form */
|
||||
.licenses-table td[data-field="domain"] {
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
/* Status column */
|
||||
.licenses-table td[data-field="status"] {
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
/* Expiry column */
|
||||
.licenses-table td[data-field="expiry"] {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
/* Copy License Key Button */
|
||||
.wclp-copy-btn {
|
||||
color: #2271b1;
|
||||
padding: 0;
|
||||
margin-left: 5px;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.wclp-copy-btn:hover {
|
||||
color: #135e96;
|
||||
}
|
||||
|
||||
.wclp-copy-btn .dashicons {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.wclp-copy-tooltip {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: #1d2327;
|
||||
color: #fff;
|
||||
padding: 4px 8px;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
white-space: nowrap;
|
||||
z-index: 100;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.wclp-copy-tooltip::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border: 5px solid transparent;
|
||||
border-top-color: #1d2327;
|
||||
}
|
||||
|
||||
.wclp-license-key {
|
||||
user-select: all;
|
||||
}
|
||||
|
||||
@@ -234,4 +234,266 @@
|
||||
// Initialize when document is ready
|
||||
$(document).ready(init);
|
||||
|
||||
// ========================================
|
||||
// Inline Editing Functionality
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Initialize inline editing
|
||||
*/
|
||||
function initInlineEditing() {
|
||||
// Edit button click
|
||||
$(document).on('click', '.wclp-edit-btn', function(e) {
|
||||
e.preventDefault();
|
||||
var $cell = $(this).closest('.wclp-editable-cell');
|
||||
showEditForm($cell);
|
||||
});
|
||||
|
||||
// Cancel button click
|
||||
$(document).on('click', '.wclp-cancel-btn', function(e) {
|
||||
e.preventDefault();
|
||||
var $cell = $(this).closest('.wclp-editable-cell');
|
||||
hideEditForm($cell);
|
||||
});
|
||||
|
||||
// Save button click
|
||||
$(document).on('click', '.wclp-save-btn', function(e) {
|
||||
e.preventDefault();
|
||||
var $cell = $(this).closest('.wclp-editable-cell');
|
||||
saveEdit($cell);
|
||||
});
|
||||
|
||||
// Lifetime button click (for expiry field)
|
||||
$(document).on('click', '.wclp-lifetime-btn', function(e) {
|
||||
e.preventDefault();
|
||||
var $cell = $(this).closest('.wclp-editable-cell');
|
||||
$cell.find('.wclp-edit-input').val('');
|
||||
saveEdit($cell);
|
||||
});
|
||||
|
||||
// Enter key saves, Escape cancels
|
||||
$(document).on('keydown', '.wclp-edit-input', function(e) {
|
||||
var $cell = $(this).closest('.wclp-editable-cell');
|
||||
if (e.keyCode === 13) { // Enter
|
||||
e.preventDefault();
|
||||
saveEdit($cell);
|
||||
} else if (e.keyCode === 27) { // Escape
|
||||
e.preventDefault();
|
||||
hideEditForm($cell);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show edit form for a cell
|
||||
*/
|
||||
function showEditForm($cell) {
|
||||
$cell.find('.wclp-display-value, .wclp-edit-btn').hide();
|
||||
$cell.find('.wclp-edit-form').show();
|
||||
$cell.find('.wclp-edit-input').focus().select();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide edit form for a cell
|
||||
*/
|
||||
function hideEditForm($cell) {
|
||||
$cell.find('.wclp-edit-form').hide();
|
||||
$cell.find('.wclp-display-value, .wclp-edit-btn').show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save edit via AJAX
|
||||
*/
|
||||
function saveEdit($cell) {
|
||||
var field = $cell.data('field');
|
||||
var licenseId = $cell.data('license-id');
|
||||
var $input = $cell.find('.wclp-edit-input');
|
||||
var value = $input.val();
|
||||
var $saveBtn = $cell.find('.wclp-save-btn');
|
||||
var originalText = $saveBtn.text();
|
||||
|
||||
// Determine action based on field
|
||||
var action;
|
||||
var data = {
|
||||
action: '',
|
||||
nonce: wclpAdmin.editNonce,
|
||||
license_id: licenseId
|
||||
};
|
||||
|
||||
switch (field) {
|
||||
case 'status':
|
||||
data.action = 'wclp_update_license_status';
|
||||
data.status = value;
|
||||
break;
|
||||
case 'expiry':
|
||||
data.action = 'wclp_update_license_expiry';
|
||||
data.expiry_date = value;
|
||||
break;
|
||||
case 'domain':
|
||||
data.action = 'wclp_update_license_domain';
|
||||
data.domain = value;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// Show saving state
|
||||
$saveBtn.text(wclpAdmin.strings.saving).prop('disabled', true);
|
||||
$cell.find('.wclp-cancel-btn, .wclp-lifetime-btn').prop('disabled', true);
|
||||
|
||||
$.ajax({
|
||||
url: wclpAdmin.ajaxUrl,
|
||||
type: 'POST',
|
||||
data: data,
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
// Update display value
|
||||
updateDisplayValue($cell, field, response.data);
|
||||
hideEditForm($cell);
|
||||
showNotice('success', response.data.message);
|
||||
} else {
|
||||
showNotice('error', response.data.message || wclpAdmin.strings.saveFailed);
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
showNotice('error', wclpAdmin.strings.saveFailed);
|
||||
},
|
||||
complete: function() {
|
||||
$saveBtn.text(originalText).prop('disabled', false);
|
||||
$cell.find('.wclp-cancel-btn, .wclp-lifetime-btn').prop('disabled', false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the display value after successful save
|
||||
*/
|
||||
function updateDisplayValue($cell, field, data) {
|
||||
var $display = $cell.find('.wclp-display-value');
|
||||
|
||||
switch (field) {
|
||||
case 'status':
|
||||
var statusHtml = '<span class="license-status license-status-' + escapeHtml(data.status) + '">' +
|
||||
escapeHtml(data.status_label) + '</span>';
|
||||
$display.html(statusHtml);
|
||||
break;
|
||||
case 'expiry':
|
||||
if (data.expiry_date) {
|
||||
$display.html(escapeHtml(data.expiry_display));
|
||||
} else {
|
||||
$display.html('<span class="license-lifetime">' + wclpAdmin.strings.lifetime + '</span>');
|
||||
}
|
||||
// Update the input value
|
||||
$cell.find('.wclp-edit-input').val(data.expiry_date || '');
|
||||
break;
|
||||
case 'domain':
|
||||
$display.text(data.domain);
|
||||
// Update the input value
|
||||
$cell.find('.wclp-edit-input').val(data.domain);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a temporary notice
|
||||
*/
|
||||
function showNotice(type, message) {
|
||||
var $notice = $('<div class="notice notice-' + type + ' is-dismissible wclp-inline-notice"><p>' +
|
||||
escapeHtml(message) + '</p></div>');
|
||||
|
||||
// Insert at the top of the wrap
|
||||
$('.wrap h1').first().after($notice);
|
||||
|
||||
// Auto-dismiss after 3 seconds
|
||||
setTimeout(function() {
|
||||
$notice.fadeOut(300, function() {
|
||||
$(this).remove();
|
||||
});
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Initialize inline editing when document is ready
|
||||
$(document).ready(initInlineEditing);
|
||||
|
||||
// ========================================
|
||||
// Copy License Key Functionality
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Initialize copy license key buttons
|
||||
*/
|
||||
function initCopyButtons() {
|
||||
$(document).on('click', '.wclp-copy-btn', function(e) {
|
||||
e.preventDefault();
|
||||
var $btn = $(this);
|
||||
var licenseKey = $btn.data('license-key');
|
||||
|
||||
copyToClipboard(licenseKey).then(function() {
|
||||
showCopyFeedback($btn, true);
|
||||
}).catch(function() {
|
||||
showCopyFeedback($btn, false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy text to clipboard
|
||||
*/
|
||||
function copyToClipboard(text) {
|
||||
// Modern Clipboard API
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
return navigator.clipboard.writeText(text);
|
||||
}
|
||||
|
||||
// Fallback for older browsers
|
||||
return new Promise(function(resolve, reject) {
|
||||
var textarea = document.createElement('textarea');
|
||||
textarea.value = text;
|
||||
textarea.style.position = 'fixed';
|
||||
textarea.style.opacity = '0';
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
|
||||
try {
|
||||
var successful = document.execCommand('copy');
|
||||
document.body.removeChild(textarea);
|
||||
if (successful) {
|
||||
resolve();
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
} catch (err) {
|
||||
document.body.removeChild(textarea);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show copy feedback
|
||||
*/
|
||||
function showCopyFeedback($btn, success) {
|
||||
var $icon = $btn.find('.dashicons');
|
||||
var originalClass = 'dashicons-clipboard';
|
||||
var feedbackClass = success ? 'dashicons-yes' : 'dashicons-no';
|
||||
var feedbackColor = success ? '#00a32a' : '#d63638';
|
||||
|
||||
// Change icon temporarily
|
||||
$icon.removeClass(originalClass).addClass(feedbackClass).css('color', feedbackColor);
|
||||
|
||||
// Show tooltip
|
||||
var message = success ? wclpAdmin.strings.copied : wclpAdmin.strings.copyFailed;
|
||||
var $tooltip = $('<span class="wclp-copy-tooltip">' + escapeHtml(message) + '</span>');
|
||||
$btn.append($tooltip);
|
||||
|
||||
// Reset after delay
|
||||
setTimeout(function() {
|
||||
$icon.removeClass(feedbackClass).addClass(originalClass).css('color', '');
|
||||
$tooltip.remove();
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
// Initialize copy buttons when document is ready
|
||||
$(document).ready(initCopyButtons);
|
||||
|
||||
})(jQuery);
|
||||
|
||||
Reference in New Issue
Block a user