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
38 changes: 8 additions & 30 deletions src/bundle/Resources/public/js/scripts/admin.error.page.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,20 @@
(function (global, doc, iconPaths) {
const notificationsContainer = doc.querySelector('.ibexa-notifications-container');
const notifications = JSON.parse(notificationsContainer.dataset.notifications);
const { template } = notificationsContainer.dataset;
const iconsMap = {
info: 'system-information',
error: 'circle-close',
warning: 'warning-triangle',
success: 'checkmark',
};
const escapeHTML = (string) => {
const stringTempNode = doc.createElement('div');
import { Alert } from '@ibexa-design-system/src/bundle/Resources/public/ts/components/alert';

stringTempNode.appendChild(doc.createTextNode(string));
import { appendNotification } from './helpers/notification.helper';

return stringTempNode.innerHTML;
};
(function (doc) {
const notificationsContainer = doc.querySelector('.ibexa-notifications-container');
const notifications = JSON.parse(notificationsContainer.dataset.notifications);
const addNotification = ({ detail }) => {
const { label, message } = detail;
const container = doc.createElement('div');
const iconSetPath = iconPaths.iconSets[iconPaths.defaultIconSet];
const iconPath = `${iconSetPath}#${iconsMap[label]}`;
const finalMessage = escapeHTML(message);

const notification = template
.replace('{{ label }}', label)
.replace('{{ message }}', finalMessage)
.replace('{{ icon_path }}', iconPath);

container.insertAdjacentHTML('beforeend', notification);

const notificationNode = container.querySelector('.alert');
const notificationNode = appendNotification(notificationsContainer, { label, message });

notificationsContainer.append(notificationNode);
new Alert(notificationNode).init();
};

Object.entries(notifications).forEach(([label, messages]) => {
messages.forEach((message) => addNotification({ detail: { label, message } }));
});

doc.body.addEventListener('ibexa-notify', addNotification, false);
})(window, window.document, window.ibexa.iconPaths);
})(window.document);
53 changes: 20 additions & 33 deletions src/bundle/Resources/public/js/scripts/admin.notifications.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,31 @@
(function (global, doc, ibexa, bootstrap) {
import { Alert } from '@ibexa-design-system/src/bundle/Resources/public/ts/components/alert';

import { appendNotification } from './helpers/notification.helper';

(function (global, doc, ibexa) {
const notificationsContainer = doc.querySelector('.ibexa-notifications-container');
const notifications = JSON.parse(notificationsContainer.dataset.notifications);
const { template } = notificationsContainer.dataset;
const iconsMap = {
info: 'about',
error: 'notice',
warning: 'warning',
success: 'approved',
};
const addNotification = ({ detail }) => {
const { onShow, label, message, customIconPath, rawPlaceholdersMap = {} } = detail;
const { onShow, label, message, customIconPath = '' } = detail;
const config = ibexa.adminUiConfig.notifications[label];
const timeout = config ? config.timeout : 0;
const container = doc.createElement('div');
const iconPath = customIconPath ?? ibexa.helpers.icon.getIconPath(iconsMap[label]);
let finalMessage = ibexa.helpers.text.escapeHTML(message);

Object.entries(rawPlaceholdersMap).forEach(([placeholder, rawText]) => {
finalMessage = finalMessage.replace(`{{ ${placeholder} }}`, rawText);
const notificationNode = appendNotification(notificationsContainer, {
label,
message,
onShow: (node) => {
if (customIconPath) {
node.querySelector('.ids-alert__icon use').setAttribute('xlink:href', customIconPath);
}

onShow?.(node);
},
});
const alertInstance = new Alert(notificationNode);

const notification = template
.replace('{{ label }}', label)
.replace('{{ message }}', finalMessage)
.replace('{{ icon_path }}', iconPath);

container.insertAdjacentHTML('beforeend', notification);

const notificationNode = container.querySelector('.alert');

notificationsContainer.append(notificationNode);
alertInstance.init();

if (timeout) {
const alertBootstrapInstance = bootstrap.Alert.getOrCreateInstance(notificationNode);

global.setTimeout(() => alertBootstrapInstance.close(), timeout);
}

if (typeof onShow === 'function') {
onShow(notificationNode);
global.setTimeout(() => alertInstance.dismiss(), timeout);
}
};

Expand All @@ -47,4 +34,4 @@
});

doc.body.addEventListener('ibexa-notify', addNotification, false);
})(window, window.document, window.ibexa, window.bootstrap);
})(window, window.document, window.ibexa);
Original file line number Diff line number Diff line change
@@ -1,19 +1,60 @@
import { getRootDOMElement } from './context.helper';
import { escapeHTML } from './text.helper';

const NOTIFICATION_INFO_LABEL = 'info';
const NOTIFICATION_SUCCESS_LABEL = 'success';
const NOTIFICATION_WARNING_LABEL = 'warning';
const NOTIFICATION_ERROR_LABEL = 'error';

/**
* Returns the notification template rendered for a given label
*
* @function getNotificationTemplate
* @param {HTMLElement} container notifications container
* @param {String} label
* @returns {String}
*/
const getNotificationTemplate = (container, label) => {
const templateName = `template${label.charAt(0).toUpperCase()}${label.slice(1)}`;

return container.dataset[templateName] ?? container.dataset.templateInfo;
};

/**
* Renders a notification from the container's template and appends it to the container
*
* @function appendNotification
* @param {HTMLElement} container notifications container
* @param {Object} config
* @param {String} config.label
* @param {String} config.message message to escape and render
* @param {Function} [config.onShow] called with the notification node before it is appended, so it can still
* be modified (e.g. markup injected into the message, a custom icon)
* without flickering
* @returns {HTMLElement} appended notification node
*/
const appendNotification = (container, { label, message, onShow }) => {
const wrapper = document.createElement('div');
const notification = getNotificationTemplate(container, label).replace('{{ message }}', () => escapeHTML(message));

wrapper.insertAdjacentHTML('beforeend', notification);

const notificationNode = wrapper.querySelector('.ids-alert');

onShow?.(notificationNode);
container.append(notificationNode);

return notificationNode;
};

/**
* Dispatches notification event
*
* @function showNotification
* @param {Object} detail
* @param {String} detail.message
* @param {String} detail.label
* @param {Function} [detail.onShow] to be called after notification Node was added
* @param {Object} detail.rawPlaceholdersMap
* @param {Function} [detail.onShow] to be called with the notification Node before it is shown
*/
const showNotification = (detail) => {
const rootDOMElement = getRootDOMElement();
Expand All @@ -27,67 +68,66 @@ const showNotification = (detail) => {
*
* @function showInfoNotification
* @param {String} message
* @param {Function} [onShow] to be called after notification Node was added
* @param {Object} rawPlaceholdersMap
* @param {Function} [onShow] to be called with the notification Node before it is shown
*/
const showInfoNotification = (message, onShow, rawPlaceholdersMap = {}) =>
const showInfoNotification = (message, onShow) =>
showNotification({
message,
label: NOTIFICATION_INFO_LABEL,
onShow,
rawPlaceholdersMap,
});

/**
* Dispatches success notification event
*
* @function showSuccessNotification
* @param {String} message
* @param {Function} [onShow] to be called after notification Node was added
* @param {Object} rawPlaceholdersMap
* @param {Function} [onShow] to be called with the notification Node before it is shown
*/
const showSuccessNotification = (message, onShow, rawPlaceholdersMap = {}) =>
const showSuccessNotification = (message, onShow) =>
showNotification({
message,
label: NOTIFICATION_SUCCESS_LABEL,
onShow,
rawPlaceholdersMap,
});

/**
* Dispatches warning notification event
*
* @function showWarningNotification
* @param {String} message
* @param {Function} [onShow] to be called after notification Node was added
* @param {Object} rawPlaceholdersMap
* @param {Function} [onShow] to be called with the notification Node before it is shown
*/
const showWarningNotification = (message, onShow, rawPlaceholdersMap = {}) =>
const showWarningNotification = (message, onShow) =>
showNotification({
message,
label: NOTIFICATION_WARNING_LABEL,
onShow,
rawPlaceholdersMap,
});

/**
* Dispatches error notification event
*
* @function showErrorNotification
* @param {(string | Error)} error
* @param {Function} [onShow] to be called after notification Node was added
* @param {Object} rawPlaceholdersMap
* @param {Function} [onShow] to be called with the notification Node before it is shown
*/
const showErrorNotification = (error, onShow, rawPlaceholdersMap = {}) => {
const showErrorNotification = (error, onShow) => {
const isErrorObj = error instanceof Error;
const message = isErrorObj ? error.message : error;

showNotification({
message,
label: NOTIFICATION_ERROR_LABEL,
onShow,
rawPlaceholdersMap,
});
};

export { showNotification, showInfoNotification, showSuccessNotification, showWarningNotification, showErrorNotification };
export {
appendNotification,
showNotification,
showInfoNotification,
showSuccessNotification,
showWarningNotification,
showErrorNotification,
};
18 changes: 14 additions & 4 deletions src/bundle/Resources/public/js/scripts/user.invitation.modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@ export class UserInvitationModal {
this.searchBtn = this.modal.querySelector('.ids-input-text__search-btn, .ibexa-input-text-wrapper__action-btn--search');
this.searchNoEntries = this.modal.querySelector('.ibexa-user-invitation-modal__search-no-entries');
this.badFileAlert = this.modal.querySelector('.ibexa-user-invitation-modal__bad-file-alert');
this.badFileAlertCloseBtn = this.badFileAlert.querySelector('.ibexa-alert__close-btn');
this.issuesAlert = this.modal.querySelector('.ibexa-user-invitation-modal__issues-alert');
this.issuesAlertIssuesContainer = this.modal.querySelector('.ibexa-user-invitation-modal__issues-alert-issues');
this.issuesAlertCloseBtn = this.issuesAlert.querySelector('.ibexa-alert__close-btn');
this.goToNextIssueBtn = this.issuesAlert.querySelector('.ibexa-user-invitation-modal__next-issue-btn');
this.addNextBtn = this.modal.querySelector('.ibexa-user-invitation-modal__add-next-btn');
this.entriesContainer = this.modal.querySelector('.ibexa-user-invitation-modal__entries');
Expand All @@ -38,6 +36,8 @@ export class UserInvitationModal {
this.handleSearch = this.handleSearch.bind(this);
this.handleEmailValidation = this.handleEmailValidation.bind(this);
this.scrollToNextIssue = this.scrollToNextIssue.bind(this);
this.handleBadFileAlertDismiss = this.handleBadFileAlertDismiss.bind(this);
this.handleIssuesAlertDismiss = this.handleIssuesAlertDismiss.bind(this);
}

// eslint-disable-next-line no-unused-vars
Expand Down Expand Up @@ -205,6 +205,16 @@ export class UserInvitationModal {
this.toggleIssuesAlert(isAnyIssue);
}

handleBadFileAlertDismiss(event) {
event.preventDefault();
this.toggleBadFileAlert(false);
}

handleIssuesAlertDismiss(event) {
event.preventDefault();
this.toggleIssuesAlert(false);
}

toggleIssuesAlert(show) {
this.issuesAlert.classList.toggle('ibexa-user-invitation-modal__issues-alert--hidden', !show);
}
Expand Down Expand Up @@ -485,8 +495,8 @@ export class UserInvitationModal {
);
this.fileInput.addEventListener('change', this.handleInputUpload, false);

this.badFileAlertCloseBtn.addEventListener('click', () => this.toggleBadFileAlert(false), false);
this.issuesAlertCloseBtn.addEventListener('click', () => this.toggleIssuesAlert(false), false);
this.badFileAlert.addEventListener('ids:alert:dismiss:before', this.handleBadFileAlertDismiss, false);
this.issuesAlert.addEventListener('ids:alert:dismiss:before', this.handleIssuesAlertDismiss, false);
this.goToNextIssueBtn.addEventListener('click', this.scrollToNextIssue, false);

this.searchInput.addEventListener('keyup', this.handleSearch, false);
Expand Down
7 changes: 7 additions & 0 deletions src/bundle/Resources/public/scss/_alert-adapter.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@use '@ibexa-admin-ui/src/bundle/Resources/public/scss/functions/calculate.rem' as *;

// TODO: Remove together with the `@ibexadesign/ui/component/alert/alert.html.twig` adapter — this only keeps
// the bottom margin that its call sites used to get from Bootstrap's `.alert`.
.ibexa-alert-adapter {
margin-bottom: calculateRem(14px);
}
Loading
Loading