notification grouping
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<div class="notification-container">{{{notification}}}</div>
|
||||
{{#if hasGrouped}}
|
||||
<div class="notification-grouped">
|
||||
<div class="notification-grouped list-container-panel">
|
||||
{{#if isGroupExpanded}}
|
||||
{{{groupedList}}}
|
||||
{{else}}
|
||||
|
||||
@@ -32,6 +32,9 @@ import Collection from 'collection';
|
||||
|
||||
class NoteCollection extends Collection {
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
paginationByNumber = false
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
import NoteCollection from 'collections/note';
|
||||
|
||||
export default class NotificationCollection extends NoteCollection {
|
||||
|
||||
paginationByNumber = true
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
import Model from 'model';
|
||||
|
||||
export default class Notification extends Model {
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
constructor(attributes, options) {
|
||||
super(attributes, options);
|
||||
|
||||
if (
|
||||
!options.url &&
|
||||
this.attributes.groupType
|
||||
) {
|
||||
this.urlRoot = 'Notification/group';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -568,6 +568,7 @@ class NotificationBadgeView extends View {
|
||||
});
|
||||
|
||||
this.listenTo(view, 'collection-fetched', () => {
|
||||
console.log(1);
|
||||
this.checkUpdates();
|
||||
this.broadcastNotificationsRead();
|
||||
});
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
import BaseFieldView from 'views/fields/base';
|
||||
import NotificationListRecordView from 'views/notification/record/list';
|
||||
import NotificationPanelView from 'views/notification/panel';
|
||||
|
||||
class NotificationContainerFieldView extends BaseFieldView {
|
||||
|
||||
@@ -59,26 +60,32 @@ class NotificationContainerFieldView extends BaseFieldView {
|
||||
isGroupExpanded = false
|
||||
|
||||
data() {
|
||||
const count = this.model.attributes.groupedCount ?? 0;
|
||||
|
||||
return {
|
||||
hasGrouped: (this.model.attributes.groupedCount ?? 0) > 1,
|
||||
hasGrouped: count > 1 || count < 0,
|
||||
isGroupExpanded: this.isGroupExpanded,
|
||||
};
|
||||
}
|
||||
|
||||
setup() {
|
||||
switch (this.model.attributes.type) {
|
||||
case 'Note':
|
||||
this.processNote(this.model.attributes.noteData);
|
||||
if (this.model.attributes.groupType) {
|
||||
this.wait(this.processGroup());
|
||||
} else {
|
||||
switch (this.model.attributes.type) {
|
||||
case 'Note':
|
||||
this.processNote(this.model.attributes.noteData);
|
||||
|
||||
break;
|
||||
break;
|
||||
|
||||
case 'MentionInPost':
|
||||
this.processMentionInPost(this.model.attributes.noteData);
|
||||
case 'MentionInPost':
|
||||
this.processMentionInPost(this.model.attributes.noteData);
|
||||
|
||||
break;
|
||||
break;
|
||||
|
||||
default:
|
||||
this.process();
|
||||
default:
|
||||
this.process();
|
||||
}
|
||||
}
|
||||
|
||||
this.addActionHandler('showGrouped', () => this.showGrouped());
|
||||
@@ -111,6 +118,32 @@ class NotificationContainerFieldView extends BaseFieldView {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
async processGroup() {
|
||||
const groupType = this.model.attributes.groupType;
|
||||
|
||||
let viewName;
|
||||
|
||||
if (groupType === 'Note') {
|
||||
viewName = 'views/notification/items/group-note';
|
||||
} else if (groupType === 'EmailReceived') {
|
||||
viewName = 'views/notification/items/group-email-received';
|
||||
}
|
||||
|
||||
if (!viewName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parentSelector = this.options.containerSelector ?? this.getSelector();
|
||||
|
||||
await this.createView('notification', viewName, {
|
||||
model: this.model,
|
||||
fullSelector: `${parentSelector} li[data-id="${this.model.id}"]`,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Record} data
|
||||
@@ -140,6 +173,7 @@ class NotificationContainerFieldView extends BaseFieldView {
|
||||
fullSelector: `${parentSelector} li[data-id="${this.model.id}"] .cell[data-name="data"]`,
|
||||
onlyContent: true,
|
||||
isNotification: true,
|
||||
isInGroup: this.options.isInGroup ?? false,
|
||||
});
|
||||
|
||||
this.wait(false);
|
||||
@@ -183,7 +217,14 @@ class NotificationContainerFieldView extends BaseFieldView {
|
||||
async showGrouped() {
|
||||
const collection = await this.getCollectionFactory().create('Notification');
|
||||
|
||||
collection.url = `Notification/${this.model.id}/group`;
|
||||
if (this.model.attributes.groupType) {
|
||||
collection.url = `Notification/group?type=${this.model.attributes.groupType}&id=` +
|
||||
this.model.id;
|
||||
|
||||
collection.maxSize = this.getConfig().get('recordsPerPageSmall');
|
||||
} else {
|
||||
collection.url = `Notification/${this.model.id}/group`;
|
||||
}
|
||||
|
||||
const button = this.element.querySelector('a[data-action="showGrouped"]');
|
||||
|
||||
@@ -221,6 +262,9 @@ class NotificationContainerFieldView extends BaseFieldView {
|
||||
{
|
||||
name: 'data',
|
||||
view: 'views/notification/fields/container',
|
||||
options: {
|
||||
isInGroup: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
@@ -235,6 +279,20 @@ class NotificationContainerFieldView extends BaseFieldView {
|
||||
await this.assignView('groupedList', view);
|
||||
|
||||
await this.reRender();
|
||||
|
||||
let viewPointer = this;
|
||||
|
||||
while (true) {
|
||||
viewPointer = viewPointer.getParentView();
|
||||
|
||||
if (!viewPointer || viewPointer instanceof NotificationPanelView) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (viewPointer instanceof NotificationPanelView) {
|
||||
viewPointer.trigger('collection-fetched');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,12 +41,7 @@ class AssignNotificationItemView extends BaseNotificationItemView {
|
||||
|
||||
this.messageData['entityType'] = this.translateEntityType(data.entityType);
|
||||
|
||||
this.messageData['entity'] =
|
||||
$('<a>')
|
||||
.attr('href', '#' + data.entityType + '/view/' + data.entityId)
|
||||
.attr('data-id', data.entityId)
|
||||
.attr('data-scope', data.entityType)
|
||||
.text(data.entityName);
|
||||
this.messageData['entity'] = 'field:related';
|
||||
|
||||
this.messageData['user'] =
|
||||
$('<a>')
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
|
||||
import BaseNotificationItemView from 'views/notification/items/base';
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
export default class GroupNoteNotificationItemView extends BaseNotificationItemView {
|
||||
|
||||
// language=Handlebars
|
||||
templateContent = `
|
||||
<div class="stream-head-container">
|
||||
<div class="pull-left stream-head-left-icon-container">
|
||||
<span
|
||||
class="far fa-envelope text-soft icon"
|
||||
>
|
||||
</div>
|
||||
<div class="stream-head-text-container">
|
||||
<span class="text-muted message">{{{message}}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stream-date-container">
|
||||
<span class="text-muted small">{{{createdAt}}}</span>
|
||||
</div>
|
||||
`
|
||||
|
||||
data() {
|
||||
return {
|
||||
...super.data(),
|
||||
};
|
||||
}
|
||||
|
||||
setup() {
|
||||
const newCount = this.model.attributes.groupedUnreadCount ?? 0;
|
||||
|
||||
this.messageData['number'] = newCount.toString();
|
||||
|
||||
this.messageName = 'groupEmailsReceivedNew';
|
||||
|
||||
if (newCount === 0) {
|
||||
this.messageName = 'groupEmailsReceived';
|
||||
}
|
||||
|
||||
this.createMessage();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2026 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
import BaseNotificationItemView from 'views/notification/items/base';
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
export default class GroupNoteNotificationItemView extends BaseNotificationItemView {
|
||||
|
||||
// language=Handlebars
|
||||
templateContent = `
|
||||
<div class="stream-head-container">
|
||||
{{#if iconClass}}
|
||||
<div class="pull-left stream-head-left-icon-container">
|
||||
<span
|
||||
class=" {{iconClass}} text-muted action icon"
|
||||
style="
|
||||
cursor: pointer;
|
||||
{{#if color}} color: {{color}}; {{/if}}
|
||||
"
|
||||
title="{{translate 'View'}}"
|
||||
data-action="quickView"
|
||||
data-id="{{relatedParentId}}"
|
||||
data-scope="{{relatedParentType}}"
|
||||
></span>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="stream-head-text-container">
|
||||
<span class="text-muted message">{{{message}}}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{~#if isFeatured~}}
|
||||
<div class="stream-post-container">
|
||||
<span class="label label-default">{{translate 'Assigned'}}</span>
|
||||
</div>
|
||||
{{~/if~}}
|
||||
<div class="stream-date-container">
|
||||
<span class="text-muted small">{{{createdAt}}}</span>
|
||||
</div>
|
||||
`
|
||||
|
||||
data() {
|
||||
const relatedParentType = this.model.attributes.relatedParentType
|
||||
const iconClass = this.getMetadata().get(`clientDefs.${relatedParentType}.iconClass`);
|
||||
const color = this.getMetadata().get(`clientDefs.${relatedParentType}.color`);
|
||||
|
||||
return {
|
||||
...super.data(),
|
||||
relatedParentId: this.model.attributes.relatedParentId,
|
||||
relatedParentType: this.model.attributes.relatedParentType,
|
||||
isFeatured: this.model.attributes.isFeatured,
|
||||
iconClass,
|
||||
color,
|
||||
};
|
||||
}
|
||||
|
||||
setup() {
|
||||
const relatedParentType = this.model.attributes.relatedParentType;
|
||||
|
||||
if (relatedParentType) {
|
||||
this.messageData['entityType'] = this.translateEntityType(relatedParentType);
|
||||
}
|
||||
|
||||
this.messageData['entity'] = 'field:relatedParent';
|
||||
|
||||
const newCount = this.model.attributes.groupedUnreadCount ?? 0;
|
||||
|
||||
this.messageData['number'] = newCount.toString();
|
||||
|
||||
this.messageName = newCount > 1 ? 'groupUpdatesMultiple' : 'groupUpdatesOne';
|
||||
|
||||
if (newCount === 0) {
|
||||
this.messageName = 'groupUpdates';
|
||||
}
|
||||
|
||||
this.createMessage();
|
||||
}
|
||||
}
|
||||
@@ -102,14 +102,8 @@ export default class UserReactionNotificationItemView extends BaseNotificationIt
|
||||
this.messageData['user'] = userElement;
|
||||
|
||||
if (relatedParentId && relatedParentType) {
|
||||
const relatedParentElement = document.createElement('a');
|
||||
relatedParentElement.href = `#${relatedParentType}/view/${relatedParentId}`;
|
||||
relatedParentElement.dataset.id = relatedParentId;
|
||||
relatedParentElement.dataset.scope = relatedParentType;
|
||||
relatedParentElement.textContent = data.entityName || relatedParentType;
|
||||
|
||||
this.messageData['entityType'] = this.translateEntityType(relatedParentType);
|
||||
this.messageData['entity'] = relatedParentElement;
|
||||
this.messageData['entity'] = 'field:relatedParent';
|
||||
|
||||
this.messageName = 'userPostInParentReaction';
|
||||
}
|
||||
|
||||
@@ -108,8 +108,21 @@ class NotificationListRecordView extends ListExpandedRecordView {
|
||||
* @return {Promise}
|
||||
*/
|
||||
showNewRecords() {
|
||||
if (this.isGroupingEnabled()) {
|
||||
return this.collection.fetch();
|
||||
}
|
||||
|
||||
return this.collection.fetchNew();
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Preferences?
|
||||
* @private
|
||||
* @return {boolean}
|
||||
*/
|
||||
isGroupingEnabled() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default NotificationListRecordView;
|
||||
|
||||
@@ -243,6 +243,10 @@ class NoteStreamView extends View {
|
||||
id = this.model.attributes.parentId;
|
||||
}
|
||||
|
||||
if (this.options.isInGroup) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.isThis && this.parentModel && scope === this.parentModel.entityType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,10 @@ class MentionInPostNoteStreamView extends NoteStreamView {
|
||||
this.messageName = 'mentionInPostTarget';
|
||||
}
|
||||
|
||||
if (!this.isUserStream || this.options.userId !== this.getUser().id) {
|
||||
if (
|
||||
(!this.options.isNotification) &&
|
||||
(!this.isUserStream || this.options.userId !== this.getUser().id)
|
||||
) {
|
||||
this.createMessage();
|
||||
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user