PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13
1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 1.7.0.9 All 211 releases
charitable / assets / js / campaign-builder / fields / html.js

html.js in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.13, at assets/js/campaign-builder/fields/html.js

418 lines 15.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* global charitable_builder, wpchar, jconfirm, charitable_panel_switch, Choices, Charitable, CharitableCampaignEmbedWizard, wpCookies, tinyMCE, CharitableUtils, List */
2
3 var CharitableCampaignBuilderFieldHTML = window.CharitableCampaignBuilderFieldHTML || (function (document, window, $) {
4
5 var $builder,
6 cm_editor = false,
7 cm_field_ids = [];
8
9 var app = {
10
11 settings: {
12 load_html_option: false,
13 },
14
15 state: {
16 editors: {},
17 addEditor: function (id, editor) {
18 this.editors[id] = editor;
19 },
20 getEditor: function (id) {
21 return this.editors[id] || null;
22 },
23 removeEditor: function (id) {
24 if (this.editors[id]) {
25 delete this.editors[id];
26 }
27 }
28 },
29
30 /**
31 * Start the engine.
32 *
33 * @since 1.0.0
34 */
35 init: function () {
36 wpchar.debug('init', 'field-html-js');
37
38 var that = this;
39 charitable_panel_switch = true;
40 s = this.settings;
41
42 // Add new handlers
43 this.handleResize();
44 this.setupCleanup();
45
46 // Document ready
47 $(app.ready);
48
49 // Page load
50 $(window).on('load', function () {
51 if (typeof $.ready.then === 'function') {
52 $.ready.then(app.load);
53 } else {
54 app.load();
55 }
56 });
57
58 // Initialize error handling
59 this.initErrorHandling();
60 },
61
62 initErrorHandling: function () {
63 window.onerror = (msg, url, lineNo, columnNo, error) => {
64 if (msg.indexOf('CodeMirror') !== -1) {
65 console.error('CodeMirror Error:', {
66 message: msg,
67 url: url,
68 lineNo: lineNo,
69 columnNo: columnNo,
70 error: error
71 });
72 return true; // Prevents the error from bubbling up
73 }
74 return false; // Let other errors bubble up
75 };
76 },
77
78 recoverEditor: function (edit_field_id) {
79 const editor = this.state.getEditor(edit_field_id);
80 if (!editor || !editor.codemirror) {
81 return;
82 }
83
84 try {
85 // Attempt to recover editor state
86 editor.codemirror.refresh();
87
88 // If editor is hidden, try to make it visible
89 const wrapper = editor.codemirror.getWrapperElement();
90 if (wrapper && wrapper.style.display === 'none') {
91 wrapper.style.display = '';
92 editor.codemirror.refresh();
93 }
94 } catch (error) {
95 console.error('Editor recovery failed:', error);
96 this.destroy(edit_field_id);
97 this.codemirrorInit(edit_field_id);
98 }
99 },
100
101 initAutoSave: function (edit_field_id) {
102 const editor = this.state.getEditor(edit_field_id);
103 if (!editor || !editor.codemirror) {
104 return;
105 }
106
107 let saveTimeout;
108 editor.codemirror.on('change', () => {
109 clearTimeout(saveTimeout);
110 saveTimeout = setTimeout(() => {
111 const content = editor.codemirror.getValue();
112 this.saveEditorContent(edit_field_id, content);
113 }, 1000);
114 });
115 },
116
117 saveEditorContent: function (edit_field_id, content) {
118 const field_object = $('#charitable-panel-field-settings-field_html_html_' + edit_field_id);
119 if (field_object.length) {
120 field_object.val(content).trigger('change');
121 wpchar.debug('Content saved for editor: ' + edit_field_id, 'field-html-js');
122 }
123 },
124
125 cleanupMemory: function () {
126 // Clear any unused editor instances
127 Object.keys(this.state.editors).forEach(id => {
128 const field_object = $('#charitable-panel-field-settings-field_html_html_' + id);
129 if (!field_object.length) {
130 this.destroy(id);
131 }
132 });
133
134 // Clear any orphaned CodeMirror instances
135 $('.CodeMirror').each((index, element) => {
136 const $element = $(element);
137 const $textarea = $element.next('textarea');
138 if (!$textarea.length) {
139 $element.remove();
140 }
141 });
142 },
143
144 initPeriodicCleanup: function () {
145 setInterval(() => {
146 this.cleanupMemory();
147 }, 300000); // Run every 5 minutes
148 },
149
150 // Updated ready function with all new functionality integrated
151 ready: function () {
152 wpchar.debug('ready', 'field-html-js');
153
154 $builder = $('#charitable-builder');
155
156 // Initialize periodic cleanup
157 app.initPeriodicCleanup();
158
159 $builder.on('charitableFieldAddHTML', function (event, field_id, field_type) {
160
161 wpchar.debug('Field added: ' + field_id, 'field-html-js');
162 wpchar.debug('Field type: ' + field_type, 'field-html-js');
163
164 if (field_type !== 'html') {
165 wpchar.debug('Filed type is not HTML: ' + field_type, 'field-html-js');
166 return;
167 }
168
169 if (parseInt(field_id) > 0) {
170 wpchar.debug('Field ID is valid: ' + field_id, 'field-html-js');
171 // Check if editor already exists
172 const existingEditor = app.state.getEditor(field_id);
173 if (existingEditor) {
174 wpchar.debug('Recovering existing codemirror at ' + field_id, 'field-html-js');
175 // app.recoverEditor(field_id);
176 } else if (cm_field_ids.indexOf(field_id) === -1) {
177 wpchar.debug('Initializing new codemirror at ' + field_id, 'field-html-js');
178 app.codemirrorInit(field_id);
179 app.initAutoSave(field_id);
180 }
181 }
182 });
183
184 $builder.on('charitableFieldEdit', function (event, type, section, edit_field_id, field_id, field_type) {
185 if (field_type !== 'html') {
186 return;
187 }
188
189 if (edit_field_id == '' && field_id !== '') {
190 edit_field_id = field_id;
191 }
192
193 if (parseInt(edit_field_id) > 0) {
194 // Check if editor already exists
195 const existingEditor = app.state.getEditor(edit_field_id);
196 if (existingEditor) {
197 app.recoverEditor(edit_field_id);
198 } else if (cm_field_ids.indexOf(edit_field_id) === -1) {
199 wpchar.debug('Initializing new codemirror at ' + edit_field_id, 'field-html-js');
200 app.codemirrorInit(edit_field_id);
201 app.initAutoSave(edit_field_id);
202 }
203 }
204 });
205
206 // Add handler for field deletion
207 $builder.on('charitableFieldDelete', function (event, field_id) {
208 if (cm_field_ids.includes(field_id)) {
209 app.destroy(field_id);
210 }
211 });
212
213 // Add handler for panel switches
214 $builder.on('charitablePanelSwitch', function () {
215 setTimeout(() => {
216 Object.keys(app.state.editors).forEach(id => {
217 const editor = app.state.getEditor(id);
218 if (editor && editor.codemirror) {
219 editor.codemirror.refresh();
220 }
221 });
222 }, 100);
223 });
224
225 // Add handler for window focus
226 $(window).on('focus', () => {
227 Object.keys(app.state.editors).forEach(id => {
228 const editor = app.state.getEditor(id);
229 if (editor && editor.codemirror) {
230 editor.codemirror.refresh();
231 }
232 });
233 });
234 },
235
236 load: function () {
237 wpchar.debug('load', 'field-html-js');
238
239 // Refresh any existing editors after page load
240 setTimeout(() => {
241 Object.keys(app.state.editors).forEach(id => {
242 const editor = app.state.getEditor(id);
243 if (editor && editor.codemirror) {
244 editor.codemirror.refresh();
245 }
246 });
247 }, 500);
248 },
249
250 // Add this to your app object
251 destroy: function (edit_field_id) {
252 if (cm_editor && cm_editor.codemirror) {
253 cm_editor.codemirror.toTextArea();
254 cm_editor = null;
255 }
256
257 // Remove from tracked IDs
258 const index = cm_field_ids.indexOf(edit_field_id);
259 if (index > -1) {
260 cm_field_ids.splice(index, 1);
261 }
262 },
263
264 validateField: function (edit_field_id) {
265 const field_object = $('#charitable-panel-field-settings-field_html_html_' + edit_field_id);
266
267 if (!field_object.length) {
268 wpchar.debug('Field not found: ' + edit_field_id, 'field-html-js');
269 return false;
270 }
271
272 if (!field_object.is('textarea')) {
273 wpchar.debug('Field is not a textarea: ' + edit_field_id, 'field-html-js');
274 return false;
275 }
276
277 if (field_object.parent().find('.CodeMirror').length > 0) {
278 wpchar.debug('CodeMirror already initialized for: ' + edit_field_id, 'field-html-js');
279 return false;
280 }
281
282 return true;
283 },
284
285 waitForElement: function (field_id, callback, maxAttempts = 10) {
286 let attempts = 0;
287
288 const checkElement = setInterval(function () {
289 attempts++;
290 const element = $('#charitable-panel-field-settings-field_html_html_' + field_id);
291
292 if (element.length) {
293 clearInterval(checkElement);
294 callback(element);
295 wpchar.debug('Trying to initialize field: ' + field_id, 'field-html-js');
296 } else if (attempts >= maxAttempts) {
297 clearInterval(checkElement);
298 wpchar.debug('Field not found after ' + maxAttempts + ' attempts', 'field-html-js');
299 }
300 }, 500);
301 },
302
303 codemirrorInit: function (edit_field_id) {
304 this.waitForElement(edit_field_id, (element) => {
305 if (!this.validateField(edit_field_id)) {
306 return;
307 }
308
309 const settings = this.getEditorSettings();
310 element.css('visibility', 'hidden');
311
312 try {
313 setTimeout(() => {
314 const editor = wp.codeEditor.initialize(element[0], settings);
315
316 if (editor && editor.codemirror) {
317 this.state.addEditor(edit_field_id, editor);
318
319 editor.codemirror.on('change', () => {
320 editor.codemirror.save();
321 element.trigger('change');
322 });
323
324 element.css('visibility', 'visible');
325 cm_field_ids.push(edit_field_id);
326
327 editor.codemirror.setOption('extraKeys', {
328 'Ctrl-Space': 'autocomplete',
329 'Tab': function (cm) {
330 if (cm.somethingSelected()) {
331 cm.indentSelection('add');
332 } else {
333 cm.replaceSelection(Array(cm.getOption('indentUnit') + 1).join(' '));
334 }
335 }
336 });
337
338 // Refresh editor after initialization to prevent rendering issues
339 editor.codemirror.refresh();
340
341 wpchar.debug('CodeMirror initialized for: ' + edit_field_id, 'field-html-js');
342 }
343 }, 500);
344 } catch (error) {
345 console.error('CodeMirror initialization error:', error);
346 element.css('visibility', 'visible');
347 }
348 });
349 },
350
351 getEditorSettings: function () {
352 const defaultSettings = wp.codeEditor.defaultSettings ? _.clone(wp.codeEditor.defaultSettings) : {};
353
354 return {
355 codemirror: _.extend({}, defaultSettings.codemirror, {
356 mode: 'html',
357 lineNumbers: true,
358 lineWrapping: true,
359 indentUnit: 2,
360 tabSize: 2,
361 autoCloseTags: true,
362 autoCloseBrackets: true,
363 matchBrackets: true,
364 foldGutter: true,
365 gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
366 extraKeys: {
367 'Ctrl-Space': 'autocomplete'
368 },
369 hintOptions: {
370 completeSingle: false
371 },
372 theme: 'default',
373 scrollbarStyle: 'overlay'
374 })
375 };
376 },
377
378 handleResize: function () {
379 $(window).on('resize', _.debounce(() => {
380 Object.values(this.state.editors).forEach(editor => {
381 if (editor && editor.codemirror) {
382 editor.codemirror.refresh();
383 }
384 });
385 }, 250));
386 },
387
388 setupCleanup: function () {
389 $(window).on('beforeunload', () => {
390 Object.keys(this.state.editors).forEach(id => {
391 this.destroy(id);
392 });
393 });
394 },
395
396 // Add diagnostic function for troubleshooting.
397 diagnostics: function () {
398 return {
399 activeEditors: Object.keys(this.state.editors).length,
400 trackedFields: cm_field_ids.length,
401 editorInstances: $('.CodeMirror').length,
402 textareaFields: $('[id^=charitable-panel-field-settings-field_html_html_]').length,
403 memoryUsage: window.performance && window.performance.memory ?
404 window.performance.memory.usedJSHeapSize / 1048576 + ' MB' :
405 'Not available'
406 };
407 }
408
409 }
410
411 // Provide access to public functions/properties.
412 return app;
413
414 }(document, window, jQuery));
415
416 CharitableCampaignBuilderFieldHTML.init();
417
418