[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 /** 2 * @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org> 3 * @license GNU General Public License version 2 or later; see LICENSE.txt 4 */ 5 if (!Joomla) { 6 throw new Error('Joomla API is not properly initiated'); 7 } 8 /** 9 * An object holding all the information of the selected image in media manager 10 * eg: 11 * { 12 * extension: "png" 13 * fileType: "image/png" 14 * height: 44 15 * path: "local-images:/powered_by.png" 16 * thumb: undefined 17 * width: 294 18 * } 19 */ 20 21 22 Joomla.selectedMediaFile = {}; 23 const supportedExtensions = Joomla.getOptions('media-picker', {}); 24 25 if (!Object.keys(supportedExtensions).length) { 26 throw new Error('No supported extensions provided'); 27 } 28 /** 29 * Event Listener that updates the Joomla.selectedMediaFile 30 * to the selected file in the media manager 31 */ 32 33 34 document.addEventListener('onMediaFileSelected', async e => { 35 Joomla.selectedMediaFile = e.detail; 36 const currentModal = Joomla.Modal.getCurrent(); 37 const container = currentModal.querySelector('.modal-body'); 38 39 if (!container) { 40 return; 41 } 42 43 const optionsEl = container.querySelector('joomla-field-mediamore'); 44 45 if (optionsEl) { 46 optionsEl.parentNode.removeChild(optionsEl); 47 } // No extra attributes (lazy, alt) for fields 48 49 50 if (container.closest('joomla-field-media')) { 51 return; 52 } 53 54 const { 55 images, 56 audios, 57 videos, 58 documents 59 } = supportedExtensions; 60 61 if (Joomla.selectedMediaFile.path) { 62 let type; 63 64 if (images.includes(Joomla.selectedMediaFile.extension.toLowerCase())) { 65 type = 'images'; 66 } else if (audios.includes(Joomla.selectedMediaFile.extension.toLowerCase())) { 67 type = 'audios'; 68 } else if (videos.includes(Joomla.selectedMediaFile.extension.toLowerCase())) { 69 type = 'videos'; 70 } else if (documents.includes(Joomla.selectedMediaFile.extension.toLowerCase())) { 71 type = 'documents'; 72 } 73 74 if (type) { 75 container.insertAdjacentHTML('afterbegin', `<joomla-field-mediamore 76 parent-id="$currentModal.id}" 77 type="$type}" 78 summary-label="$Joomla.Text._('JFIELD_MEDIA_SUMMARY_LABEL')}" 79 lazy-label="$Joomla.Text._('JFIELD_MEDIA_LAZY_LABEL')}" 80 alt-label="$Joomla.Text._('JFIELD_MEDIA_ALT_LABEL')}" 81 alt-check-label="$Joomla.Text._('JFIELD_MEDIA_ALT_CHECK_LABEL')}" 82 alt-check-desc-label="$Joomla.Text._('JFIELD_MEDIA_ALT_CHECK_DESC_LABEL')}" 83 classes-label="$Joomla.Text._('JFIELD_MEDIA_CLASS_LABEL')}" 84 figure-classes-label="$Joomla.Text._('JFIELD_MEDIA_FIGURE_CLASS_LABEL')}" 85 figure-caption-label="$Joomla.Text._('JFIELD_MEDIA_FIGURE_CAPTION_LABEL')}" 86 embed-check-label="$Joomla.Text._('JFIELD_MEDIA_EMBED_CHECK_LABEL')}" 87 embed-check-desc-label="$Joomla.Text._('JFIELD_MEDIA_EMBED_CHECK_DESC_LABEL')}" 88 download-check-label="$Joomla.Text._('JFIELD_MEDIA_DOWNLOAD_CHECK_LABEL')}" 89 download-check-desc-label="$Joomla.Text._('JFIELD_MEDIA_DOWNLOAD_CHECK_DESC_LABEL')}" 90 title-label="$Joomla.Text._('JFIELD_MEDIA_TITLE_LABEL')}" 91 width-label="$Joomla.Text._('JFIELD_MEDIA_WIDTH_LABEL')}" 92 height-label="$Joomla.Text._('JFIELD_MEDIA_HEIGHT_LABEL')}" 93 ></joomla-field-mediamore> 94 `); 95 } 96 } 97 }); 98 /** 99 * Method to check if passed param is HTMLElement 100 * 101 * @param o {string|HTMLElement} Element to be checked 102 * 103 * @returns {boolean} 104 */ 105 106 const isElement = o => typeof HTMLElement === 'object' ? o instanceof HTMLElement : o && typeof o === 'object' && o.nodeType === 1 && typeof o.nodeName === 'string'; 107 /** 108 * Method to return the image size 109 * 110 * @param url {string} 111 * 112 * @returns {bool} 113 */ 114 115 116 const getImageSize = url => new Promise((resolve, reject) => { 117 const img = new Image(); 118 img.src = url; 119 120 img.onload = () => { 121 Joomla.selectedMediaFile.width = img.width; 122 Joomla.selectedMediaFile.height = img.height; 123 resolve(true); 124 }; 125 126 img.onerror = () => { 127 // eslint-disable-next-line prefer-promise-reject-errors 128 reject(false); 129 }; 130 }); 131 132 const insertAsImage = async (media, editor, fieldClass) => { 133 if (media.url) { 134 const { 135 rootFull 136 } = Joomla.getOptions('system.paths'); 137 const parts = media.url.split(rootFull); 138 139 if (parts.length > 1) { 140 // eslint-disable-next-line prefer-destructuring 141 Joomla.selectedMediaFile.url = parts[1]; 142 143 if (media.thumb_path) { 144 Joomla.selectedMediaFile.thumb = media.thumb_path; 145 } else { 146 Joomla.selectedMediaFile.thumb = false; 147 } 148 } else if (media.thumb_path) { 149 Joomla.selectedMediaFile.url = media.url; 150 Joomla.selectedMediaFile.thumb = media.thumb_path; 151 } 152 } else { 153 Joomla.selectedMediaFile.url = false; 154 } 155 156 if (Joomla.selectedMediaFile.url) { 157 let attribs; 158 let isLazy = ''; 159 let alt = ''; 160 let appendAlt = ''; 161 let classes = ''; 162 let figClasses = ''; 163 let figCaption = ''; 164 let imageElement = ''; 165 166 if (!isElement(editor)) { 167 const currentModal = fieldClass.closest('.modal-content'); 168 attribs = currentModal.querySelector('joomla-field-mediamore'); 169 170 if (attribs) { 171 if (attribs.getAttribute('alt-check') === 'true') { 172 appendAlt = ' alt=""'; 173 } 174 175 alt = attribs.getAttribute('alt-value') ? ` alt="$attribs.getAttribute('alt-value')}"` : appendAlt; 176 classes = attribs.getAttribute('img-classes') ? ` class="$attribs.getAttribute('img-classes')}"` : ''; 177 figClasses = attribs.getAttribute('fig-classes') ? ` class="image $attribs.getAttribute('fig-classes')}"` : ' class="image"'; 178 figCaption = attribs.getAttribute('fig-caption') ? `$attribs.getAttribute('fig-caption')}` : ''; 179 180 if (attribs.getAttribute('is-lazy') === 'true') { 181 isLazy = ` loading="lazy" width="$Joomla.selectedMediaFile.width}" height="$Joomla.selectedMediaFile.height}"`; 182 183 if (Joomla.selectedMediaFile.width === 0 || Joomla.selectedMediaFile.height === 0) { 184 try { 185 await getImageSize(Joomla.selectedMediaFile.url); 186 isLazy = ` loading="lazy" width="$Joomla.selectedMediaFile.width}" height="$Joomla.selectedMediaFile.height}"`; 187 } catch (err) { 188 isLazy = ''; 189 } 190 } 191 } 192 } 193 194 if (figCaption) { 195 imageElement = `<figure$figClasses}><img src="$Joomla.selectedMediaFile.url}"$classes}$isLazy}$alt} data-path="$Joomla.selectedMediaFile.path}"/><figcaption>$figCaption}</figcaption></figure>`; 196 } else { 197 imageElement = `<img src="$Joomla.selectedMediaFile.url}"$classes}$isLazy}$alt} data-path="$Joomla.selectedMediaFile.path}"/>`; 198 } 199 200 if (attribs) { 201 attribs.parentNode.removeChild(attribs); 202 } 203 204 Joomla.editors.instances[editor].replaceSelection(imageElement); 205 } else { 206 if (Joomla.selectedMediaFile.width === 0 || Joomla.selectedMediaFile.height === 0) { 207 try { 208 await getImageSize(Joomla.selectedMediaFile.url); // eslint-disable-next-line no-empty 209 } catch (err) { 210 Joomla.selectedMediaFile.height = 0; 211 Joomla.selectedMediaFile.width = 0; 212 } 213 } 214 215 fieldClass.markValid(); 216 fieldClass.setValue(`$Joomla.selectedMediaFile.url}#joomlaImage://${media.path.replace(':', '')}?width=${Joomla.selectedMediaFile.width}&height=${Joomla.selectedMediaFile.height}`); 217 } 218 } 219 }; 220 221 const insertAsOther = (media, editor, fieldClass, type) => { 222 if (media.url) { 223 const { 224 rootFull 225 } = Joomla.getOptions('system.paths'); 226 const parts = media.url.split(rootFull); 227 228 if (parts.length > 1) { 229 // eslint-disable-next-line prefer-destructuring 230 Joomla.selectedMediaFile.url = parts[1]; 231 } else { 232 Joomla.selectedMediaFile.url = media.url; 233 } 234 } else { 235 Joomla.selectedMediaFile.url = false; 236 } 237 238 let attribs; 239 240 if (Joomla.selectedMediaFile.url) { 241 // Available Only inside an editor 242 if (!isElement(editor)) { 243 let outputText; 244 const currentModal = fieldClass.closest('.modal-content'); 245 attribs = currentModal.querySelector('joomla-field-mediamore'); 246 247 if (attribs) { 248 const embedable = attribs.getAttribute('embed-it'); 249 250 if (embedable && embedable === 'true') { 251 if (type === 'audios') { 252 outputText = `<audio controls src="$Joomla.selectedMediaFile.url}"></audio>`; 253 } 254 255 if (type === 'documents') { 256 // @todo use ${Joomla.selectedMediaFile.filetype} in type 257 const title = attribs.getAttribute('title'); 258 outputText = `<object type="application/$Joomla.selectedMediaFile.extension}" data="$Joomla.selectedMediaFile.url}" $title ? `title="$title}"` : ''} width="$attribs.getAttribute('width')}" height="$attribs.getAttribute('height')}"> 259 $Joomla.Text._('JFIELD_MEDIA_UNSUPPORTED').replace('{tag}', `<a download href="$Joomla.selectedMediaFile.url}">`).replace(/{extension}/g, Joomla.selectedMediaFile.extension)} 260 </object>`; 261 } 262 263 if (type === 'videos') { 264 outputText = `<video controls width="$attribs.getAttribute('width')}" height="$attribs.getAttribute('height')}"> 265 <source src="$Joomla.selectedMediaFile.url}" type="$Joomla.selectedMediaFile.fileType}"> 266 </video>`; 267 } 268 } else if (Joomla.editors.instances[editor].getSelection() !== '') { 269 outputText = `<a download href="$Joomla.selectedMediaFile.url}">$Joomla.editors.instances[editor].getSelection()}</a>`; 270 } else { 271 const name = /([\w-]+)\./.exec(Joomla.selectedMediaFile.url); 272 outputText = `<a download href="$Joomla.selectedMediaFile.url}">$Joomla.Text._('JFIELD_MEDIA_DOWNLOAD_FILE').replace('{file}', name[1])}</a>`; 273 } 274 } 275 276 if (attribs) { 277 attribs.parentNode.removeChild(attribs); 278 } 279 280 Joomla.editors.instances[editor].replaceSelection(outputText); 281 } else { 282 fieldClass.markValid(); 283 fieldClass.givenType = type; 284 fieldClass.setValue(Joomla.selectedMediaFile.url); 285 } 286 } 287 }; 288 /** 289 * Method to append the image in an editor or a field 290 * 291 * @param {{}} resp 292 * @param {string|HTMLElement} editor 293 * @param {string} fieldClass 294 */ 295 296 297 const execTransform = async (resp, editor, fieldClass) => { 298 if (resp.success === true) { 299 const media = resp.data[0]; 300 const { 301 images, 302 audios, 303 videos, 304 documents 305 } = supportedExtensions; 306 307 if (Joomla.selectedMediaFile.extension && images.includes(media.extension.toLowerCase())) { 308 return insertAsImage(media, editor, fieldClass); 309 } 310 311 if (Joomla.selectedMediaFile.extension && audios.includes(media.extension.toLowerCase())) { 312 return insertAsOther(media, editor, fieldClass, 'audios'); 313 } 314 315 if (Joomla.selectedMediaFile.extension && documents.includes(media.extension.toLowerCase())) { 316 return insertAsOther(media, editor, fieldClass, 'documents'); 317 } 318 319 if (Joomla.selectedMediaFile.extension && videos.includes(media.extension.toLowerCase())) { 320 return insertAsOther(media, editor, fieldClass, 'videos'); 321 } 322 323 return ''; 324 } 325 326 return ''; 327 }; 328 /** 329 * Method that resolves the real url for the selected media file 330 * 331 * @param data {object} The data for the detail 332 * @param editor {string|object} The data for the detail 333 * @param fieldClass {HTMLElement} The fieldClass for the detail 334 * 335 * @returns {void} 336 */ 337 338 339 Joomla.getMedia = (data, editor, fieldClass) => new Promise((resolve, reject) => { 340 if (!data || typeof data === 'object' && (!data.path || data.path === '')) { 341 Joomla.selectedMediaFile = {}; 342 resolve({ 343 resp: { 344 success: false 345 } 346 }); 347 return; 348 } 349 350 const url = `$Joomla.getOptions('system.paths').baseFull}index.php?option=com_media&task=api.files&url=true&path=$data.path}&mediatypes=0,1,2,3&$Joomla.getOptions('csrf.token')}=1&format=json`; 351 fetch(url, { 352 method: 'GET', 353 headers: { 354 'Content-Type': 'application/json' 355 } 356 }).then(response => response.json()).then(async response => resolve(await execTransform(response, editor, fieldClass))).catch(error => reject(error)); 357 }); // For B/C purposes 358 359 360 Joomla.getImage = Joomla.getMedia; 361 /** 362 * A simple Custom Element for adding alt text and controlling 363 * the lazy loading on a selected image 364 * 365 * Will be rendered only for editor content images 366 * Attributes: 367 * - parent-id: the id of the parent media field {string} 368 * - lazy-label: The text for the checkbox label {string} 369 * - alt-label: The text for the alt label {string} 370 * - is-lazy: The value for the lazyloading (calculated, defaults to 'true') {string} 371 * - alt-value: The value for the alt text (calculated, defaults to '') {string} 372 */ 373 374 class JoomlaFieldMediaOptions extends HTMLElement { 375 get type() { 376 return this.getAttribute('type'); 377 } 378 379 get parentId() { 380 return this.getAttribute('parent-id'); 381 } 382 383 get lazytext() { 384 return this.getAttribute('lazy-label'); 385 } 386 387 get alttext() { 388 return this.getAttribute('alt-label'); 389 } 390 391 get altchecktext() { 392 return this.getAttribute('alt-check-label'); 393 } 394 395 get altcheckdesctext() { 396 return this.getAttribute('alt-check-desc-label'); 397 } 398 399 get embedchecktext() { 400 return this.getAttribute('embed-check-label'); 401 } 402 403 get embedcheckdesctext() { 404 return this.getAttribute('embed-check-desc-label'); 405 } 406 407 get downloadchecktext() { 408 return this.getAttribute('download-check-label'); 409 } 410 411 get downloadcheckdesctext() { 412 return this.getAttribute('download-check-desc-label'); 413 } 414 415 get classestext() { 416 return this.getAttribute('classes-label'); 417 } 418 419 get figclassestext() { 420 return this.getAttribute('figure-classes-label'); 421 } 422 423 get figcaptiontext() { 424 return this.getAttribute('figure-caption-label'); 425 } 426 427 get summarytext() { 428 return this.getAttribute('summary-label'); 429 } 430 431 get widthtext() { 432 return this.getAttribute('width-label'); 433 } 434 435 get heighttext() { 436 return this.getAttribute('height-label'); 437 } 438 439 get titletext() { 440 return this.getAttribute('title-label'); 441 } 442 443 connectedCallback() { 444 if (this.type === 'images') { 445 this.innerHTML = `<details open> 446 <summary>$this.summarytext}</summary> 447 <div class=""> 448 <div class="form-group"> 449 <div class="input-group"> 450 <label class="input-group-text" for="$this.parentId}-alt">$this.alttext}</label> 451 <input class="form-control" type="text" id="$this.parentId}-alt" data-is="alt-value" /> 452 </div> 453 </div> 454 <div class="form-group"> 455 <div class="form-check"> 456 <input class="form-check-input" type="checkbox" id="$this.parentId}-alt-check"> 457 <label class="form-check-label" for="$this.parentId}-alt-check">$this.altchecktext}</label> 458 <div><small class="form-text">$this.altcheckdesctext}</small></div> 459 </div> 460 </div> 461 <div class="form-group"> 462 <div class="form-check"> 463 <input class="form-check-input" type="checkbox" id="$this.parentId}-lazy" checked> 464 <label class="form-check-label" for="$this.parentId}-lazy">$this.lazytext}</label> 465 </div> 466 </div> 467 <div class="form-group"> 468 <div class="input-group"> 469 <label class="input-group-text" for="$this.parentId}-classes">$this.classestext}</label> 470 <input class="form-control" type="text" id="$this.parentId}-classes" data-is="img-classes"/> 471 </div> 472 </div> 473 <div class="form-group"> 474 <div class="input-group"> 475 <label class="input-group-text" for="$this.parentId}-figclasses">$this.figclassestext}</label> 476 <input class="form-control" type="text" id="$this.parentId}-figclasses" data-is="fig-classes"/> 477 </div> 478 </div> 479 <div class="form-group"> 480 <div class="input-group"> 481 <label class="input-group-text" for="$this.parentId}-figcaption">$this.figcaptiontext}</label> 482 <input class="form-control" type="text" id="$this.parentId}-figcaption" data-is="fig-caption"/> 483 </div> 484 </div> 485 </div> 486 </details>`; 487 this.lazyInputFn = this.lazyInputFn.bind(this); 488 this.altCheckFn = this.altCheckFn.bind(this); 489 this.inputFn = this.inputFn.bind(this); // Add event listeners 490 491 this.lazyInput = this.querySelector(`#$this.parentId}-lazy`); 492 this.lazyInput.addEventListener('change', this.lazyInputFn); 493 this.altCheck = this.querySelector(`#$this.parentId}-alt-check`); 494 this.altCheck.addEventListener('input', this.altCheckFn); 495 [].slice.call(this.querySelectorAll('input[type="text"]')).map(el => { 496 el.addEventListener('input', this.inputFn); 497 const { 498 is 499 } = el.dataset; 500 501 if (is) { 502 this.setAttribute(is, el.value.replace(/"/g, '"')); 503 } 504 505 return el; 506 }); // Set initial values 507 508 this.setAttribute('is-lazy', !!this.lazyInput.checked); 509 this.setAttribute('alt-check', false); 510 } else if (['audios', 'videos', 'documents'].includes(this.type)) { 511 this.innerHTML = `<details open> 512 <summary>$this.summarytext}</summary> 513 <div class=""> 514 <div class="form-group"> 515 <div class="form-check"> 516 <input class="form-check-input radio" type="radio" name="flexRadioDefault" id="$this.parentId}-embed-check-2" value="0" checked> 517 <label class="form-check-label" for="$this.parentId}-embed-check-2"> 518 $this.downloadchecktext} 519 <div><small class="form-text">$this.downloadcheckdesctext}</small></div> 520 </label> 521 </div> 522 <div class="form-check"> 523 <input class="form-check-input radio" type="radio" name="flexRadioDefault" id="$this.parentId}-embed-check-1" value="1"> 524 <label class="form-check-label" for="$this.parentId}-embed-check-1"> 525 $this.embedchecktext} 526 <div><small class="form-text">$this.embedcheckdesctext}</small></div> 527 </label> 528 </div> 529 </div> 530 <div class="toggable-parts" style="display: none"> 531 <div style="display: $this.type === 'audios' ? 'none' : 'block'}"> 532 <div class="form-group"> 533 <div class="input-group"> 534 <label class="input-group-text" for="$this.parentId}-width">$this.widthtext}</label> 535 <input class="form-control" type="text" id="$this.parentId}-width" value="800" data-is="width"/> 536 </div> 537 </div> 538 <div class="form-group"> 539 <div class="input-group"> 540 <label class="input-group-text" for="$this.parentId}-height">$this.heighttext}</label> 541 <input class="form-control" type="text" id="$this.parentId}-height" value="600" data-is="height"/> 542 </div> 543 </div> 544 <div style="display: $this.type === 'document' ? 'block' : 'none'}"> 545 <div class="form-group"> 546 <div class="input-group"> 547 <label class="input-group-text" for="$this.parentId}-title">$this.titletext}</label> 548 <input class="form-control" type="text" id="$this.parentId}-title" value="" data-is="title"/> 549 </div> 550 </div> 551 </div> 552 </div> 553 </div> 554 </details>`; 555 this.embedInputFn = this.embedInputFn.bind(this); 556 this.inputFn = this.inputFn.bind(this); 557 [].slice.call(this.querySelectorAll('.form-check-input.radio')).map(el => el.addEventListener('input', this.embedInputFn)); 558 this.setAttribute('embed-it', false); 559 [].slice.call(this.querySelectorAll('input[type="text"]')).map(el => { 560 el.addEventListener('input', this.inputFn); 561 const { 562 is 563 } = el.dataset; 564 565 if (is) { 566 this.setAttribute(is, el.value.replace(/"/g, '"')); 567 } 568 569 return el; 570 }); 571 } 572 } 573 574 disconnectedCallback() { 575 if (this.type === 'image') { 576 this.lazyInput.removeEventListener('input', this.lazyInputFn); 577 this.altInput.removeEventListener('input', this.inputFn); 578 this.altCheck.removeEventListener('input', this.altCheckFn); 579 } 580 581 if (['audio', 'video', 'document'].includes(this.type)) { 582 [].slice.call(this.querySelectorAll('.form-check-input.radio')).map(el => el.removeEventListener('input', this.embedInputFn)); 583 [].slice.call(this.querySelectorAll('input[type="text"]')).map(el => el.removeEventListener('input', this.embedInputFn)); 584 } 585 586 this.innerHTML = ''; 587 } 588 589 lazyInputFn(e) { 590 this.setAttribute('is-lazy', !!e.target.checked); 591 } 592 593 altCheckFn(e) { 594 this.setAttribute('alt-check', !!e.target.checked); 595 } 596 597 inputFn(e) { 598 const { 599 is 600 } = e.target.dataset; 601 602 if (is) { 603 this.setAttribute(is, e.target.value.replace(/"/g, '"')); 604 } 605 } 606 607 embedInputFn(e) { 608 const { 609 value 610 } = e.target; 611 this.setAttribute('embed-it', value !== '0'); 612 const toggable = this.querySelector('.toggable-parts'); 613 614 if (toggable) { 615 if (toggable.style.display !== 'block') { 616 toggable.style.display = 'block'; 617 } else { 618 toggable.style.display = 'none'; 619 } 620 } 621 } 622 623 } 624 625 customElements.define('joomla-field-mediamore', JoomlaFieldMediaOptions);
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |