Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions build/media_source/system/joomla.asset.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,29 @@
"defer": true
}
},
{
"name": "messages-legacy",
"type": "script",
"uri": "system/messages-es5.min.js",
"attributes": {
"nomodule": true,
"defer": true
},
"dependencies": [
"core"
]
},
{
"name": "messages",
"type": "script",
"uri": "system/messages.min.js",
"attributes": {
"type": "module"
},
"dependencies": [
"messages-legacy"
]
},
{
"name": "template.active",
"type": "style",
Expand Down
150 changes: 47 additions & 103 deletions build/media_source/system/js/core.es6.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,45 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/

import { sanitizeHtml } from 'bootstrap/js/src/util/sanitizer.js';

const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i;
const DATA_ATTRIBUTE_PATTERN = /^data-[\w-]*$/i;

const DefaultAllowlist = {
// Global attributes allowed on any supplied element below.
'*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN, DATA_ATTRIBUTE_PATTERN],
a: ['target', 'href', 'title', 'rel'],
area: [],
b: [],
br: [],
col: [],
code: [],
div: [],
em: [],
hr: [],
h1: [],
h2: [],
h3: [],
h4: [],
h5: [],
h6: [],
i: [],
img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],
li: [],
ol: [],
p: [],
pre: [],
s: [],
small: [],
span: [],
sub: [],
sup: [],
strong: [],
u: [],
ul: [],
};

// Only define the Joomla namespace if not defined.
window.Joomla = window.Joomla || {};

Expand Down Expand Up @@ -620,112 +659,17 @@ window.Joomla.Modal = window.Joomla.Modal || {
};

/**
* Render messages send via JSON
* Used by some javascripts such as validate.js
* PLEASE NOTE: do NOT use user supplied input in messages as potential HTML markup is NOT
* sanitized!
*
* @param {object} messages JavaScript object containing the messages to render.
* Example:
* const messages = {
* "message": ["This will be a green message", "So will this"],
* "error": ["This will be a red message", "So will this"],
* "info": ["This will be a blue message", "So will this"],
* "notice": ["This will be same as info message", "So will this"],
* "warning": ["This will be a orange message", "So will this"],
* "my_custom_type": ["This will be same as info message", "So will this"]
* };
* @param {string} selector The selector of the container where the message will be rendered
* @param {bool} keepOld If we shall discard old messages
* @param {int} timeout The milliseconds before the message self destruct
* @return void
*/
Joomla.renderMessages = (messages, selector, keepOld, timeout) => {
let messageContainer;
let typeMessages;
let messagesBox;
let title;
let titleWrapper;
let messageWrapper;
let alertClass;

if (typeof selector === 'undefined' || (selector && selector === '#system-message-container')) {
messageContainer = document.getElementById('system-message-container');
} else {
messageContainer = document.querySelector(selector);
}

if (typeof keepOld === 'undefined' || (keepOld && keepOld === false)) {
Joomla.removeMessages(messageContainer);
}

[].slice.call(Object.keys(messages)).forEach((type) => {
// Array of messages of this type
typeMessages = messages[type];
messagesBox = document.createElement('joomla-alert');

if (['notice', 'message', 'error', 'warning'].indexOf(type) > -1) {
alertClass = (type === 'notice') ? 'info' : type;
alertClass = (type === 'message') ? 'success' : alertClass;
alertClass = (type === 'error') ? 'danger' : alertClass;
alertClass = (type === 'warning') ? 'warning' : alertClass;
} else {
alertClass = 'info';
}

messagesBox.setAttribute('type', alertClass);
messagesBox.setAttribute('dismiss', 'true');

if (timeout && parseInt(timeout, 10) > 0) {
messagesBox.setAttribute('auto-dismiss', timeout);
}

// Title
title = Joomla.Text._(type);

// Skip titles with untranslated strings
if (typeof title !== 'undefined') {
titleWrapper = document.createElement('div');
titleWrapper.className = 'alert-heading';
titleWrapper.innerHTML = `<span class="${type}"></span><span class="visually-hidden">${Joomla.Text._(type) ? Joomla.Text._(type) : type}</span>`;
messagesBox.appendChild(titleWrapper);
}

// Add messages to the message box
messageWrapper = document.createElement('div');
messageWrapper.className = 'alert-wrapper';
typeMessages.forEach((typeMessage) => {
messageWrapper.innerHTML += `<div class="alert-message">${typeMessage}</div>`;
});
messagesBox.appendChild(messageWrapper);

messageContainer.appendChild(messagesBox);
});
};

/**
* Remove messages
*
* @param {element} container The element of the container of the message
* to be removed
* @param {string} unsafeHtml The html for sanitization
* @param {object} allowList The list of HTMLElements with an array of allowed attributes
* @param {function} sanitizeFn A custom sanitization function
*
* @return {void}
* @return string
*/
Joomla.removeMessages = (container) => {
let messageContainer;

if (container) {
messageContainer = container;
} else {
messageContainer = document.getElementById('system-message-container');
}

const alerts = [].slice.call(messageContainer.querySelectorAll('joomla-alert'));
if (alerts.length) {
alerts.forEach((alert) => {
alert.close();
});
}
Joomla.sanitizeHtml = (unsafeHtml, allowList, sanitizeFn) => {
const allowed = (allowList === undefined || allowList === null)
? DefaultAllowlist : { ...DefaultAllowlist, ...allowList };
return sanitizeHtml(unsafeHtml, allowed, sanitizeFn);
};

/**
Expand Down
123 changes: 123 additions & 0 deletions build/media_source/system/js/messages.es6.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/**
* @copyright (C) 2020 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/

// Import the Alert Custom Element
import 'joomla-ui-custom-elements/src/js/alert/alert.js';

/**
* Returns the container of the Messages
*
* @param {string|HTMLElement} container The container
*
* @returns {HTMLElement}
*/
const getMessageContainer = (container) => {
let messageContainer;

if (container instanceof HTMLElement) {
return container;
}
if (typeof container === 'undefined' || (container && container === '#system-message-container')) {
messageContainer = document.getElementById('system-message-container');
} else {
messageContainer = document.querySelector(container);
}

return messageContainer;
};

/**
* Render messages send via JSON
* Used by some javascripts such as validate.js
*
* @param {object} messages JavaScript object containing the messages to render.
* Example:
* const messages = {
* "message": ["This will be a green message", "So will this"],
* "error": ["This will be a red message", "So will this"],
* "info": ["This will be a blue message", "So will this"],
* "notice": ["This will be same as info message", "So will this"],
* "warning": ["This will be a orange message", "So will this"],
* "my_custom_type": ["This will be same as info message", "So will this"]
* };
* @param {string} selector The selector of the container where the message will be rendered
* @param {bool} keepOld If we shall discard old messages
* @param {int} timeout The milliseconds before the message self destruct
* @return void
*/
Joomla.renderMessages = (messages, selector, keepOld, timeout) => {
const messageContainer = getMessageContainer(selector);
if (typeof keepOld === 'undefined' || (keepOld && keepOld === false)) {
Joomla.removeMessages(messageContainer);
}

[].slice.call(Object.keys(messages)).forEach((type) => {
let alertClass = type;

// Array of messages of this type
const typeMessages = messages[type];
const messagesBox = document.createElement('joomla-alert');

if (['success', 'info', 'danger', 'warning'].indexOf(type) < 0) {
alertClass = (type === 'notice') ? 'info' : type;
alertClass = (type === 'message') ? 'success' : alertClass;
alertClass = (type === 'error') ? 'danger' : alertClass;
alertClass = (type === 'warning') ? 'warning' : alertClass;
}

messagesBox.setAttribute('type', alertClass);
messagesBox.setAttribute('dismiss', true);

if (timeout && parseInt(timeout, 10) > 0) {
messagesBox.setAttribute('auto-dismiss', timeout);
}

// Title
const title = Joomla.Text._(type);

// Skip titles with untranslated strings
if (typeof title !== 'undefined') {
const titleWrapper = document.createElement('div');
titleWrapper.className = 'alert-heading';
titleWrapper.innerHTML = Joomla.sanitizeHtml(`<span class="${type}"></span><span class="visually-hidden">${Joomla.Text._(type) ? Joomla.Text._(type) : type}</span>`);
messagesBox.appendChild(titleWrapper);
}

// Add messages to the message box
const messageWrapper = document.createElement('div');
messageWrapper.className = 'alert-wrapper';
typeMessages.forEach((typeMessage) => {
messageWrapper.innerHTML += Joomla.sanitizeHtml(`<div class="alert-message">${typeMessage}</div>`);
});
messagesBox.appendChild(messageWrapper);
messageContainer.appendChild(messagesBox);
});
};

/**
* Remove messages
*
* @param {element} container The element of the container of the message
* to be removed
*
* @return {void}
*/
Joomla.removeMessages = (container) => {
const messageContainer = getMessageContainer(container);
const alerts = [].slice.call(messageContainer.querySelectorAll('joomla-alert'));
if (alerts.length) {
alerts.forEach((alert) => {
alert.close();
});
}
};

document.addEventListener('DOMContentLoaded', () => {
const messages = Joomla.getOptions('joomla.messages');
if (messages) {
Object.keys(messages)
.map((message) => Joomla.renderMessages(messages[message], undefined, true, undefined));
}
});
2 changes: 1 addition & 1 deletion installation/template/error.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

$this->getWebAssetManager()
->useStyle('webcomponent.joomla-alert')
->useScript('webcomponent.joomla-alert');
->useScript('messages');

// Add script options
$this->addScriptOptions('system.installation', ['url' => Route::_('index.php')]);
Expand Down
2 changes: 1 addition & 1 deletion installation/template/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

$this->getWebAssetManager()
->useStyle('webcomponent.joomla-alert')
->useScript('webcomponent.joomla-alert')
->useScript('messages')
->useScript('webcomponent.core-loader');


Expand Down
Loading