1tinymce.PluginManager.add('portal-unesp-gallery', function (editor, url) { 2 // Configuration constants 3 const GALLERY_PREVIEW_IMAGE_LIMIT = 1000; 4 5 // Helper function to create gallery shortcode 6 function createGalleryShortcode(data) { 7 return `[portal-unesp-gallery title="${data.title}" description="${data.description}" images="${data.images}"]`; 8 } 9 10 // Helper function to read placeholder data 11 function getPlaceholderData(placeholder, getAttrib) { 12 return { 13 title: getAttrib(placeholder, 'data-mce-gallery-title') || '', 14 description: getAttrib(placeholder, 'data-mce-gallery-description') || '', 15 images: getAttrib(placeholder, 'data-mce-gallery-images') || '' 16 }; 17 } 18 19 // Helper function to update placeholder data 20 function setPlaceholderData(placeholder, data) { 21 editor.dom.setAttribs(placeholder, { 22 'data-mce-gallery-title': data.title, 23 'data-mce-gallery-description': data.description, 24 'data-mce-gallery-images': data.images 25 }); 26 } 27 28 // Helper function to parse shortcode attributes 29 function getShortcodeAttribute(attributes, name) { 30 const match = attributes.match(new RegExp(`${name}=("([^"]*)"|'([^']*)')`)); 31 return match ? (match[2] || match[3] || '') : ''; 32 } 33 34 // Helper function to update gallery placeholder content 35 function updateGalleryPlaceholder(placeholder, data) { 36 // Update the placeholder attributes 37 setPlaceholderData(placeholder, data); 38 39 // Update the visible content 40 const contentDiv = editor.dom.select('div', placeholder)[0]; 41 if (contentDiv) { 42 contentDiv.innerHTML = createPlaceholderContentHtml(data); 43 } 44 } 45 46 // Helper function to get fileManagerUrl from textarea 47 function getFileManagerUrl() { 48 const textarea = editor.getElement(); 49 return textarea ? textarea.getAttribute('data-tinymce-file-manager-portal-unesp-gallery-url') || '' : ''; 50 } 51 52 // Helper function to open gallery edit dialog 53 function openGalleryEditDialog(galleryData, updateCallback) { 54 // Save window scroll position 55 document.body.style.overflow = 'hidden'; 56 const fileManagerUrl = getFileManagerUrl(); 57 const dialogUrl = fileManagerUrl 58 ? url + `/gallery-dialog.php?operation=update&file_manager_url=${encodeURIComponent(fileManagerUrl)}` 59 : url + '/gallery-dialog.php?operation=update'; 60 $.colorbox({ 61 href: dialogUrl, 62 iframe: true, 63 width: '100%', 64 height: '100%', 65 onComplete: function () { 66 const iframe = $('.cboxIframe')[0]; 67 const iframeWindow = iframe.contentWindow; 68 iframeWindow.galleryData = galleryData; 69 // Restore scroll position 70 }, 71 onClosed: function () { 72 document.body.style.overflow = ''; 73 // Cleanup if needed 74 if (window[updateCallback]) { 75 delete window[updateCallback]; 76 } 77 } 78 }); 79 } 80 81 // Helper function to open gallery creation dialog 82 function openGalleryCreateDialog() { 83 const fileManagerUrl = getFileManagerUrl(); 84 const dialogUrl = fileManagerUrl 85 ? url + `/gallery-dialog.php?operation=insert&file_manager_url=${encodeURIComponent(fileManagerUrl)}` 86 : url + '/gallery-dialog.php?operation=insert'; 87 $.colorbox({ 88 href: dialogUrl, 89 iframe: true, 90 width: '1200px', 91 height: '95%', 92 onClosed: function () { 93 if (window.insertGallery) { 94 delete window.insertGallery; 95 } 96 } 97 }); 98 } 99 100 // Function to handle editing a gallery 101 function handleGalleryEdit(placeholder) { 102 const galleryData = getPlaceholderData(placeholder, editor.dom.getAttrib); 103 104 window.updateGallery = function (data) { 105 updateGalleryPlaceholder(placeholder, data); 106 $.colorbox.close(); 107 delete window.updateGallery; 108 }; 109 110 openGalleryEditDialog(galleryData, 'updateGallery'); 111 } 112 113 // Helper function to set button state based on TinyMCE version 114 function setButtonState(api, isActive) { 115 if (api.setActive) { 116 api.setActive(isActive); 117 } else if (api.setState) { 118 api.setState(isActive ? 2 : 0); // 2 is active, 0 is inactive 119 } else { 120 const buttonElement = api.getEl ? api.getEl() : null; 121 if (buttonElement) { 122 if (isActive) { 123 editor.dom.addClass(buttonElement, 'mce-active'); 124 } else { 125 editor.dom.removeClass(buttonElement, 'mce-active'); 126 } 127 } 128 } 129 } 130 131 // Register a custom icon for the toolbar button 132 editor.ui.registry.addIcon( 133 'portal-unesp-gallery', 134 '<svg xmlns="http://www.w3.org/2000/svg" viewBox="38.292 38.354 23 23" width="23px" height="23px">' + 135 '<path d="M 40.292 56.054 L 43.592 52.854 C 43.892 52.554 44.292 52.554 44.592 52.854 L 47.292 55.354 L 51.392 51.354 C 51.692 50.954 52.192 50.954 52.392 51.354 L 54.392 53.254 L 54.392 45.354 L 40.292 45.354 Z M 40.292 59.354 L 43.292 59.354 L 46.092 56.454 L 44.092 54.454 L 40.292 58.254 Z M 54.292 55.354 L 51.792 52.954 L 45.392 59.454 L 54.292 59.454 Z M 39.292 43.354 L 43.292 43.354 L 43.292 39.354 C 43.292 38.754 43.692 38.354 44.292 38.354 L 60.292 38.354 C 60.892 38.354 61.292 38.754 61.292 39.354 L 61.292 55.354 C 61.292 55.954 60.892 56.354 60.292 56.354 L 56.292 56.354 L 56.292 60.354 C 56.292 60.954 55.892 61.354 55.292 61.354 L 39.292 61.354 C 38.74 61.354 38.292 60.906 38.292 60.354 L 38.292 44.354 C 38.292 43.754 38.692 43.354 39.292 43.354 Z M 55.292 43.354 C 55.892 43.354 56.292 43.754 56.292 44.354 L 56.292 46.452 L 56.392 46.354
135C 56.692 45.954 57.192 45.954 57.392 46.354 L 59.392 48.254 L 59.392 40.354 L 45.292 40.354 L 45.292 43.354 L 48.56 43.354 C 48.897 42.77 49.522 42.354 50.292 42.354 C 51.006 42.354 51.667 42.735 52.024 43.354 Z M 43.56 50.354 C 42.79 49.02 43.752 47.354 45.292 47.354 C 46.006 47.354 46.667 47.735 47.024 48.354 C 47.794 49.687 46.831 51.354 45.292 51.354 C 44.577 51.354 43.917 50.972 43.56 50.354 Z M 59.292 54.454 L 59.292 50.354 L 56.792 47.954 L 56.292 48.462 L 56.292 54.454 Z" fill-rule="nonzero" style="stroke-width: 1;"/>' + 136 '</svg>' 137 ); 138 139 // Add a button to the editor 140 editor.ui.registry.addToggleButton('portal-unesp-gallery', { 141 // text: 'Galeria', 142 icon: 'portal-unesp-gallery', 143 tooltip: 'Inserir/editar galeria', 144 onAction: function () { 145 // Check if a gallery placeholder is selected 146 const selectedNode = editor.selection.getNode(); 147 const galleryPlaceholder = editor.dom.getParent(selectedNode, '.pu-portal-unesp-gallery-placeholder'); 148 149 if (galleryPlaceholder) { 150 // Edit existing gallery 151 handleGalleryEdit(galleryPlaceholder); 152 } else { 153 // Insert new gallery 154 window.insertGallery = function (data) { 155 // Create the shortcode with the provided data 156 const shortcode = createGalleryShortcode(data); 157 editor.insertContent(shortcode); 158 $.colorbox.close(); 159 delete window.insertGallery; 160 }; 161 162 // Open a Colorbox modal with an iframe 163 openGalleryCreateDialog(); 164 } 165 }, 166 onSetup: function (api) { 167 // Function to check if a gallery is selected 168 const checkGallerySelected = function () { 169 const selectedNode = editor.selection.getNode(); 170 const galleryPlaceholder = editor.dom.getParent(selectedNode, '.pu-portal-unesp-gallery-placeholder'); 171 setButtonState(api, !!galleryPlaceholder); 172 }; 173 174 // Initial state 175 checkGallerySelected(); 176 177 // Use the selectorChangedWithUnbind method for more efficient updates 178 const selectionChangeUnbind = editor.selection.selectorChangedWithUnbind( 179 '.pu-portal-unesp-gallery-placeholder', 180 function (state) { 181 setButtonState(api, state); 182 } 183 ).unbind; 184 185 // Also listen for NodeChange for broader selection changes 186 editor.on('NodeChange', checkGallerySelected); 187 188 // Return a function to be called when the button is removed 189 return function () { 190 selectionChangeUnbind(); 191 editor.off('NodeChange', checkGallerySelected); 192 }; 193 } 194 }); 195 196 // Load external CSS for the gallery placeholder 197 editor.on('init', function () { 198 const linkElem = editor.dom.create('link', { 199 rel: 'stylesheet', 200 href: url + '/plugin.css' 201 }); 202 editor.getDoc().getElementsByTagName('head')[0].appendChild(linkElem); 203 }); 204 205 // Helper function to create image previews 206 function createImagePreview(imagesString) { 207 let imagePreview = ''; 208 try { 209 if (imagesString) { 210 const imageItems = imagesString.split('||'); 211 imagePreview = '<div class="pu-portal-unesp-gallery-preview">'; 212 // Limit to first N images for preview 213 const previewImages = imageItems.slice(0, GALLERY_PREVIEW_IMAGE_LIMIT); 214 previewImages.forEach(item => { 215 // Split by pipe to get URL and legend 216 const parts = item.trim().split('~'); 217 const url = parts[0] ? parts[0].trim() : ''; 218 const legend = parts.length > 1 ? parts[1].trim() : ''; 219 220 if (!url) { 221 return; 222 } 223 224 // Add image with legend as title attribute for tooltip 225 imagePreview += `<img src="${url}" alt="Preview" title="${legend}">`; 226 }); 227 if (imageItems.length > GALLERY_PREVIEW_IMAGE_LIMIT) { 228 imagePreview += `<span class="pu-portal-unesp-gallery-more">+${imageItems.length - GALLERY_PREVIEW_IMAGE_LIMIT} mais</span>`;
229 } 230 imagePreview += '</div>'; 231 } 232 } catch (e) { 233 console.error('Error creating image preview:', e); 234 imagePreview = '<div class="pu-portal-unesp-gallery-preview">Error loading preview</div>'; 235 } 236 return imagePreview; 237 } 238 239 // Helper function to create placeholder inner HTML 240 function createPlaceholderContentHtml(data) { 241 const imagePreview = createImagePreview(data.images); 242 const hasTitle = Boolean(data.title && data.title.trim()); 243 const hasDescription = Boolean(data.description && data.description.trim()); 244 const headerHtml = (hasTitle || hasDescription) 245 ? ` 246 <header class="pu-portal-unesp-gallery-header"> 247 ${hasTitle ? `<h4 class="pu-portal-unesp-gallery-title">${data.title}</h4>` : ''} 248 ${hasDescription ? `<p class="pu-portal-unesp-gallery-description">${data.description}</p>` : ''} 249 </header> 250 ` 251 : ''; 252 return ` 253 ${headerHtml} 254 ${imagePreview} 255 `; 256 } 257 258 // Helper function to create full placeholder HTML 259 function createPlaceholderHtml(data) { 260 return `<div class="pu-portal-unesp-gallery pu-portal-unesp-gallery-placeholder" 261 data-mce-gallery-title="${data.title}" 262 data-mce-gallery-description="${data.description}" 263 data-mce-gallery-images="${data.images}" 264 contenteditable="false"> 265 <div> 266 ${createPlaceholderContentHtml(data)} 267 </div> 268 <div class="pu-portal-unesp-gallery-actions"> 269 <span class="bg-white"><span class="pu-portal-unesp-gallery-edit btn btn-outline-primary btn-sm"><i class="fa fa-pencil"></i> Editar</span></span> 270 <span class="bg-white"><span class="pu-portal-unesp-gallery-delete btn btn-outline-danger btn-sm"><i class="fa fa-times"></i> Excluir</span></span> 271 </div> 272 </div>`; 273 } 274 275 // Convert shortcodes to placeholders when content is set or loaded 276 editor.on('BeforeSetContent', function (e) { 277 e.content = e.content.replace(/\[portal-unesp-gallery([^\]]*)\]/g, function (match, attributes) { 278 // Parse attributes 279 const title = getShortcodeAttribute(attributes, 'title'); 280 const description = getShortcodeAttribute(attributes, 'description'); 281 const images = getShortcodeAttribute(attributes, 'images'); 282 283 // Create a placeholder HTML 284 return createPlaceholderHtml({ title, description, images }); 285 }); 286 }); 287 288 // Helper function to convert placeholders to shortcodes 289 function convertPlaceholdersToShortcodes(content) { 290 // Create a temporary DOM element to parse the HTML 291 const tempDiv = document.createElement('div'); 292 tempDiv.innerHTML = content; 293 294 // Find all gallery placeholders 295 const placeholders = tempDiv.querySelectorAll('.pu-portal-unesp-gallery-placeholder'); 296 297 // Replace each placeholder with its shortcode 298 placeholders.forEach(placeholder => { 299 const { title, description, images } = getPlaceholderData( 300 placeholder, 301 (el, attr) => el.getAttribute(attr) 302 ); 303 304 // Create the shortcode 305 const shortcode = document.createTextNode( 306 createGalleryShortcode({ title, description, images }) 307 ); 308 309 // Replace the placeholder with the shortcode 310 placeholder.parentNode.replaceChild(shortcode, placeholder); 311 }); 312 313 // Return the modified HTML 314 return tempDiv.innerHTML; 315 } 316 317 // Convert placeholders back to shortcodes when content is retrieved 318 editor.on('GetContent', function (e) { 319 e.content = convertPlaceholdersToShortcodes(e.content); 320 }); 321 322 // Hook into the code plugin's dialog 323 editor.on('PreInit', function () { 324 // Check if the code plugin is available 325 if (editor.plugins.code) { 326 // Store the original openCodeEditor function 327 const originalOpenCodeEditor = editor.plugins.code.openCodeEditor; 328 329 // Override the openCodeEditor function 330 editor.plugins.code.openCodeEditor = function () { 331 // Convert placeholders to shortcodes in the editor content 332 const content = editor.getContent(); 333 const convertedContent = convertPlaceholdersToShortcodes(content); 334 335 // Temporarily set the editor content to the converted version 336 editor.setContent(convertedContent); 337 338 // Call the original function 339 const result = originalOpenCodeEditor.apply(this, arguments); 340 341 // After the code dialog is closed, we'll need to convert shortcodes back to placeholders 342 // This happens automatically via BeforeSetContent when the code dialog applies changes 343 344 return result; 345 }; 346 } 347 }); 348 349 // Handle clicks on the placeholder to edit or delete 350 editor.on('click', function (e) { 351 352 // Check if clicked on gallery placeholder 353 if (e.target.closest('.pu-portal-unesp-gallery-placeholder')) { 354 // Check if edit button was clicked 355 if (e.target.closest('.pu-portal-unesp-gallery-edit')) { 356 const placeholder = editor.dom.getParent(e.target, '.pu-portal-unesp-gallery-placeholder'); 357 if (placeholder) { 358 handleGalleryEdit(placeholder); 359 } 360 } 361 // Check if delete button was clicked 362 else if (e.target.closest('.pu-portal-unesp-gallery-delete')) { 363 const placeholder = editor.dom.getParent(e.target, '.pu-portal-unesp-gallery-placeholder'); 364 if (placeholder) {
365 // Confirm deletion 366 editor.windowManager.open({ 367 title: 'Excluir Galeria', 368 body: { 369 type: 'panel', 370 items: [ 371 { 372 type: 'htmlpanel', 373 html: '<p>Tem certeza que deseja excluir esta galeria?</p>' 374 } 375 ] 376 }, 377 buttons: [ 378 { 379 type: 'cancel', 380 text: 'Cancelar' 381 }, 382 { 383 type: 'submit', 384 text: 'Excluir', 385 primary: true 386 } 387 ], 388 onSubmit: function (api) { 389 // Remove the placeholder from the editor 390 editor.dom.remove(placeholder); 391 392 // Force selection update by setting cursor to current position 393 editor.selection.collapse(true); 394 395 // Force a node change event to update UI states 396 editor.nodeChanged(); 397 398 // Set focus back to the editor 399 editor.focus(); 400 401 api.close(); 402 } 403 }); 404 } 405 } 406 return false; 407 } 408 }); 409 410 // Return metadata about the plugin 411 return { 412 getMetadata: function () { 413 return { 414 name: 'Portal Unesp Gallery', 415 url: 'https://www.unesp.br/manuais/portal' 416 }; 417 } 418 }; 419});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.