PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.64
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.64
51.1.87 51.1.86 51.1.84 51.1.85 51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 All 41 releases
king-addons / includes / extensions / alt-text-generator / alt-text-media.js

alt-text-media.js in King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder 51.1.64, at includes/extensions/alt-text-generator/alt-text-media.js

372 lines 17.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * King Addons Media Library Alt Text Generation Script
3 */
4 (function($, _, wp) {
5 'use strict';
6
7 $(document).ready(function() {
8
9 // --- Media Library List View Button Handler --- //
10 $('body').on('click', '.king-addons-generate-alt-button', function(e) {
11 e.preventDefault();
12
13 var $button = $(this);
14 var $container = $button.closest('.king-addons-alt-text-status');
15 var $spinner = $container.find('.spinner');
16 var $resultSpan = $container.find('.king-addons-alt-text-result');
17 var attachmentId = $container.data('attachment-id');
18
19 if ($button.is('.disabled')) {
20 return; // Prevent multiple clicks
21 }
22
23 if (!attachmentId) {
24 console.error('King Addons: Missing attachment ID (List View).');
25 $resultSpan.text(kingAddonsMediaAltText.error_text + ': Missing ID').css('color', 'red').show();
26 return;
27 }
28
29 $button.addClass('disabled').prop('disabled', true);
30 $spinner.css('display', 'inline-block');
31 $resultSpan.hide().text('');
32
33 $.ajax({
34 url: kingAddonsMediaAltText.ajax_url,
35 type: 'POST',
36 data: {
37 action: 'king_addons_generate_single_alt',
38 nonce: kingAddonsMediaAltText.nonce,
39 attachment_id: attachmentId
40 },
41 dataType: 'json',
42 success: function(response) {
43 if (response.success) {
44 $container.empty().append('<span>' + escapeHtml(response.data.alt_text) + '</span>');
45 } else {
46 var errorMessage = response.data && response.data.message ? response.data.message : kingAddonsMediaAltText.error_text;
47 $resultSpan.text(escapeHtml(errorMessage)).css('color', 'red').show();
48 $button.removeClass('disabled').prop('disabled', false);
49 }
50 },
51 error: function(jqXHR, textStatus, errorThrown) {
52 console.error('King Addons AJAX Error (List View):', textStatus, errorThrown, jqXHR.responseText);
53 var specificMessage = kingAddonsMediaAltText.error_text + ': ' + textStatus;
54 if (jqXHR.responseText) {
55 try {
56 var errorResponse = JSON.parse(jqXHR.responseText);
57 if (errorResponse && errorResponse.data && errorResponse.data.message) {
58 specificMessage = escapeHtml(errorResponse.data.message);
59 }
60 } catch (e) { /* Ignore */ }
61 }
62 $resultSpan.text(specificMessage).css('color', 'red').show();
63 $button.removeClass('disabled').prop('disabled', false);
64 },
65 complete: function() {
66 $spinner.hide();
67 }
68 });
69 });
70 // --- End Media Library List View Button Handler --- //
71
72 // --- Media Modal Button Injection using wp.media API --- //
73 if (typeof wp !== 'undefined' && wp.media) {
74
75 // Define our custom button view for generation
76 const KingAddonsGenerateButton = wp.media.view.Button.extend({
77 className: 'button button-primary button-big king-addons-generate-modal-button ',
78 template: _.template('<span class="spinner king-addons-modal-spinner" style="float: none; vertical-align: middle; margin-left: 5px; display: none;"></span><span class="king-addons-button-text"></span>'),
79
80 initialize: function() {
81 wp.media.view.Button.prototype.initialize.apply(this, arguments);
82 this.model = this.options.attachmentModel;
83 this.$altInput = this.options.$altInput;
84 this.bindHandlers();
85 },
86
87 render: function() {
88 wp.media.view.Button.prototype.render.apply(this, arguments);
89 this.$el.html(this.template());
90 this.$spinner = this.$el.find('.king-addons-modal-spinner');
91 this.$buttonTextSpan = this.$el.find('.king-addons-button-text');
92 this.$buttonTextSpan.text(kingAddonsMediaAltText.generating_text ? kingAddonsMediaAltText.generating_text.replace('...', ' with AI') : 'Generate with AI');
93 return this;
94 },
95
96 bindHandlers: function() {
97 // Check if alt text exists initially and hide button if it does
98 if (this.model && this.model.get('alt')) {
99 this.$el.hide();
100 }
101 // Listen for changes on the alt text model property
102 if (this.model) {
103 this.listenTo(this.model, 'change:alt', this.toggleVisibility);
104 }
105 // Also listen to changes directly on the input field
106 if (this.$altInput) {
107 this.$altInput.on('input change', _.debounce(this.toggleVisibilityBasedOnInput.bind(this), 300));
108 }
109 },
110
111 // Hide button if alt text is added
112 toggleVisibility: function() {
113 if (this.model && this.model.get('alt')) {
114 this.$el.fadeOut();
115 } else {
116 this.$el.fadeIn();
117 }
118 },
119
120 toggleVisibilityBasedOnInput: function() {
121 if (this.$altInput && this.$altInput.val()) {
122 this.$el.fadeOut();
123 } else if (!this.model || !this.model.get('alt')) {
124 this.$el.fadeIn();
125 }
126 },
127
128 // Handle the button click: Perform AJAX request
129 click: function(e) {
130 e.preventDefault();
131 var attachmentId = this.model.id;
132
133 if (!attachmentId || !this.$altInput || this.$el.is('.disabled')) {
134 console.error('King Addons: Missing data for modal generation.', { id: attachmentId, input: this.$altInput });
135 return;
136 }
137
138 // Find the status span, which is now external
139 this.$statusSpan = this.$el.next('.king-addons-modal-status');
140 if (!this.$statusSpan.length) {
141 console.error("King Addons: Could not find external status span next to button.");
142 return;
143 }
144
145 // Show loading state
146 this.$el.addClass('disabled').prop('disabled', true);
147 this.$spinner.css('display', 'inline-block').addClass('is-active');
148 this.$buttonTextSpan.css('display', 'none');
149 this.$statusSpan.text('').css('color', '');
150
151 var self = this;
152
153 $.ajax({
154 url: kingAddonsMediaAltText.ajax_url,
155 type: 'POST',
156 data: {
157 action: 'king_addons_generate_single_alt',
158 nonce: kingAddonsMediaAltText.nonce,
159 attachment_id: attachmentId
160 },
161 dataType: 'json',
162 success: function(response) {
163 if (response.success && response.data.alt_text) {
164 // Update the input value
165 self.$altInput.val(response.data.alt_text).trigger('change');
166 // Update the model as well
167 self.model.set('alt', response.data.alt_text);
168
169 if (self.$statusSpan) self.$statusSpan.text('Generated!').css('color', 'green');
170 setTimeout(function() {
171 if (self.$statusSpan) self.$statusSpan.text('');
172 }, 3000);
173 } else {
174 var errorMessage = response.data && response.data.message ? response.data.message : kingAddonsMediaAltText.error_text;
175 if (self.$statusSpan) self.$statusSpan.text(escapeHtml(errorMessage)).css('color', 'red');
176 if (self.$el) self.$el.removeClass('disabled').prop('disabled', false);
177 if (self.$spinner) self.$spinner.css('display', 'none').removeClass('is-active');
178 if (self.$buttonTextSpan) self.$buttonTextSpan.css('display', 'inline');
179 }
180 },
181 error: function(jqXHR, textStatus, errorThrown) {
182 console.error('King Addons Modal Generate AJAX Error:', textStatus, errorThrown, jqXHR.responseText);
183 var specificMessage = kingAddonsMediaAltText.error_text;
184 if (jqXHR.responseText) {
185 try {
186 var errorResponse = JSON.parse(jqXHR.responseText);
187 if (errorResponse && errorResponse.data && errorResponse.data.message) {
188 specificMessage = escapeHtml(errorResponse.data.message);
189 }
190 } catch (e) { /* Ignore */ }
191 }
192 if (self.$statusSpan) self.$statusSpan.text(specificMessage + ' (' + textStatus + ')').css('color', 'red');
193 if (self.$el) self.$el.removeClass('disabled').prop('disabled', false);
194 if (self.$spinner) self.$spinner.css('display', 'none').removeClass('is-active');
195 if (self.$buttonTextSpan) self.$buttonTextSpan.css('display', 'inline');
196 },
197 complete: function() {
198 if (self.$spinner && self.$spinner.is(':visible')) {
199 self.$spinner.css('display', 'none').removeClass('is-active');
200 }
201 }
202 });
203 }
204 });
205
206 // Define our custom settings link view for when API key is missing
207 const KingAddonsSettingsLink = wp.media.view.Button.extend({
208 className: 'button button-primary button-big king-addons-settings-modal-link',
209 tagName: 'a',
210
211 initialize: function() {
212 wp.media.view.Button.prototype.initialize.apply(this, arguments);
213 this.model = this.options.attachmentModel;
214 this.$altInput = this.options.$altInput;
215 this.bindHandlers();
216 },
217
218 render: function() {
219 wp.media.view.Button.prototype.render.apply(this, arguments);
220 this.$el.text('Generate with AI').attr({
221 'href': kingAddonsMediaAltText.settings_url,
222 'target': '_blank',
223 'rel': 'noopener noreferrer'
224 });
225 return this;
226 },
227
228 bindHandlers: function() {
229 // Check if alt text exists initially and hide button if it does
230 if (this.model && this.model.get('alt')) {
231 this.$el.hide();
232 }
233 // Listen for changes on the alt text model property
234 if (this.model) {
235 this.listenTo(this.model, 'change:alt', this.toggleVisibility);
236 }
237 // Also listen to changes directly on the input field
238 if (this.$altInput) {
239 this.$altInput.on('input change', _.debounce(this.toggleVisibilityBasedOnInput.bind(this), 300));
240 }
241 },
242
243 // Hide button if alt text is added
244 toggleVisibility: function() {
245 if (this.model && this.model.get('alt')) {
246 this.$el.fadeOut();
247 } else {
248 this.$el.fadeIn();
249 }
250 },
251
252 toggleVisibilityBasedOnInput: function() {
253 if (this.$altInput && this.$altInput.val()) {
254 this.$el.fadeOut();
255 } else if (!this.model || !this.model.get('alt')) {
256 this.$el.fadeIn();
257 }
258 },
259
260 // Handle clicks (default link behavior)
261 click: function(e) {
262 // Let the default link behavior handle this
263 // The link will open the settings page in a new tab
264 }
265 });
266
267 // Hook into the media frame rendering
268 wp.media.events.on('editor:render', function(editor) {
269 attachButtonToSidebar(editor);
270 });
271
272 // Need to handle different ways the sidebar might render/update
273 var originalSidebar = wp.media.view.Attachment.Details.TwoColumn;
274 if (originalSidebar) {
275 wp.media.view.Attachment.Details.TwoColumn = originalSidebar.extend({
276 render: function() {
277 originalSidebar.prototype.render.apply(this, arguments);
278 attachButtonLogic(this);
279 }
280 });
281 }
282 var originalDetails = wp.media.view.Attachment.Details;
283 if (originalDetails) {
284 wp.media.view.Attachment.Details = originalDetails.extend({
285 render: function() {
286 originalDetails.prototype.render.apply(this, arguments);
287 attachButtonLogic(this);
288 }
289 });
290 }
291
292 // Function to find the alt text field and inject the button
293 function attachButtonLogic(viewInstance) {
294 var attachmentModel = viewInstance.model;
295 if (!attachmentModel) return;
296
297 var $altSetting = viewInstance.$el.find('.setting[data-setting="alt"]');
298 if (!$altSetting.length) return;
299
300 var $altDesc = viewInstance.$el.find('#alt-text-description');
301 if (!$altDesc.length) return;
302
303 var $altTextarea = $altSetting.find('textarea');
304 if (!$altTextarea.length) return;
305
306 // Prevent duplicate injection for this specific view instance
307 if ($altSetting.data('king-addons-button-added')) return;
308 $altSetting.data('king-addons-button-added', true);
309
310 // Create status span separately (only needed for generation)
311 var $statusSpan = $('<span class="king-addons-modal-status" style="margin-left: 10px; vertical-align: baseline;"></span>');
312
313 // Choose which button to show based on API key availability
314 var buttonInstance;
315 if (kingAddonsMediaAltText.has_api_key) {
316 // Instantiate and render the generation button
317 buttonInstance = new KingAddonsGenerateButton({
318 controller: viewInstance.controller,
319 model: viewInstance.model,
320 attachmentModel: attachmentModel,
321 $altInput: $altTextarea
322 }).render();
323 } else {
324 // Instantiate and render the settings link
325 buttonInstance = new KingAddonsSettingsLink({
326 controller: viewInstance.controller,
327 model: viewInstance.model,
328 attachmentModel: attachmentModel,
329 $altInput: $altTextarea
330 }).render();
331 }
332
333 // Wrap button and status span in a container div with a unique class
334 const $containerDiv = $('<div class="king-addons-modal-alt-gen-container"></div>');
335 $containerDiv.append(buttonInstance.$el);
336
337 // Only add status span for generation button
338 if (kingAddonsMediaAltText.has_api_key) {
339 $containerDiv.append($statusSpan);
340 }
341
342 // Insert the container after the textarea
343 $altDesc.prepend($containerDiv);
344 }
345
346 // Helper for editor:render event
347 function attachButtonToSidebar(sidebarView) {
348 if (sidebarView && sidebarView.details) {
349 attachButtonLogic(sidebarView.details);
350 }
351 }
352
353 } else {
354 console.error("King Addons: wp.media object not found.");
355 }
356 // --- End Media Modal Button Injection --- //
357
358 });
359
360 // Basic HTML escaping function
361 function escapeHtml(unsafe) {
362 if (!unsafe) return '';
363 return unsafe
364 .replace(/&/g, "&amp;")
365 .replace(/</g, "&lt;")
366 .replace(/>/g, "&gt;")
367 .replace(/"/g, "&quot;")
368 .replace(/'/g, "&#039;");
369 }
370
371 })(jQuery, _, wp);
372