← All changes
|
includes/extensions/Custom_Code_Manager/assets/admin.js
+131
-142
51.1.44
→
51.1.86
View file →
| @@ -64,8 +64,15 @@ | ||
| 64 | 64 | $(document).on('click', '.kng-cc-export-btn', handleExportSingle); |
| 65 | 65 | |
| 66 | 66 | // Delete button |
| 67 | 67 | $(document).on('click', '.kng-cc-delete-btn', handleDelete); |
| 68 | + | |
| 69 | + // Pro upgrade popup | |
| 70 | + $('#kng-cc-add-new-limit').on('click', showProPopup); | |
| 71 | + $('#kng-cc-pro-popup-close, .kng-cc-pro-popup-dismiss').on('click', hideProPopup); | |
| 72 | + $('#kng-cc-pro-popup').on('click', function(e) { | |
| 73 | + if (e.target === this) hideProPopup(); | |
| 74 | + }); | |
| 68 | 75 | } |
| 69 | 76 | |
| 70 | 77 | function filterSnippets() { |
| 71 | 78 | const search = $('#kng-cc-search').val().toLowerCase(); |
| @@ -173,8 +180,17 @@ | ||
| 173 | 180 | function handleDuplicate(e) { |
| 174 | 181 | e.preventDefault(); |
| 175 | 182 | const id = $(this).data('id'); |
| 176 | 183 | |
| 184 | + // Check limit client-side first | |
| 185 | + if (!kngCCAdmin.hasPro) { | |
| 186 | + const currentCount = $('.kng-cc-row').length; | |
| 187 | + if (currentCount >= kngCCAdmin.freeLimit) { | |
| 188 | + showProPopup(); | |
| 189 | + return; | |
| 190 | + } | |
| 191 | + } | |
| 192 | + | |
| 177 | 193 | $.ajax({ |
| 178 | 194 | url: kngCCAdmin.ajaxUrl, |
| 179 | 195 | method: 'POST', |
| 180 | 196 | data: { |
| @@ -186,9 +202,14 @@ | ||
| 186 | 202 | if (response.success) { |
| 187 | 203 | showNotice(kngCCAdmin.strings.duplicated, 'success'); |
| 188 | 204 | location.reload(); |
| 189 | 205 | } else { |
| 190 | - showNotice(response.data.message || kngCCAdmin.strings.error, 'error'); | |
| 206 | + // If server returns limit error, show popup instead of error | |
| 207 | + if (!kngCCAdmin.hasPro && response.data && (response.data.limit || (response.data.message && response.data.message.indexOf('limit') !== -1))) { | |
| 208 | + showProPopup(); | |
| 209 | + } else { | |
| 210 | + showNotice(response.data.message || kngCCAdmin.strings.error, 'error'); | |
| 211 | + } | |
| 191 | 212 | } |
| 192 | 213 | }, |
| 193 | 214 | error: function() { |
| 194 | 215 | showNotice(kngCCAdmin.strings.error, 'error'); |
| @@ -208,10 +229,29 @@ | ||
| 208 | 229 | nonce: kngCCAdmin.nonce, |
| 209 | 230 | id: id |
| 210 | 231 | }, |
| 211 | 232 | success: function(response) { |
| 212 | - if (response.success) { | |
| 213 | - downloadJSON(response.data.export, 'snippet-' + id + '.json'); | |
| 233 | + if (response.success && response.data.export.snippets && response.data.export.snippets.length) { | |
| 234 | + var snippet = response.data.export.snippets[0]; | |
| 235 | + var code = snippet.code || ''; | |
| 236 | + var type = snippet.type || 'txt'; | |
| 237 | + var title = (snippet.title || 'snippet').replace(/[^a-zA-Z0-9_-]/g, '_'); | |
| 238 | + | |
| 239 | + var extMap = { css: '.css', js: '.js', html: '.html' }; | |
| 240 | + var mimeMap = { css: 'text/css', js: 'application/javascript', html: 'text/html' }; | |
| 241 | + var ext = extMap[type] || '.txt'; | |
| 242 | + var mime = mimeMap[type] || 'text/plain'; | |
| 243 | + | |
| 244 | + var blob = new Blob([code], { type: mime }); | |
| 245 | + var url = URL.createObjectURL(blob); | |
| 246 | + var a = document.createElement('a'); | |
| 247 | + a.href = url; | |
| 248 | + a.download = title + ext; | |
| 249 | + document.body.appendChild(a); | |
| 250 | + a.click(); | |
| 251 | + document.body.removeChild(a); | |
| 252 | + URL.revokeObjectURL(url); | |
| 253 | + | |
| 214 | 254 | showNotice(kngCCAdmin.strings.exportReady, 'success'); |
| 215 | 255 | } else { |
| 216 | 256 | showNotice(response.data.message || kngCCAdmin.strings.error, 'error'); |
| 217 | 257 | } |
| @@ -449,10 +489,8 @@ | ||
| 449 | 489 | } |
| 450 | 490 | |
| 451 | 491 | function saveSnippet() { |
| 452 | 492 | const $btn = $('#kng-cc-save-btn'); |
| 453 | - const $saveIcon = $btn.find('.kng-cc-save-icon'); | |
| 454 | - const $spinner = $btn.find('.kng-cc-spinner'); | |
| 455 | 493 | const $text = $btn.find('.kng-cc-save-text'); |
| 456 | 494 | |
| 457 | 495 | // Collect form data |
| 458 | 496 | collectRulesFromForm(); |
| @@ -482,11 +520,10 @@ | ||
| 482 | 520 | $('#kng-cc-title').focus(); |
| 483 | 521 | return; |
| 484 | 522 | } |
| 485 | 523 | |
| 486 | - // Show loading | |
| 487 | - $saveIcon.hide(); | |
| 488 | - $spinner.show(); | |
| 524 | + // Show saving state | |
| 525 | + $btn.removeClass('is-saved').addClass('is-saving'); | |
| 489 | 526 | $text.text(kngCCAdmin.strings.saving); |
| 490 | 527 | $btn.prop('disabled', true); |
| 491 | 528 | |
| 492 | 529 | $.ajax({ |
| @@ -498,8 +535,10 @@ | ||
| 498 | 535 | snippet: JSON.stringify(snippet) |
| 499 | 536 | }, |
| 500 | 537 | success: function(response) { |
| 501 | 538 | if (response.success) { |
| 539 | + // Show success state: green + checkmark | |
| 540 | + $btn.removeClass('is-saving').addClass('is-saved'); | |
| 502 | 541 | $text.text(kngCCAdmin.strings.saved); |
| 503 | 542 | |
| 504 | 543 | // Update ID if new snippet |
| 505 | 544 | if (!snippet.id && response.data.id) { |
| @@ -509,14 +548,14 @@ | ||
| 509 | 548 | const newUrl = window.location.href.replace('view=new', 'view=edit&id=' + response.data.id); |
| 510 | 549 | window.history.replaceState({}, '', newUrl); |
| 511 | 550 | } |
| 512 | 551 | |
| 552 | + // Return to normal after delay | |
| 513 | 553 | setTimeout(function() { |
| 514 | - $spinner.hide(); | |
| 515 | - $saveIcon.show(); | |
| 554 | + $btn.removeClass('is-saved'); | |
| 516 | 555 | $text.text('Save Snippet'); |
| 517 | 556 | $btn.prop('disabled', false); |
| 518 | - }, 1500); | |
| 557 | + }, 2000); | |
| 519 | 558 | } else { |
| 520 | 559 | showNotice(response.data.message || kngCCAdmin.strings.error, 'error'); |
| 521 | 560 | resetSaveButton(); |
| 522 | 561 | } |
| @@ -527,10 +566,9 @@ | ||
| 527 | 566 | } |
| 528 | 567 | }); |
| 529 | 568 | |
| 530 | 569 | function resetSaveButton() { |
| 531 | - $spinner.hide(); | |
| 532 | - $saveIcon.show(); | |
| 570 | + $btn.removeClass('is-saving is-saved'); | |
| 533 | 571 | $text.text('Save Snippet'); |
| 534 | 572 | $btn.prop('disabled', false); |
| 535 | 573 | } |
| 536 | 574 | } |
| @@ -646,9 +684,9 @@ | ||
| 646 | 684 | switch (type) { |
| 647 | 685 | case 'page': |
| 648 | 686 | case 'post': |
| 649 | 687 | html = '<div class="kng-cc-rule-search-wrap">' + |
| 650 | - '<input type="text" class="kng-v3-input kng-cc-rule-search" placeholder="Search ' + type + 's..." data-type="' + type + '" />' + | |
| 688 | + '<input type="text" class="kng-v3-input kng-cc-rule-search" placeholder="Search by title or ID..." data-type="' + type + '" />' + | |
| 651 | 689 | '<input type="hidden" class="kng-cc-rule-value-input" name="rules[' + index + '][value]" value="' + (rule.value || '') + '" />' + |
| 652 | 690 | '<div class="kng-cc-rule-search-results"></div>' + |
| 653 | 691 | '</div>'; |
| 654 | 692 | break; |
| @@ -759,9 +797,9 @@ | ||
| 759 | 797 | if (response.success && response.data.length) { |
| 760 | 798 | let html = ''; |
| 761 | 799 | response.data.forEach(function(item) { |
| 762 | 800 | html += '<div class="kng-cc-search-result" data-id="' + item.id + '" data-title="' + escapeHtml(item.title) + '">' + |
| 763 | - escapeHtml(item.title) + | |
| 801 | + escapeHtml(item.title) + ' <span style="color:var(--kng-v3-muted);font-size:12px;">(ID: ' + item.id + ')</span>' + | |
| 764 | 802 | '</div>'; |
| 765 | 803 | }); |
| 766 | 804 | $results.html(html).show(); |
| 767 | 805 | } else { |
| @@ -1004,8 +1042,16 @@ | ||
| 1004 | 1042 | // ========================================================================= |
| 1005 | 1043 | // Utilities |
| 1006 | 1044 | // ========================================================================= |
| 1007 | 1045 | |
| 1046 | + function showProPopup() { | |
| 1047 | + $('#kng-cc-pro-popup').fadeIn(200); | |
| 1048 | + } | |
| 1049 | + | |
| 1050 | + function hideProPopup() { | |
| 1051 | + $('#kng-cc-pro-popup').fadeOut(150); | |
| 1052 | + } | |
| 1053 | + | |
| 1008 | 1054 | function showNotice(message, type) { |
| 1009 | 1055 | // Remove existing notices |
| 1010 | 1056 | $('.kng-cc-notice').remove(); |
| 1011 | 1057 | |
| @@ -1162,140 +1208,83 @@ | ||
| 1162 | 1208 | document.head.appendChild(style); |
| 1163 | 1209 | })(); |
| 1164 | 1210 | |
| 1165 | 1211 | // ========================================================================= |
| 1166 | -// Theme Switcher | |
| 1212 | +// Theme Switcher (ka-v3-theme-segment) | |
| 1167 | 1213 | // ========================================================================= |
| 1168 | 1214 | |
| 1169 | -(function initThemeSwitcher() { | |
| 1215 | +(function initThemeSwitcher($) { | |
| 1170 | 1216 | 'use strict'; |
| 1171 | - | |
| 1172 | - const THEME_KEY = 'kng_cc_theme'; | |
| 1173 | - const $admin = $('.kng-cc-admin'); | |
| 1174 | - | |
| 1175 | - if (!$admin.length) return; | |
| 1176 | - | |
| 1177 | - // Get saved theme or default to auto | |
| 1178 | - let currentTheme = localStorage.getItem(THEME_KEY) || 'auto'; | |
| 1179 | - | |
| 1180 | - // Apply saved theme | |
| 1181 | - applyTheme(currentTheme); | |
| 1182 | - | |
| 1183 | - // Create theme switcher if in header | |
| 1184 | - const $headerRight = $('.kng-cc-header-right'); | |
| 1185 | - if ($headerRight.length && !$('.kng-cc-theme-switcher').length) { | |
| 1186 | - const switcher = ` | |
| 1187 | - <div class="kng-cc-theme-switcher"> | |
| 1188 | - <button type="button" class="kng-cc-theme-btn" data-theme="light" title="Light Theme"> | |
| 1189 | - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
| 1190 | - <circle cx="12" cy="12" r="5"/> | |
| 1191 | - <line x1="12" y1="1" x2="12" y2="3"/> | |
| 1192 | - <line x1="12" y1="21" x2="12" y2="23"/> | |
| 1193 | - <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/> | |
| 1194 | - <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/> | |
| 1195 | - <line x1="1" y1="12" x2="3" y2="12"/> | |
| 1196 | - <line x1="21" y1="12" x2="23" y2="12"/> | |
| 1197 | - <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/> | |
| 1198 | - <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/> | |
| 1199 | - </svg> | |
| 1200 | - </button> | |
| 1201 | - <button type="button" class="kng-cc-theme-btn" data-theme="auto" title="Auto Theme"> | |
| 1202 | - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
| 1203 | - <rect x="2" y="3" width="20" height="14" rx="2" ry="2"/> | |
| 1204 | - <line x1="8" y1="21" x2="16" y2="21"/> | |
| 1205 | - <line x1="12" y1="17" x2="12" y2="21"/> | |
| 1206 | - </svg> | |
| 1207 | - </button> | |
| 1208 | - <button type="button" class="kng-cc-theme-btn" data-theme="dark" title="Dark Theme"> | |
| 1209 | - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
| 1210 | - <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/> | |
| 1211 | - </svg> | |
| 1212 | - </button> | |
| 1213 | - </div> | |
| 1214 | - `; | |
| 1215 | - $headerRight.prepend(switcher); | |
| 1216 | - | |
| 1217 | - // Add event listeners | |
| 1218 | - $('.kng-cc-theme-btn').on('click', function() { | |
| 1219 | - const theme = $(this).data('theme'); | |
| 1220 | - setTheme(theme); | |
| 1217 | + | |
| 1218 | + var $segment = $('#ka-v3-theme-segment'); | |
| 1219 | + if (!$segment.length) return; | |
| 1220 | + | |
| 1221 | + var $buttons = $segment.find('.ka-v3-segmented-btn'); | |
| 1222 | + var mql = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null; | |
| 1223 | + var mode = ($segment.attr('data-active') || 'dark').toString(); | |
| 1224 | + var mqlHandler = null; | |
| 1225 | + | |
| 1226 | + function saveUISetting(value) { | |
| 1227 | + if (!window.kngCCAdmin) return; | |
| 1228 | + $.post(kngCCAdmin.ajaxUrl, { | |
| 1229 | + action: 'king_addons_save_dashboard_ui', | |
| 1230 | + nonce: kngCCAdmin.themeNonce, | |
| 1231 | + key: 'theme_mode', | |
| 1232 | + value: value | |
| 1221 | 1233 | }); |
| 1222 | 1234 | } |
| 1223 | - | |
| 1224 | - function applyTheme(theme) { | |
| 1225 | - currentTheme = theme; | |
| 1226 | - | |
| 1227 | - // Remove all theme classes | |
| 1228 | - $admin.removeClass('kng-theme-light kng-theme-dark').removeAttr('data-theme'); | |
| 1229 | - | |
| 1230 | - // Update active button | |
| 1231 | - $('.kng-cc-theme-btn').removeClass('is-active'); | |
| 1232 | - $(`.kng-cc-theme-btn[data-theme="${theme}"]`).addClass('is-active'); | |
| 1233 | - | |
| 1234 | - if (theme === 'light') { | |
| 1235 | - $admin.attr('data-theme', 'light'); | |
| 1236 | - } else if (theme === 'dark') { | |
| 1237 | - $admin.attr('data-theme', 'dark').addClass('kng-theme-dark'); | |
| 1238 | - } | |
| 1239 | - // 'auto' - no class, uses prefers-color-scheme | |
| 1235 | + | |
| 1236 | + function updateSegment(activeMode) { | |
| 1237 | + $segment.attr('data-active', activeMode); | |
| 1238 | + $buttons.each(function() { | |
| 1239 | + var theme = ($(this).data('theme') || 'dark').toString(); | |
| 1240 | + $(this).attr('aria-pressed', theme === activeMode ? 'true' : 'false'); | |
| 1241 | + }); | |
| 1240 | 1242 | } |
| 1241 | - | |
| 1242 | - function setTheme(theme) { | |
| 1243 | - applyTheme(theme); | |
| 1244 | - localStorage.setItem(THEME_KEY, theme); | |
| 1245 | - | |
| 1246 | - // Show subtle feedback | |
| 1247 | - showThemeNotice(theme); | |
| 1243 | + | |
| 1244 | + function applyTheme(isDark) { | |
| 1245 | + $('body').toggleClass('ka-v3-dark', isDark); | |
| 1246 | + document.documentElement.classList.toggle('ka-v3-dark', isDark); | |
| 1248 | 1247 | } |
| 1249 | - | |
| 1250 | - function showThemeNotice(theme) { | |
| 1251 | - const themeNames = { | |
| 1252 | - light: 'Light Theme', | |
| 1253 | - dark: 'Dark Theme', | |
| 1254 | - auto: 'Auto Theme' | |
| 1255 | - }; | |
| 1256 | - | |
| 1257 | - const $notice = $(` | |
| 1258 | - <div class="kng-cc-theme-notice"> | |
| 1259 | - ${themeNames[theme]} activated | |
| 1260 | - </div> | |
| 1261 | - `); | |
| 1262 | - | |
| 1263 | - $('body').append($notice); | |
| 1264 | - | |
| 1265 | - setTimeout(() => { | |
| 1266 | - $notice.addClass('is-visible'); | |
| 1267 | - }, 10); | |
| 1268 | - | |
| 1269 | - setTimeout(() => { | |
| 1270 | - $notice.removeClass('is-visible'); | |
| 1271 | - setTimeout(() => $notice.remove(), 300); | |
| 1272 | - }, 2000); | |
| 1273 | - } | |
| 1274 | - | |
| 1275 | - // Add notice styles | |
| 1276 | - const style = document.createElement('style'); | |
| 1277 | - style.textContent = ` | |
| 1278 | - .kng-cc-theme-notice { | |
| 1279 | - position: fixed; | |
| 1280 | - bottom: 32px; | |
| 1281 | - right: 32px; | |
| 1282 | - background: var(--kng-v3-card-bg); | |
| 1283 | - color: var(--kng-v3-text); | |
| 1284 | - padding: 12px 20px; | |
| 1285 | - border-radius: 100px; | |
| 1286 | - font-size: 13px; | |
| 1287 | - font-weight: 500; | |
| 1288 | - box-shadow: var(--kng-v3-shadow-lg); | |
| 1289 | - opacity: 0; | |
| 1290 | - transform: translateY(20px); | |
| 1291 | - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| 1292 | - z-index: 10000; | |
| 1293 | - border: 1px solid var(--kng-v3-border); | |
| 1248 | + | |
| 1249 | + function setThemeMode(nextMode, save) { | |
| 1250 | + mode = (nextMode || 'dark').toString(); | |
| 1251 | + updateSegment(mode); | |
| 1252 | + | |
| 1253 | + if (mqlHandler && mql) { | |
| 1254 | + if (mql.removeEventListener) { | |
| 1255 | + mql.removeEventListener('change', mqlHandler); | |
| 1256 | + } else if (mql.removeListener) { | |
| 1257 | + mql.removeListener(mqlHandler); | |
| 1258 | + } | |
| 1259 | + mqlHandler = null; | |
| 1294 | 1260 | } |
| 1295 | - .kng-cc-theme-notice.is-visible { | |
| 1296 | - opacity: 1; | |
| 1297 | - transform: translateY(0); | |
| 1261 | + | |
| 1262 | + if (mode === 'auto') { | |
| 1263 | + applyTheme(!!(mql && mql.matches)); | |
| 1264 | + mqlHandler = function(e) { | |
| 1265 | + if (mode !== 'auto') return; | |
| 1266 | + applyTheme(!!e.matches); | |
| 1267 | + }; | |
| 1268 | + if (mql) { | |
| 1269 | + if (mql.addEventListener) { | |
| 1270 | + mql.addEventListener('change', mqlHandler); | |
| 1271 | + } else if (mql.addListener) { | |
| 1272 | + mql.addListener(mqlHandler); | |
| 1273 | + } | |
| 1274 | + } | |
| 1275 | + } else { | |
| 1276 | + applyTheme(mode === 'dark'); | |
| 1298 | 1277 | } |
| 1299 | - `; | |
| 1300 | - document.head.appendChild(style); | |
| 1301 | -})(); | |
| 1278 | + | |
| 1279 | + if (save) { | |
| 1280 | + saveUISetting(mode); | |
| 1281 | + } | |
| 1282 | + } | |
| 1283 | + | |
| 1284 | + $segment.on('click', '.ka-v3-segmented-btn', function(e) { | |
| 1285 | + e.preventDefault(); | |
| 1286 | + setThemeMode(($(this).data('theme') || 'dark').toString(), true); | |
| 1287 | + }); | |
| 1288 | + | |
| 1289 | + setThemeMode(mode, false); | |
| 1290 | +})(jQuery); | |