Merge pull request #1848 from cryptpad/notification-page

Improve notification page accessibility, responsiveness and UI
This commit is contained in:
yflory 2025-06-27 15:36:51 +02:00 committed by GitHub
commit f3b4bbcbc7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 133 additions and 89 deletions

View File

@ -141,6 +141,8 @@ define(req, function(AppConfig, Default, Language) {
Messages.badges_premium = "Premium user";
Messages.badges_error = "Error while validating this user's data";
Messages.profile_badges = "Badges";
Messages.user_profile = 'Go to user profile';
Messages.add_password = "Add your document password";
Messages.show_password = "Show password";
Messages.hide_password = "Hide password";
Messages.page_next = "Next page";

View File

@ -4469,5 +4469,61 @@ define([
return container;
};
UIElements.reorderDOM = function ($content, isDrawer) {
var reorderDOM = Util.throttle(function ($content, observer) {
if (!$content.length) { return; }
// List all children based on their "order" property
var map = {};
$content[0].childNodes.forEach((node) => {
try {
if (!node.attributes) { return; }
let nodeWithOrder;
if (isDrawer) { // HACK: the order is set on their inner "a" tag
let $n = $(node);
if (!$n.attr('class') &&
($n.find('.fa').length || $n.find('.cptools').length)) {
nodeWithOrder = $n.find('.fa')[0] || $n.find('.cptools')[0];
}
}
var order = getComputedStyle(nodeWithOrder || node).getPropertyValue("order");
var a = map[order] = map[order] || [];
a.push(node);
} catch (e) { console.error(e, node); }
});
// Disconnect the observer while we're reordering to avoid infinite loop
observer.disconnect();
Object.keys(map).sort(function (a, b) {
return Number(a) - Number(b);
}).forEach(function (k) {
var arr = map[k];
if (!Number(k)) { return; } // No need to "append" if order is -1
// Reorder
arr.forEach(function (node) {
$content.append(node);
});
});
observer.start();
}, 100);
let observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.addedNodes.length) {
reorderDOM($content, observer);
}
});
});
observer.start = function () {
if (!$content.length) { return; }
observer.observe($content[0], {
childList: true
});
};
observer.start();
};
return UIElements;
});

View File

@ -81,17 +81,22 @@ define([
Common.openURL(Hash.hashToHref('', 'calendar'));
});
} else if (userData && typeof(userData) === "object" && userData.profile) {
avatar = h('span.cp-avatar');
avatar = h('span.cp-avatar',{
tabindex: 0,
title: Messages.user_profile,
'aria-label': Messages.user_profile,
role: 'button'
});
Common.displayAvatar($(avatar), userData.avatar, userData.displayName || userData.name);
$(avatar).click(function (e) {
const handler = function (e) {
e.stopPropagation();
Common.openURL(Hash.hashToHref(userData.profile, 'profile'));
});
};
Util.onClickEnter($(avatar), handler, { space: true });
} else if (userData && userData.supportTeam) {
avatar = h('span.cp-avatar-image', h('img', { src:'/customize/CryptPad_logo.svg' }));
}
var order = -Math.floor((Util.find(data, ['content', 'msg', 'ctime']) || 0) / 1000);
const tabIndexValue = undefined;//data.content.isDismissible ? undefined : '0';
notif = h('li.cp-notification', {
role: 'menuitem',
tabindex: '0',
@ -100,7 +105,7 @@ define([
}, [
avatar,
h('div.cp-notification-content', {
tabindex: tabIndexValue
tabindex: 0
}, [
h('p', data.content.msg.type + ' - ' +formatData(data))
])
@ -119,15 +124,19 @@ define([
$(notif).find('.cp-notification-content p').html(data.content.getFormatText());
}, 60000);
}
const label = $(notif).find('.cp-notification-content p').text();
$(notif).find('.cp-notification-content').attr('aria-label', label);
}
$(notif).mouseenter((e) => {
e.stopPropagation();
$(notif).focus();
if($(notif).find('li[tabindex="0"]').length) {
$(notif).focus();
};
});
if (data.content.isClickable) {
$(notif).find('.cp-notification-content').addClass("cp-clickable").on('click keypress', function (event) {
$(notif).find('.cp-notification-content').addClass("cp-clickable").attr('role', 'link').on('click keypress', function (event) {
if (event.type === 'click' || (event.type === 'keypress' && event.which === 13)) {
data.content.handler();
}
@ -136,7 +145,10 @@ define([
if (data.content.isDismissible) {
var dismissIcon = h('span.fa.fa-times');
var dismiss = h('div.cp-notification-dismiss', {
tabindex: 0,
title: Messages.notifications_dismiss,
'aria-label': Messages.notifications_dismiss,
role: 'button'
}, dismissIcon);
$(dismiss).addClass("cp-clickable")
.on('click keypress', function (event) {

View File

@ -65,60 +65,6 @@ MessengerUI, Messages, Pages, PadTypes) {
return 'cp-toolbar-uid-' + String(Math.random()).substring(2);
};
var observeChildren = function ($content, isDrawer) {
var reorderDOM = Util.throttle(function ($content, observer) {
if (!$content.length) { return; }
// List all children based on their "order" property
var map = {};
$content[0].childNodes.forEach((node) => {
try {
if (!node.attributes) { return; }
let nodeWithOrder;
if (isDrawer) { // HACK: the order is set on their inner "a" tag
let $n = $(node);
if (!$n.attr('class') &&
($n.find('.fa').length || $n.find('.cptools').length)) {
nodeWithOrder = $n.find('.fa')[0] || $n.find('.cptools')[0];
}
}
var order = getComputedStyle(nodeWithOrder || node).getPropertyValue("order");
var a = map[order] = map[order] || [];
a.push(node);
} catch (e) { console.error(e, node); }
});
// Disconnect the observer while we're reordering to avoid infinite loop
observer.disconnect();
Object.keys(map).sort(function (a, b) {
return Number(a) - Number(b);
}).forEach(function (k) {
var arr = map[k];
if (!Number(k)) { return; } // No need to "append" if order is 0
// Reorder
arr.forEach(function (node) {
$content.append(node);
});
});
observer.start();
}, 100);
let observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.addedNodes.length) {
reorderDOM($content, observer);
}
});
});
observer.start = function () {
if (!$content.length) { return; }
observer.observe($content[0], {
childList: true
});
};
observer.start();
};
var createRealtimeToolbar = function (config) {
if (!config.$container) { return; }
var $container = config.$container;
@ -1231,7 +1177,7 @@ MessengerUI, Messages, Pages, PadTypes) {
$button.attr('aria-label', Messages.notificationsPage);
var $n = $button.find('.cp-dropdown-button-title').hide();
var $empty = $(div).find('.cp-notifications-empty');
observeChildren($(div));
UIElements.reorderDOM($(div));
var refresh = function () {
updateUserList(toolbar, config);
@ -1457,14 +1403,14 @@ MessengerUI, Messages, Pages, PadTypes) {
toolbar.$history = $toolbar.find('.'+Bar.constants.history);
toolbar.$user = $toolbar.find('.'+Bar.constants.userAdmin);
observeChildren(toolbar.$drawer, true);
observeChildren(toolbar.$bottomL);
observeChildren(toolbar.$bottomM);
observeChildren(toolbar.$bottomR);
observeChildren(toolbar.$top);
observeChildren(toolbar.$user);
UIElements.reorderDOM(toolbar.$drawer, true);
UIElements.reorderDOM(toolbar.$bottomL);
UIElements.reorderDOM(toolbar.$bottomM);
UIElements.reorderDOM(toolbar.$bottomR);
UIElements.reorderDOM(toolbar.$top);
UIElements.reorderDOM(toolbar.$user);
if (config.$contentContainer) {
observeChildren(config.$contentContainer);
UIElements.reorderDOM(config.$contentContainer);
}
toolbar.$userAdmin = $toolbar.find('.'+Bar.constants.userAdmin);

View File

@ -21,6 +21,10 @@
&:hover {
background-color: @cp_notif-hover;
}
&:focus-visible {
outline: @variables_focus_style;
border-radius: @variables_radius;
}
}
.cp-app-notifications-panel {
@ -59,6 +63,8 @@
justify-content: center;
align-items: center;
width: 3rem;
border-radius: @variables_radius;
margin: 0.2rem;
}
}
}
@ -90,16 +96,20 @@
}
.cp-avatar {
.avatar_main(48px);
padding: 0 10px;
padding: 5px;
margin: 0.2rem;
cursor: pointer;
&:hover {
background-color: @cp_notif-hover;
}
&:focus-visible {
outline: @variables_focus_style;
border-radius: @variables_radius;
}
}
.cp-avatar-calendar {
font-size: 45px;
padding: 0 12px;
overflow: hidden;
padding: 12px;
}
&.cp-app-notification-archived {
background-color: @cp_notif-bg;
@ -114,16 +124,18 @@
flex-direction: row;
justify-content: flex-start;
align-items: center;
margin: 0.1rem;
&:focus-visible {
outline: @variables_focus_style;
border-radius: @variables_radius;
}
p {
display: inline-block;
margin: 1rem 1rem;
flex: 1;
min-width: 0;
}
.notification-time {
margin: 1rem 1rem;
color: grey;
margin-left: auto;
margin: 1rem 1rem 1rem auto;
}
}
@ -136,6 +148,10 @@
align-items: center;
border-left: 1px solid @cp_notif-table-border;
width: 3rem;
margin: 0.2rem;
&:focus-visible{
border-radius: @variables_radius;
}
}
}
}

View File

@ -12,7 +12,9 @@ define([
'/common/hyperscript.js',
'/customize/messages.js',
'/common/common-interface.js',
'/common/common-ui-elements.js',
'/common/notifications.js',
'/common/common-util.js',
'css!/components/bootstrap/dist/css/bootstrap.min.css',
'css!/components/components-font-awesome/css/font-awesome.min.css',
@ -27,7 +29,9 @@ define([
h,
Messages,
UI,
Notifications
UIElements,
Notifications,
Util,
)
{
var APP = {};
@ -74,14 +78,16 @@ define([
h("h5.cp-app-notifications-panel-title",
(Messages.notificationsPage || "Notifications") + " - " + categoryName),
h("div.cp-app-notifications-panel-titlebar-buttons", [
dismissAll = h("div.cp-app-notifications-dismissall.cp-clickable", { title: Messages.notifications_dismissAll || "Dismiss All" }, h("span.fa.fa-trash")),
dismissAll = h("div.cp-app-notifications-dismissall.cp-clickable", { tabindex: 0, title: Messages.notifications_dismissAll || "Dismiss All", 'aria-label': Messages.notifications_dismissAll || "Dismiss All", role: 'button' }, h("span.fa.fa-trash")),
]),
]),
notifsList = h("div.cp-app-notifications-panel-list", [
notifsList = h("div.cp-app-notifications-panel-list", {'role': 'list', 'aria-label': (Messages.notificationsPage || "Notifications") + " " + categoryName }, [
h("div.cp-notification.no-notifications", Messages.notifications_empty),
]),
]);
UIElements.reorderDOM($(notifsList));
// add notification
var addNotification = function (data, el) {
// if the type of notification correspond
@ -89,6 +95,8 @@ define([
notifsData.push(data);
var icon = $(el).find(".cp-reminder");
$(icon).addClass('cp-avatar-calendar');
$(el).attr('tabindex', -1);
$(el).attr('role', 'listitem');
$(notifsList).prepend(el);
}
};
@ -107,6 +115,7 @@ define([
var time = new Date(data.content.time);
$(el).find(".cp-notification-content").append(h("span.notification-time", time.toLocaleString()));
$(el).addClass("cp-app-notification-archived");
$(el).attr('tabindex', -1);
if (isDataUnread) {
$(el).hide();
} else {
@ -122,7 +131,7 @@ define([
var loadmore;
var lastKnownHash;
$(dismissAll).remove();
loadmore = h("div.cp-app-notification-loadmore.cp-clickable", Messages.history_loadMore);
loadmore = h("button.cp-app-notification-loadmore.cp-clickable", Messages.history_loadMore);
$(loadmore).click(function () {
common.mailbox.getNotificationsHistory('notifications', 10, lastKnownHash, function (err, messages, end) {
if (!Array.isArray(messages)) { return; }
@ -153,14 +162,17 @@ define([
$('.cp-app-notification-archived[data-hash="' + data.hash + '"]').css('display', 'flex');
}
});
$(dismissAll).click(function () {
const handler = function (e) {
if (!notifsData) {
return;
}
notifsData.forEach(function (data) {
if (data.content.isDismissible) {
data.content.dismissHandler();
}
});
});
};
Util.onClickEnter($(dismissAll), handler, { space: true });
return $div;
};
@ -198,18 +210,18 @@ define([
});
};
var createLeftside = function () {
var $categories = $('<div>', {'class': 'cp-sidebarlayout-categories'})
var $categories = $('<div>', {'class': 'cp-sidebarlayout-categories', 'role': 'menu' })
.appendTo(APP.$leftside);
var metadataMgr = common.getMetadataMgr();
var privateData = metadataMgr.getPrivateData();
var active = privateData.category || 'all';
common.setHash(active);
Object.keys(categories).forEach(function (key) {
var $category = $('<div>', {'class': 'cp-sidebarlayout-category', 'tabindex': 0}).appendTo($categories);
if (key === 'all') { $category.append($('<span>', {'class': 'fa fa-bars'})); }
if (key === 'friends') { $category.append($('<span>', {'class': 'fa fa-user'})); }
if (key === 'pads') { $category.append($('<span>', {'class': 'cptools cptools-richtext'})); }
if (key === 'archived') { $category.append($('<span>', {'class': 'fa fa-archive'})); }
var $category = $('<div>', {'class': 'cp-sidebarlayout-category', 'tabindex': 0, 'role': 'menuitem'}).appendTo($categories);
if (key === 'all') { $category.append($('<span>', {'class': 'fa fa-bars', 'aria-hidden': 'true'})); }
if (key === 'friends') { $category.append($('<span>', {'class': 'fa fa-user', 'aria-hidden': 'true'})); }
if (key === 'pads') { $category.append($('<span>', {'class': 'cptools cptools-richtext', 'aria-hidden': 'true'})); }
if (key === 'archived') { $category.append($('<span>', {'class': 'fa fa-archive', 'aria-hidden': 'true'})); }
if (key === active) {
$category.addClass('cp-leftside-active');