PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.13
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.13
2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 2.6.4 2.6.3 2.5.23 All 138 releases
metasync / admin / js / metasync-opengraph.js

metasync-opengraph.js in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.13, at admin/js/metasync-opengraph.js

604 lines 19.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * MetaSync Open Graph Admin JavaScript
3 *
4 * @package MetaSync
5 * @subpackage MetaSync/admin/js
6 * @since 1.0.0
7 */
8
9 (function ($) {
10 'use strict';
11
12 // Check if required dependencies are available
13 if (typeof $ === 'undefined') {
14 return;
15 }
16
17 if (typeof metasync_og === 'undefined') {
18 return;
19 }
20
21 /**
22 * Open Graph Meta Box functionality
23 */
24 var MetaSyncOpenGraph = {
25
26 initialized: false,
27
28 /**
29 * Initialize the functionality
30 */
31 init: function () {
32 if (this.initialized) {
33 return;
34 }
35
36 this.bindEvents();
37 this.updateCharacterCounts();
38 this.generateInitialPreview();
39 this.handleUrlFieldState();
40 this.updateUrlFieldOnStatusChange();
41 this.handleUsePermalinkClick();
42 this.initializeTwitterCardSections();
43 this.initialized = true;
44 },
45
46 /**
47 * Bind event handlers
48 */
49 bindEvents: function () {
50 var self = this;
51
52 // Toggle Open Graph section
53 $(document).on('change', '.metasync-og-toggle', function () {
54 var $content = $('.metasync-og-content');
55 if ($(this).is(':checked')) {
56 $content.slideDown();
57 self.generatePreview();
58 } else {
59 $content.slideUp();
60 }
61 });
62
63 // Toggle Twitter Card sections based on card type
64 $(document).on('change', '#metasync_twitter_card', function () {
65 var cardType = $(this).val();
66 var $appSection = $('.metasync-twitter-app-section');
67 var $playerSection = $('.metasync-twitter-player-section');
68
69 // Hide all sections first
70 // $appSection.hide();
71 // $playerSection.hide();
72 // Hide all sections first and disable their inputs
73 $appSection.hide().find('input, select, textarea').prop('disabled', true);
74 $playerSection.hide().find('input, select, textarea').prop('disabled', true);
75 // Show relevant section based on card type
76 if (cardType === 'app') {
77 // $appSection.slideDown();
78 $appSection.slideDown().find('input, select, textarea').prop('disabled', false);
79 } else if (cardType === 'player') {
80 // $playerSection.slideDown();
81 $playerSection.slideDown().find('input, select, textarea').prop('disabled', false);
82 }
83 });
84
85 // Preview tab switching
86 $(document).on('click', '.metasync-preview-tab', function (e) {
87 e.preventDefault();
88 var platform = $(this).data('platform');
89
90 // Update tabs
91 $('.metasync-preview-tab').removeClass('active');
92 $(this).addClass('active');
93
94 // Update panels
95 $('.metasync-preview-panel').removeClass('active');
96 $('.metasync-preview-panel[data-platform="' + platform + '"]').addClass('active');
97
98 });
99
100 // Real-time updates as user types (shorter debounce)
101 $(document).on('input keyup paste', '.metasync-og-input', function () {
102 self.updateCharacterCount($(this));
103 self.updatePreviewInstantly($(this));
104 });
105
106 // Image upload for Open Graph
107 $(document).on('click', '.metasync-upload-image', function (e) {
108 e.preventDefault();
109 self.openMediaUploader($(this), '#metasync_og_image', '.metasync-image-preview', '.metasync-remove-image');
110 });
111
112 // Image upload for Twitter
113 $(document).on('click', '.metasync-upload-twitter-image', function (e) {
114 e.preventDefault();
115 self.openMediaUploader($(this), '#metasync_twitter_image', '.metasync-twitter-image-preview', '.metasync-remove-twitter-image');
116 });
117
118 // Remove Open Graph image
119 $(document).on('click', '.metasync-remove-image', function (e) {
120 e.preventDefault();
121 self.removeImage('#metasync_og_image', '.metasync-image-preview', '.metasync-remove-image');
122 });
123
124 // Remove Twitter image
125 $(document).on('click', '.metasync-remove-twitter-image', function (e) {
126 e.preventDefault();
127 self.removeImage('#metasync_twitter_image', '.metasync-twitter-image-preview', '.metasync-remove-twitter-image');
128 });
129
130 // Manual preview refresh
131 $(document).on('click', '.metasync-refresh-preview', function (e) {
132 e.preventDefault();
133 self.generatePreview();
134 });
135
136 // Auto-sync Twitter fields with Open Graph
137 $(document).on('input', '#metasync_og_title', function () {
138 var twitterTitle = $('#metasync_twitter_title');
139 if (twitterTitle.val() === '' || twitterTitle.data('auto-synced')) {
140 twitterTitle.val($(this).val()).data('auto-synced', true);
141 self.updateCharacterCount(twitterTitle);
142 }
143 });
144
145 $(document).on('input', '#metasync_og_description', function () {
146 var twitterDesc = $('#metasync_twitter_description');
147 if (twitterDesc.val() === '' || twitterDesc.data('auto-synced')) {
148 twitterDesc.val($(this).val()).data('auto-synced', true);
149 self.updateCharacterCount(twitterDesc);
150 }
151 });
152
153 $(document).on('input', '#metasync_og_image', function () {
154 var twitterImage = $('#metasync_twitter_image');
155 if (twitterImage.val() === '' || twitterImage.data('auto-synced')) {
156 twitterImage.val($(this).val()).data('auto-synced', true);
157 self.updateTwitterImagePreview();
158 }
159 });
160
161 // Manual input breaks auto-sync
162 $(document).on('input', '#metasync_twitter_title, #metasync_twitter_description, #metasync_twitter_image', function () {
163 $(this).data('auto-synced', false);
164 });
165 },
166
167 /**
168 * Update character count for an input
169 */
170 updateCharacterCount: function ($input) {
171 var maxLength = parseInt($input.attr('maxlength')) || 0;
172 var currentLength = $input.val().length;
173 var $counter = $('.metasync-char-count[data-target="' + $input.attr('id') + '"]');
174
175 if ($counter.length && maxLength > 0) {
176 $counter.text(currentLength + '/' + maxLength);
177
178 // Add warning class if approaching limit
179 if (currentLength > maxLength * 0.9) {
180 $counter.addClass('metasync-char-warning');
181 } else {
182 $counter.removeClass('metasync-char-warning');
183 }
184 }
185 },
186
187 /**
188 * Update all character counts
189 */
190 updateCharacterCounts: function () {
191 var self = this;
192 $('.metasync-og-input[maxlength]').each(function () {
193 self.updateCharacterCount($(this));
194 });
195 },
196
197 /**
198 * Open WordPress media uploader
199 */
200 openMediaUploader: function ($button, inputSelector, previewSelector, removeButtonSelector) {
201 var self = this;
202
203
204 // Check if wp.media is available
205 if (typeof wp === 'undefined' || typeof wp.media === 'undefined') {
206 alert('WordPress media library is not available. Please refresh the page.');
207 return;
208 }
209
210 // Create media frame
211 var frame = wp.media({
212 title: metasync_og.strings.select_image,
213 button: {
214 text: metasync_og.strings.use_image
215 },
216 multiple: false,
217 library: {
218 type: 'image'
219 }
220 });
221
222 // Handle image selection
223 frame.on('select', function () {
224 var attachment = frame.state().get('selection').first().toJSON();
225 var imageUrl = attachment.sizes.large ? attachment.sizes.large.url : attachment.url;
226
227 $(inputSelector).val(imageUrl);
228 $(previewSelector + ' img').attr('src', imageUrl);
229 $(previewSelector).show();
230 $(removeButtonSelector).show();
231
232 // Update preview instantly
233 if (inputSelector === '#metasync_og_image') {
234 self.updatePreviewImages(imageUrl);
235 } else if (inputSelector === '#metasync_twitter_image') {
236 self.updateTwitterPreviewImage(imageUrl);
237 }
238
239 self.debouncePreview();
240 });
241
242 // Open the frame
243 frame.open();
244 },
245
246 /**
247 * Remove image
248 */
249 removeImage: function (inputSelector, previewSelector, removeButtonSelector) {
250 $(inputSelector).val('');
251 $(previewSelector).hide();
252 $(removeButtonSelector).hide();
253
254 // Update preview instantly
255 if (inputSelector === '#metasync_og_image') {
256 this.updatePreviewImages('');
257 } else if (inputSelector === '#metasync_twitter_image') {
258 this.updateTwitterPreviewImage($('#metasync_og_image').val() || '');
259 }
260
261 this.debouncePreview();
262 },
263
264 /**
265 * Update Twitter image preview
266 */
267 updateTwitterImagePreview: function () {
268 var imageUrl = $('#metasync_twitter_image').val();
269 var $preview = $('.metasync-twitter-image-preview');
270 var $removeBtn = $('.metasync-remove-twitter-image');
271
272 if (imageUrl) {
273 $preview.find('img').attr('src', imageUrl);
274 $preview.show();
275 $removeBtn.show();
276 } else {
277 $preview.hide();
278 $removeBtn.hide();
279 }
280 },
281
282 /**
283 * Update preview instantly without AJAX for better UX
284 */
285 updatePreviewInstantly: function ($input) {
286 if (!$('.metasync-og-toggle').is(':checked')) {
287 return;
288 }
289
290 var inputId = $input.attr('id');
291 var value = $input.val();
292
293 // Update preview content immediately based on input
294 switch(inputId) {
295 case 'metasync_og_title':
296 $('.facebook-preview-title, .linkedin-preview-title').text(value || 'Your Post Title');
297 // Only update Twitter title if it's auto-synced
298 if (!$('#metasync_twitter_title').val() || $('#metasync_twitter_title').data('auto-synced')) {
299 $('.twitter-card-title').text(value || 'Your Post Title');
300 }
301 break;
302 case 'metasync_og_description':
303 $('.facebook-preview-description, .linkedin-preview-description').text(value || 'Your post description will appear here when shared on social media platforms.');
304 // Only update Twitter description if it's auto-synced
305 if (!$('#metasync_twitter_description').val() || $('#metasync_twitter_description').data('auto-synced')) {
306 $('.twitter-card-description').text(value || 'Your post description will appear here when shared on social media platforms.');
307 }
308 break;
309 case 'metasync_og_image':
310 this.updatePreviewImages(value);
311 break;
312 case 'metasync_og_url':
313 var domain = this.extractDomain(value);
314 $('.facebook-preview-domain').text(domain.toUpperCase());
315 $('.twitter-card-domain, .linkedin-preview-domain').text(domain);
316 break;
317 case 'metasync_twitter_title':
318 $('.twitter-card-title').text(value || $('#metasync_og_title').val() || 'Your Post Title');
319 break;
320 case 'metasync_twitter_description':
321 $('.twitter-card-description').text(value || $('#metasync_og_description').val() || 'Your post description will appear here when shared on social media platforms.');
322 break;
323 case 'metasync_twitter_image':
324 this.updateTwitterPreviewImage(value || $('#metasync_og_image').val());
325 break;
326 }
327
328 // Also debounce the full AJAX update
329 this.debouncePreview();
330 },
331
332 /**
333 * Extract domain from URL
334 */
335 extractDomain: function (url) {
336 if (!url) {
337 return window.location.hostname || 'your-site.com';
338 }
339
340 try {
341 var a = document.createElement('a');
342 a.href = url;
343 return a.hostname || 'your-site.com';
344 } catch(e) {
345 return 'your-site.com';
346 }
347 },
348
349 /**
350 * Update preview images instantly
351 */
352 updatePreviewImages: function (imageUrl) {
353 var $fbLinkedin = $('.facebook-preview-image, .linkedin-preview-image');
354 var imgHtml = imageUrl
355 ? '<img src="' + imageUrl + '" alt="Preview" onerror="this.parentElement.classList.add(\'preview-no-image\'); this.style.display=\'none\'; this.parentElement.innerHTML=\'<div class=\\\"preview-placeholder\\\"><span>📷</span><p>Image failed to load</p></div>\';">'
356 : '<div class="preview-placeholder"><span>📷</span><p>No image selected</p></div>';
357
358 $fbLinkedin.each(function () {
359 $(this).toggleClass('preview-no-image', !imageUrl).html(imgHtml);
360 });
361
362 // Only update Twitter if it has no dedicated image
363 var twitterImage = $('#metasync_twitter_image').val();
364 if (!twitterImage || $('#metasync_twitter_image').data('auto-synced')) {
365 this.updateTwitterPreviewImage(imageUrl);
366 }
367 },
368
369 /**
370 * Update Twitter preview image independently
371 */
372 updateTwitterPreviewImage: function (imageUrl) {
373 var $twitter = $('.twitter-card-image');
374 if (imageUrl) {
375 $twitter.removeClass('preview-no-image').html(
376 '<img src="' + imageUrl + '" alt="Preview" onerror="this.parentElement.classList.add(\'preview-no-image\'); this.style.display=\'none\'; this.parentElement.innerHTML=\'<div class=\\\"preview-placeholder\\\"><span>📷</span><p>Image failed to load</p></div>\';">'
377 );
378 } else {
379 $twitter.addClass('preview-no-image').html(
380 '<div class="preview-placeholder"><span>📷</span><p>No image selected</p></div>'
381 );
382 }
383 },
384
385 /**
386 * Debounced preview generation
387 */
388 debouncePreview: function () {
389 var self = this;
390 clearTimeout(this.previewTimeout);
391 this.previewTimeout = setTimeout(function () {
392 self.generatePreview();
393 }, 1000); // Longer timeout since we have instant updates
394 },
395
396 /**
397 * Generate initial preview on page load
398 */
399 generateInitialPreview: function () {
400 // Only generate if toggle is checked and we don't already have a preview
401 if ($('.metasync-og-toggle').is(':checked')) {
402 var $content = $('.metasync-preview-content-wrapper');
403 if ($content.is(':empty') || $content.find('.metasync-preview-tabs').length === 0) {
404 this.generatePreview();
405 }
406 }
407 },
408
409 /**
410 * Generate social media preview
411 */
412 generatePreview: function () {
413 if (!$('.metasync-og-toggle').is(':checked')) {
414 return;
415 }
416
417 var $container = $('.metasync-preview-content-wrapper');
418 var $loading = $('.metasync-preview-loading');
419
420 // Get current values
421 var title = $('#metasync_og_title').val() || $('#title').val() || $('h1.wp-heading-inline').text() || '';
422 var description = $('#metasync_og_description').val() || '';
423 var image = $('#metasync_og_image').val() || '';
424 var url = $('#metasync_og_url').val() || (typeof metasync_og !== 'undefined' && metasync_og.current_permalink) || window.location.href;
425
426 // Get Twitter Card data
427 var twitter_title = $('#metasync_twitter_title').val() || '';
428 var twitter_description = $('#metasync_twitter_description').val() || '';
429 var twitter_image = $('#metasync_twitter_image').val() || '';
430
431 // Show loading state
432 $loading.show();
433 $container.hide();
434
435 // Make AJAX request
436 $.ajax({
437 url: metasync_og.ajax_url,
438 type: 'POST',
439 data: {
440 action: 'metasync_og_preview',
441 nonce: metasync_og.nonce,
442 title: title,
443 description: description,
444 image: image,
445 url: url,
446 twitter_title: twitter_title,
447 twitter_description: twitter_description,
448 twitter_image: twitter_image
449 },
450 success: function (response) {
451 if (response.success) {
452 $container.empty().show();
453 var parser = new DOMParser();
454 var doc = parser.parseFromString(response.data.preview || '', 'text/html');
455 while (doc.body.firstChild) {
456 $container[0].appendChild(doc.body.firstChild);
457 }
458 } else {
459 $container.empty().append(
460 $('<p>').addClass('metasync-error').text('Failed to generate preview: ' + (response.data || 'Unknown error'))
461 ).show();
462 }
463 },
464 error: function (xhr, status, error) {
465 $container.empty().append(
466 $('<p>').addClass('metasync-error').text('AJAX Error: ' + error)
467 ).show();
468 },
469 complete: function () {
470 $loading.hide();
471 }
472 });
473 },
474
475 /**
476 * Handle URL field state based on post status
477 */
478 handleUrlFieldState: function () {
479 var $urlField = $('#metasync_og_url');
480 var $urlNotice = $('.metasync-url-disabled-notice');
481 var $usePermalinkBtn = $('.metasync-use-permalink');
482
483 if ($urlField.length) {
484 // Check if this is a new post (auto-draft only)
485 var isNewPost = $urlField.prop('disabled');
486
487 if (isNewPost) {
488 // For new posts (auto-draft), disable the field and show notice
489 $urlField.prop('disabled', true);
490 if ($urlNotice.length === 0) {
491 $urlField.after('<p class="description metasync-url-disabled-notice"><span class="dashicons dashicons-info"></span>This field will be automatically populated with the post permalink after you save the post for the first time.</p>');
492 }
493 $usePermalinkBtn.hide();
494 } else {
495 // For existing posts (draft, published, etc.), enable the field and show permalink button
496 $urlField.prop('disabled', false);
497 $urlNotice.remove();
498 $usePermalinkBtn.show();
499 }
500 }
501 },
502
503 /**
504 * Update URL field when post status changes
505 */
506 updateUrlFieldOnStatusChange: function () {
507 var self = this;
508
509 // Listen for post status changes (when user clicks Save Draft, Publish, etc.)
510 $(document).on('click', '#publish, #save-post, #save-post-ajax', function () {
511 // Small delay to allow WordPress to process the status change
512 setTimeout(function () {
513 self.handleUrlFieldState();
514 }, 500);
515 });
516
517 // Also listen for form submission
518 $(document).on('submit', '#post', function () {
519 setTimeout(function () {
520 self.handleUrlFieldState();
521 }, 1000);
522 });
523 },
524
525 /**
526 * Handle "Use Post Permalink" button click
527 */
528 handleUsePermalinkClick: function () {
529 var self = this;
530
531 $(document).on('click', '.metasync-use-permalink', function (e) {
532 e.preventDefault();
533
534 // Get the current permalink from the placeholder or generate it
535 var $urlField = $('#metasync_og_url');
536 var currentPermalink = $urlField.attr('placeholder');
537
538 // If no placeholder, try to get it from the localized data
539 if (!currentPermalink && typeof metasync_og !== 'undefined' && metasync_og.current_permalink) {
540 currentPermalink = metasync_og.current_permalink;
541 }
542
543 if (currentPermalink) {
544 $urlField.val(currentPermalink);
545 // Trigger change event to update preview
546 $urlField.trigger('change');
547 }
548 });
549 },
550
551 /**
552 * Initialize Twitter Card sections based on current card type
553 */
554 initializeTwitterCardSections: function () {
555 var cardType = $('#metasync_twitter_card').val();
556 var $appSection = $('.metasync-twitter-app-section');
557 var $playerSection = $('.metasync-twitter-player-section');
558
559 // Hide all sections first
560 // $appSection.hide();
561 // $playerSection.hide();
562 // Hide all sections first and disable their inputs
563 $appSection.hide().find('input, select, textarea').prop('disabled', true);
564 $playerSection.hide().find('input, select, textarea').prop('disabled', true);
565 // Show relevant section based on current card type
566 if (cardType === 'app') {
567 $appSection.show();
568 $appSection.show().find('input, select, textarea').prop('disabled', false);
569 } else if (cardType === 'player') {
570 // $playerSection.show();
571 $playerSection.show().find('input, select, textarea').prop('disabled', false);
572 }
573 }
574
575
576 };
577
578
579 /**
580 * Initialize when document is ready
581 */
582 $(document).ready(function () {
583 if ($('.metasync-opengraph-meta-box').length) {
584 MetaSyncOpenGraph.init();
585 } else {
586 // Try again after a delay in case the meta box is loaded dynamically
587 setTimeout(function () {
588 if ($('.metasync-opengraph-meta-box').length) {
589 MetaSyncOpenGraph.init();
590 } else {
591 }
592 }, 1000);
593 }
594 });
595
596 // Also try to initialize on window load as a fallback
597 $(window).on('load', function () {
598 if ($('.metasync-opengraph-meta-box').length && !MetaSyncOpenGraph.initialized) {
599 MetaSyncOpenGraph.init();
600 }
601 });
602
603 })(jQuery);
604