PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.21
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.21
2.7.1 2.7.2 2.7.0 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 All 141 releases
metasync / custom-pages / js / custom-pages-editor.js

custom-pages-editor.js in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.21, at custom-pages/js/custom-pages-editor.js

428 lines 11.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * MetaSync Custom Pages Enhanced Editor
3 *
4 * Provides enhanced HTML editing with CodeMirror syntax highlighting
5 * and live preview functionality
6 */
7
8 /* global metasyncCustomPagesSettings */
9
10 (function ($) {
11 'use strict';
12
13 var CustomPagesEditor = {
14 editor: null,
15
16 /**
17 * Initialize the editor
18 */
19 init: function () {
20 this.setupCodeEditor();
21 this.bindEvents();
22 this.toggleControls();
23 },
24
25 /**
26 * Setup CodeMirror editor
27 */
28 setupCodeEditor: function () {
29 var textarea = document.getElementById('metasync_html_content');
30
31 if (!textarea) {
32 console.error('MetaSync: HTML content textarea not found');
33 return;
34 }
35
36 if (typeof metasyncCustomPagesSettings === 'undefined' || !metasyncCustomPagesSettings.codeEditorSettings) {
37 console.warn('MetaSync: CodeMirror settings not available, using plain textarea');
38 return;
39 }
40
41 // Initialize CodeMirror
42 var editorSettings = metasyncCustomPagesSettings.codeEditorSettings;
43
44 // Enhanced settings for HTML editing
45 editorSettings.codemirror = $.extend({}, editorSettings.codemirror, {
46 mode: 'htmlmixed',
47 lineNumbers: true,
48 lineWrapping: true,
49 matchBrackets: true,
50 autoCloseTags: true,
51 autoCloseBrackets: true,
52 matchTags: { bothTags: true },
53 extraKeys: {
54 'Ctrl-Space': 'autocomplete',
55 'Ctrl-J': 'toMatchingTag',
56 'F11': function (cm) {
57 cm.setOption('fullScreen', !cm.getOption('fullScreen'));
58 },
59 'Esc': function (cm) {
60 if (cm.getOption('fullScreen')) {
61 cm.setOption('fullScreen', false);
62 }
63 }
64 },
65 theme: 'default',
66 indentUnit: 2,
67 tabSize: 2,
68 indentWithTabs: false
69 });
70
71 try {
72 this.editor = wp.codeEditor.initialize(textarea, editorSettings);
73
74 if (this.editor) {
75 console.log('MetaSync: CodeMirror initialized successfully');
76
77 // Force CodeMirror to refresh and load textarea content
78 setTimeout(function () {
79 if (this.editor && this.editor.codemirror) {
80 this.editor.codemirror.refresh();
81 // Ensure textarea value is synced to CodeMirror
82 var textareaValue = textarea.value;
83 if (textareaValue && textareaValue.length > 0) {
84 this.editor.codemirror.setValue(textareaValue);
85 console.log('MetaSync: Loaded ' + textareaValue.length + ' characters into CodeMirror');
86 }
87 }
88 }.bind(this), 100);
89
90 // Add fullscreen toggle button
91 this.addFullscreenButton();
92 } else {
93 console.warn('MetaSync: CodeMirror failed to initialize, using plain textarea');
94 }
95 } catch (error) {
96 console.error('MetaSync: Error initializing CodeMirror:', error);
97 }
98 },
99
100 /**
101 * Add fullscreen button to editor toolbar
102 */
103 addFullscreenButton: function () {
104 if (!this.editor) {
105 return;
106 }
107
108 var $editorWrap = $(this.editor.codemirror.getWrapperElement()).parent();
109
110 var $toolbar = $('<div class="metasync-editor-toolbar"></div>');
111 var $fullscreenBtn = $('<button type="button" class="button metasync-fullscreen-btn" title="Toggle Fullscreen (F11)"><span class="dashicons dashicons-fullscreen-alt"></span></button>');
112
113 $toolbar.append($fullscreenBtn);
114 $editorWrap.prepend($toolbar);
115
116 var editor = this.editor.codemirror;
117 $fullscreenBtn.on('click', function (e) {
118 e.preventDefault();
119 editor.setOption('fullScreen', !editor.getOption('fullScreen'));
120 $(this).find('.dashicons').toggleClass('dashicons-fullscreen-alt dashicons-fullscreen-exit-alt');
121 });
122 },
123
124 /**
125 * Bind event handlers
126 */
127 bindEvents: function () {
128 var self = this;
129
130 // Toggle HTML controls when checkbox changes
131 $('#metasync_html_enabled').on('change', function () {
132 self.toggleControls();
133 });
134
135 // File upload handling
136 $('#metasync_html_file').on('change', function (e) {
137 self.handleFileUpload(e);
138 });
139
140 // Preview button
141 $('#metasync-preview-html').on('click', function (e) {
142 e.preventDefault();
143 self.previewHTML();
144 });
145
146 // Format HTML button
147 $(document).on('click', '.metasync-format-html', function (e) {
148 e.preventDefault();
149 self.formatHTML();
150 });
151
152 // Minify HTML button
153 $(document).on('click', '.metasync-minify-html', function (e) {
154 e.preventDefault();
155 self.minifyHTML();
156 });
157
158 // CRITICAL FIX: Sync CodeMirror content back to textarea before form submission
159 // This ensures that changes made in CodeMirror are saved when publishing/updating
160 $('#post').on('submit', function () {
161 self.syncEditorContent();
162 });
163
164 // Also sync on autosave
165 $(document).on('heartbeat-tick.autosave', function () {
166 self.syncEditorContent();
167 });
168
169 // Sync before WordPress autosave runs
170 if (typeof wp !== 'undefined' && wp.autosave) {
171 $(document).on('before-autosave', function () {
172 self.syncEditorContent();
173 });
174 }
175
176 // Periodic sync every 10 seconds to ensure data is never lost
177 setInterval(function () {
178 self.syncEditorContent();
179 }, 10000);
180
181 // Sync before page unload (when user navigates away)
182 $(window).on('beforeunload', function () {
183 self.syncEditorContent();
184 });
185
186 // Add keyboard shortcuts info
187 this.addKeyboardShortcutsInfo();
188 },
189
190 /**
191 * Sync CodeMirror content back to textarea
192 * CRITICAL: This must be called before form submission to save changes
193 */
194 syncEditorContent: function () {
195 if (this.editor && this.editor.codemirror) {
196 var content = this.editor.codemirror.getValue();
197 $('#metasync_html_content').val(content);
198 console.log('MetaSync: Synced ' + content.length + ' characters from CodeMirror to textarea');
199 return true;
200 }
201 return false;
202 },
203
204 /**
205 * Toggle HTML controls visibility
206 */
207 toggleControls: function () {
208 var isEnabled = $('#metasync_html_enabled').is(':checked');
209
210 if (isEnabled) {
211 $('#metasync-html-controls').slideDown();
212 } else {
213 $('#metasync-html-controls').slideUp();
214 }
215 },
216
217 /**
218 * Handle HTML file upload
219 */
220 handleFileUpload: function (e) {
221 var self = this;
222 var file = e.target.files[0];
223
224 if (!file) {
225 return;
226 }
227
228 // Validate file type
229 if (!file.name.match(/\.(html|htm)$/i)) {
230 alert('Please upload an HTML file (.html or .htm)');
231 e.target.value = '';
232 return;
233 }
234
235 // Validate file size (max 5MB)
236 if (file.size > 5 * 1024 * 1024) {
237 alert('File size must be less than 5MB');
238 e.target.value = '';
239 return;
240 }
241
242 var reader = new FileReader();
243
244 reader.onload = function (event) {
245 var htmlContent = event.target.result;
246
247 // Set content in CodeMirror or textarea
248 if (self.editor && self.editor.codemirror) {
249 self.editor.codemirror.setValue(htmlContent);
250 } else {
251 $('#metasync_html_content').val(htmlContent);
252 }
253
254 // Show success message
255 self.showNotice('HTML file loaded successfully. Remember to save/publish the page.', 'success');
256 };
257
258 reader.onerror = function () {
259 alert('Error reading file. Please try again.');
260 };
261
262 reader.readAsText(file);
263 },
264
265 /**
266 * Preview HTML in new window
267 */
268 previewHTML: function () {
269 var htmlContent;
270
271 if (this.editor && this.editor.codemirror) {
272 htmlContent = this.editor.codemirror.getValue();
273 } else {
274 htmlContent = $('#metasync_html_content').val();
275 }
276
277 if (!htmlContent || htmlContent.trim() === '') {
278 alert('Please enter HTML content first.');
279 return;
280 }
281
282 // Open preview in new window
283 var previewWindow = window.open('', 'preview', 'width=1200,height=800,menubar=no,toolbar=no,location=no');
284
285 if (!previewWindow) {
286 alert('Preview blocked by popup blocker. Please allow popups for this site.');
287 return;
288 }
289
290 previewWindow.document.write(htmlContent);
291 previewWindow.document.close();
292 },
293
294 /**
295 * Format/beautify HTML
296 */
297 formatHTML: function () {
298 var htmlContent;
299
300 if (this.editor && this.editor.codemirror) {
301 htmlContent = this.editor.codemirror.getValue();
302 } else {
303 htmlContent = $('#metasync_html_content').val();
304 }
305
306 if (!htmlContent) {
307 return;
308 }
309
310 // Simple HTML formatting (indent properly)
311 var formatted = this.beautifyHTML(htmlContent);
312
313 if (this.editor && this.editor.codemirror) {
314 this.editor.codemirror.setValue(formatted);
315 } else {
316 $('#metasync_html_content').val(formatted);
317 }
318
319 this.showNotice('HTML formatted successfully!', 'success');
320 },
321
322 /**
323 * Simple HTML beautifier
324 */
325 beautifyHTML: function (html) {
326 var tab = ' ';
327 var result = '';
328 var indent = '';
329
330 html.split(/>\s*</).forEach(function (element) {
331 if (element.match(/^\/\w/)) {
332 indent = indent.substring(tab.length);
333 }
334
335 result += indent + '<' + element + '>\r\n';
336
337 if (element.match(/^<?\w[^>]*[^/]$/) && !element.startsWith('input')) {
338 indent += tab;
339 }
340 });
341
342 return result.substring(1, result.length - 3);
343 },
344
345 /**
346 * Minify HTML
347 */
348 minifyHTML: function () {
349 var htmlContent;
350
351 if (this.editor && this.editor.codemirror) {
352 htmlContent = this.editor.codemirror.getValue();
353 } else {
354 htmlContent = $('#metasync_html_content').val();
355 }
356
357 if (!htmlContent) {
358 return;
359 }
360
361 // Simple minification - remove extra whitespace
362 var minified = htmlContent
363 .replace(/\s+/g, ' ')
364 .replace(/>\s+</g, '><')
365 .trim();
366
367 if (this.editor && this.editor.codemirror) {
368 this.editor.codemirror.setValue(minified);
369 } else {
370 $('#metasync_html_content').val(minified);
371 }
372
373 this.showNotice('HTML minified successfully!', 'success');
374 },
375
376 /**
377 * Show admin notice
378 */
379 showNotice: function (message, type) {
380 type = type || 'info';
381
382 var $notice = $('<div class="notice notice-' + type + ' is-dismissible"><p>' + message + '</p></div>');
383
384 $('.metasync-custom-html-container').prepend($notice);
385
386 setTimeout(function () {
387 $notice.fadeOut(function () {
388 $(this).remove();
389 });
390 }, 3000);
391 },
392
393 /**
394 * Add keyboard shortcuts info
395 */
396 addKeyboardShortcutsInfo: function () {
397 var shortcuts = [
398 { key: 'Ctrl + Space', action: 'Autocomplete' },
399 { key: 'Ctrl + J', action: 'Jump to matching tag' },
400 { key: 'F11', action: 'Toggle fullscreen' },
401 { key: 'Esc', action: 'Exit fullscreen' },
402 { key: 'Ctrl + /', action: 'Toggle comment' }
403 ];
404
405 var $info = $('<div class="metasync-keyboard-shortcuts"></div>');
406 $info.append('<h4>⌨️ Keyboard Shortcuts</h4>');
407
408 var $list = $('<ul></ul>');
409 shortcuts.forEach(function (shortcut) {
410 $list.append('<li><kbd>' + shortcut.key + '</kbd> - ' + shortcut.action + '</li>');
411 });
412
413 $info.append($list);
414 $('#metasync-html-controls').append($info);
415 }
416 };
417
418 // Initialize when document is ready
419 $(document).ready(function () {
420 // Only initialize on custom page post type
421 if ($('#metasync_custom_html').length > 0) {
422 CustomPagesEditor.init();
423 }
424 });
425
426 })(jQuery);
427
428